View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.devacfr.maven.skins.reflow.snippet;
20  
21  import javax.annotation.Nonnull;
22  import javax.annotation.Nullable;
23  
24  import java.util.Map;
25  import java.util.stream.Collectors;
26  
27  import com.google.common.base.MoreObjects;
28  import com.google.common.collect.Maps;
29  import org.devacfr.maven.skins.reflow.snippet.ComponentToken.Type;
30  import org.jsoup.nodes.Attribute;
31  import org.jsoup.nodes.Attributes;
32  import org.jsoup.nodes.Element;
33  import org.jsoup.nodes.Node;
34  import org.jsoup.nodes.TextNode;
35  import org.jsoup.parser.Tag;
36  
37  import static java.util.Objects.requireNonNull;
38  
39  /**
40   * Base of Snippet component.
41   *
42   * @author Christophe Friederich
43   * @version 2.4
44   * @param <T>
45   *            type of component
46   */
47  public class Component<T extends Component<T>> {
48  
49      /** */
50      private final Map<String, String> attributes = Maps.newHashMap();
51  
52      /** */
53      private Component<?> parent;
54  
55      /** */
56      private final Components children = new Components();
57  
58      /** */
59      private final Map<String, Components> childrenMap = Maps.newHashMap();
60  
61      /** */
62      private final Node node;
63  
64      /**
65       * @param node
66       * @param parent
67       * @return
68       */
69      public static Component<?> createComponent(@Nonnull final Node node, final Component<?> parent) {
70          return new Component<>(node).withParent(parent).addAttributes(node.attributes());
71      }
72  
73      /**
74       * @param node
75       */
76      protected Component(@Nonnull final Node node) {
77          this.node = requireNonNull(node);
78      }
79  
80      /**
81       * @return the name
82       */
83      public String getName() {
84          return node.nodeName();
85      }
86  
87      /**
88       * @return
89       */
90      @Nonnull
91      Type getInternalType() {
92          return getRootParent().getType();
93      }
94  
95      /**
96       * @return
97       */
98      public boolean isHtmlTag() {
99          return node instanceof TextNode || Tag.isKnownTag(node.nodeName());
100     }
101 
102     /**
103      * @return
104      */
105     public String getHtml() {
106         if (!isHtmlTag()) {
107             if (children.isEmpty()) {
108                 return null;
109             } else {
110                 return children.html();
111             }
112         } else {
113             return this.node.outerHtml();
114         }
115     }
116 
117     /**
118      * @return
119      */
120     public String getOwnHtml() {
121         if (!isHtmlTag()) {
122             return null;
123         } else {
124             return this.node.outerHtml();
125         }
126     }
127 
128     /**
129      * @return the parent
130      */
131     public Component<?> getParent() {
132         return parent;
133     }
134 
135     @Nullable protected Element getElement() {
136         if (this.node instanceof Element) {
137             return (Element) this.node;
138         }
139         return null;
140     }
141 
142     /**
143      * @param name
144      * @return
145      */
146     public Object get(@Nonnull final String name) {
147         requireNonNull(name);
148         String key = name.toLowerCase();
149         // if attribute
150         if (this.attributes.containsKey(key)) {
151             return this.attributes.get(key);
152         } else {
153             // is children component?
154             // check if 's' suffix allowing to retrieve children component as list
155             if (key.endsWith("s") && !this.childrenMap.containsKey(key)) {
156                 key = key.substring(0, key.length() - 1);
157                 if (this.childrenMap.containsKey(key)) {
158                     return this.childrenMap.get(key);
159                 }
160             } else if (this.childrenMap.containsKey(key)) {
161                 final Components value = this.childrenMap.get(key);
162                 // TODO i don't know if good idea, but it's works.
163                 if (value.size() > 1) {
164                     return value;
165                 }
166                 return value.first();
167             }
168         }
169         return null;
170     }
171 
172     public Map<String, String> getAttrs() {
173         return this.attributes;
174     }
175 
176     /**
177      * @return the children
178      */
179     public Components getChildren() {
180         return children;
181     }
182 
183     public Components getChildren(final String name) {
184         final String key = requireNonNull(name).toLowerCase();
185         if (this.childrenMap.containsKey(key)) {
186             return this.childrenMap.get(key);
187         }
188         return Components.empty();
189     }
190 
191     public T addChild(final Component<?> component) {
192         final String key = component.getName();
193         Components value = null;
194         if (!this.childrenMap.containsKey(key)) {
195             value = new Components();
196             this.childrenMap.put(key, value);
197         } else {
198             value = this.childrenMap.get(key);
199         }
200         value.add(component);
201         this.children.add(component);
202         return self();
203     }
204 
205     protected T withParent(final Component<?> parent) {
206         this.parent = parent;
207         return self();
208     }
209 
210     @Nonnull
211     protected SnippetComponent<?> getRootParent() {
212         Component<?> parent = this.parent;
213         while (!(parent instanceof SnippetComponent<?>)) {
214             parent = parent.parent;
215         }
216         return (SnippetComponent<?>) parent;
217     }
218 
219     /**
220      * @param attrs
221      * @return
222      */
223     protected T addAttributes(@Nonnull final Attributes attrs) {
224         attrs.asList().stream().forEach(this::addAttribute);
225         return self();
226     }
227 
228     /**
229      * @param attr
230      * @return
231      */
232     protected T addAttribute(@Nonnull final Attribute attr) {
233         this.attributes.put(requireNonNull(attr.getKey()).toLowerCase(), requireNonNull(attr.getValue()));
234         return self();
235     }
236 
237     /**
238      * @return
239      */
240     @SuppressWarnings("unchecked")
241     protected T self() {
242         return (T) this;
243     }
244 
245     /**
246      * {@inheritDoc}
247      */
248     @Override
249     public String toString() {
250         return MoreObjects.toStringHelper(this)
251                 .add("name", this.getName())
252                 .add("isHtmlTag", this.isHtmlTag())
253                 .add("attributes", this.attributes)
254                 .add("children", this.children.stream().map((cpt) -> cpt.getName()).collect(Collectors.toList()))
255                 .toString();
256     }
257 }