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.apache.rat.documentation.velocity;
20  
21  import java.lang.reflect.InvocationTargetException;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.Comparator;
25  import java.util.Objects;
26  import java.util.Set;
27  import java.util.TreeSet;
28  import java.util.UUID;
29  import java.util.stream.Collectors;
30  
31  import org.apache.commons.lang3.StringUtils;
32  import org.apache.rat.analysis.IHeaderMatcher;
33  import org.apache.rat.analysis.matchers.AbstractMatcherContainer;
34  import org.apache.rat.config.parameters.ComponentType;
35  import org.apache.rat.config.parameters.Description;
36  import org.apache.rat.config.parameters.DescriptionBuilder;
37  import org.apache.rat.configuration.XMLConfig;
38  
39  /**
40   * The Matcher representation for documentation.
41   */
42  public class Matcher {
43      /**
44       * The description for this matcher.
45       */
46      private final Description desc;
47  
48      /**
49       * The header matcher we are wrapping. May be {@code null}.
50       */
51      private final IHeaderMatcher self;
52  
53      /**
54       * The description of the enclosed attribute. May be null.
55       */
56      private final Enclosed enclosed;
57  
58      /**
59       * The set of attributes for this matcher.
60       */
61      private final Set<Attribute> attributes;
62  
63      /**
64       * Copy constructor.
65       * @param matcher the matcher to copy.
66       */
67      Matcher(final Matcher matcher) {
68          this.desc = matcher.desc;
69          this.self = matcher.self;
70          this.enclosed = matcher.enclosed;
71          this.attributes = matcher.attributes;
72      }
73  
74      /**
75       * Creates from a system Matcher.
76       * @param self the IHeaderMatcher to wrap.
77       */
78      Matcher(final IHeaderMatcher self) {
79          this(DescriptionBuilder.buildMap(self.getClass()), self);
80      }
81  
82      /**
83       * Creates from a description and a system matcher.
84       * @param desc the description of the matcher.
85       * @param self the matcher to wrap. May be {@code null}.
86       */
87      Matcher(final Description desc, final IHeaderMatcher self) {
88          Objects.requireNonNull(desc);
89          this.desc = desc;
90          this.self = self;
91          Enclosed enclosed = null;
92          attributes = new TreeSet<>(Comparator.comparing(Attribute::getName));
93          for (Description child : desc.childrenOfType(ComponentType.PARAMETER)) {
94              if (XMLConfig.isInlineNode(desc.getCommonName(), child.getCommonName())) {
95                  enclosed = new Enclosed(child);
96              } else {
97                  attributes.add(new Attribute(child));
98              }
99          }
100         this.enclosed = enclosed;
101     }
102 
103     /**
104      * Get the name of the matcher type.
105      * @return the name of the matcher type.
106      */
107     public String getName() {
108         return desc.getCommonName();
109     }
110 
111     /**
112      * Gets the description of the matcher type.
113      * @return The description of the matcher type or an empty string.
114      */
115     public String getDescription() {
116         return StringUtils.defaultIfEmpty(desc.getDescription(), "");
117     }
118 
119     /**
120      * If the matcher encloses another matcher return the definition of that enclosure.
121      * @return the description of the enclose matcher. May be {@code null}.
122      */
123     public Enclosed getEnclosed() {
124         return enclosed;
125     }
126 
127     /**
128      * Gets the attributes of this matcher.
129      * @return a collection of attributes for this matcher. May be empty but not {@code null}.
130      */
131     public Collection<Attribute> getAttributes() {
132         return attributes;
133     }
134 
135     /**
136      * Gets the direct children of this matcher.
137      * @return A collection of the direct children of this matcher. May be empty but not {@code null}.
138      */
139     Collection<Matcher> getChildren() {
140         if (self != null && enclosed != null && IHeaderMatcher.class.equals(enclosed.desc.getChildType())) {
141             if (self instanceof AbstractMatcherContainer) {
142                 AbstractMatcherContainer matcherContainer = (AbstractMatcherContainer) self;
143                 return matcherContainer.getEnclosed().stream().map(Matcher::new).collect(Collectors.toList());
144             }
145             try {
146                 IHeaderMatcher matcher = (IHeaderMatcher) enclosed.desc.getter(self.getClass()).invoke(self);
147                 return Collections.singleton(new Matcher(matcher));
148             } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
149                 throw new RuntimeException(e);
150             }
151         }
152         return Collections.emptyList();
153     }
154 
155     /**
156      * The description of an enclosed matcher.
157      */
158     public final class Enclosed {
159         /** The description for the enclosed item */
160         private final Description desc;
161 
162         /**
163          * Constructor.
164          * @param desc the description of the enclosed matcher.
165          */
166         Enclosed(final Description desc) {
167             this.desc = desc;
168         }
169 
170         /**
171          * Gets the required flag.
172          * @return the word "required" or "optional" depending on the state of the required flag.
173          */
174         public String getRequired() {
175             return desc.isRequired() ? "required" : "optional";
176         }
177 
178         /**
179          * Gets the phrase "or more " if the enclosed matcher may be multiple.
180          * @return the phrase "or more " or an empty string.
181          */
182         public String getCollection() {
183             return desc.isCollection() ? "or more " : "";
184         }
185 
186         /**
187          * Get the type of the enclosed matcher.
188          * @return the type of the enclosed matcher.
189          */
190         public String getType() {
191             return desc.getChildType().getSimpleName();
192         }
193 
194         /**
195          * Gets the value of the enclosed matcher.
196          *  <ul>
197          *  <li>If the Matcher does not have an {@link IHeaderMatcher} defined this method returns {@code null}.</li>
198          *  <li>If the value is a string it is normalized before being returned.</li>
199          *  </ul>
200          * @return the value of the enclosed matcher in the provided {@link IHeaderMatcher}.
201          * @see StringUtils#normalizeSpace(String)
202          */
203         public Object getValue() {
204             if (self != null) {
205                 try {
206                     Object value = desc.getter(self.getClass()).invoke(self);
207                     return value instanceof String ? StringUtils.normalizeSpace((String) value) : value;
208                 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
209                     throw new RuntimeException(e);
210                 }
211             }
212             return null;
213         }
214         Class<?> getChildType() {
215             return desc.getChildType();
216         }
217     }
218 
219     /**
220      * The definition of an attribute.
221      */
222     public final class Attribute {
223         /**
224          * The description for the attribute.
225          */
226         private final Description description;
227 
228         /**
229          * Constructor
230          * @param description the description for this attribute.
231          */
232         Attribute(final Description description) {
233             this.description = description;
234         }
235 
236         /**
237          * Gets the attribute name.
238          * @return the attribut name.
239          */
240         public String getName() {
241             return description.getCommonName();
242         }
243 
244         /**
245          * Gets the required flag.
246          * @return the word "required" or "optional" depending on the state of the required flag.
247          */
248         public String getRequired() {
249             return description.isRequired() ? "required" : "optional";
250         }
251 
252         /**
253          * Get the type of the attribute.
254          * @return the type of the enclosed matcher.
255          */
256         public String getType() {
257             return description.getChildType().getSimpleName();
258         }
259 
260         /**
261          * Gets the description of the attribute or an empty string.
262          * @return the description of the attribute or an empty string.
263          */
264         public String getDescription() {
265             return StringUtils.defaultIfEmpty(description.getDescription(), "");
266         }
267 
268         /**
269          * Gets the value of the enclosed matcher.
270          * <ul>
271          * <li>If the Matcher does not have an {@link IHeaderMatcher} defined this method returns {@code null}.</li>
272          * <li>If the value of the attribute (self) is {@code null}, this method returns {@code null}.</li>
273          * <li>If the value is a string, it is normalized before being returned.</li>
274          * <li>If the attribute is an "id" and the value is a UUID, {@code null} is returned.</li>
275          * <li>otherwise, the string value is returned.</li>
276          * </ul>
277          * @return the value of the enclosed matcher in the provided {@link IHeaderMatcher}.
278          * @see StringUtils#normalizeSpace(String)
279          */
280         public String getValue() {
281             if (self != null) {
282                 try {
283                     Object value = description.getter(self.getClass()).invoke(self);
284                     if (value != null) {
285                         String result = value.toString();
286                         if (description.getCommonName().equals("id")) {
287                             try {
288                                 UUID.fromString(result);
289                                 return null;
290                             } catch (IllegalArgumentException e) {
291                                 // do nothing.
292                             }
293                         }
294                         return result;
295                     }
296                 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
297                     throw new RuntimeException(e);
298                 }
299             }
300             return null;
301         }
302     }
303 }