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 matcherContainer) {
142                 return matcherContainer.getEnclosed().stream().map(Matcher::new).collect(Collectors.toList());
143             }
144             try {
145                 IHeaderMatcher matcher = (IHeaderMatcher) enclosed.desc.getter(self.getClass()).invoke(self);
146                 return Collections.singleton(new Matcher(matcher));
147             } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
148                 throw new RuntimeException(e);
149             }
150         }
151         return Collections.emptyList();
152     }
153 
154     /**
155      * The description of an enclosed matcher.
156      */
157     public final class Enclosed {
158         /** The description for the enclosed item */
159         private final Description desc;
160 
161         /**
162          * Constructor.
163          * @param desc the description of the enclosed matcher.
164          */
165         Enclosed(final Description desc) {
166             this.desc = desc;
167         }
168 
169         /**
170          * Gets the required flag.
171          * @return the word "required" or "optional" depending on the state of the required flag.
172          */
173         public String getRequired() {
174             return desc.isRequired() ? "required" : "optional";
175         }
176 
177         /**
178          * Gets the phrase "or more " if the enclosed matcher may be multiple.
179          * @return the phrase "or more " or an empty string.
180          */
181         public String getCollection() {
182             return desc.isCollection() ? "or more " : "";
183         }
184 
185         /**
186          * Get the type of the enclosed matcher.
187          * @return the type of the enclosed matcher.
188          */
189         public String getType() {
190             return desc.getChildType().getSimpleName();
191         }
192 
193         /**
194          * Gets the value of the enclosed matcher.
195          *  <ul>
196          *  <li>If the Matcher does not have an {@link IHeaderMatcher} defined this method returns {@code null}.</li>
197          *  <li>If the value is a string it is normalized before being returned.</li>
198          *  </ul>
199          * @return the value of the enclosed matcher in the provided {@link IHeaderMatcher}.
200          * @see StringUtils#normalizeSpace(String)
201          */
202         public Object getValue() {
203             if (self != null) {
204                 try {
205                     Object value = desc.getter(self.getClass()).invoke(self);
206                     return value instanceof String ? StringUtils.normalizeSpace((String) value) : value;
207                 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
208                     throw new RuntimeException(e);
209                 }
210             }
211             return null;
212         }
213         Class<?> getChildType() {
214             return desc.getChildType();
215         }
216     }
217 
218     /**
219      * The definition of an attribute.
220      */
221     public final class Attribute {
222         /**
223          * The description for the attribute.
224          */
225         private final Description description;
226 
227         /**
228          * Constructor
229          * @param description the description for this attribute.
230          */
231         Attribute(final Description description) {
232             this.description = description;
233         }
234 
235         /**
236          * Gets the attribute name.
237          * @return the attribut name.
238          */
239         public String getName() {
240             return description.getCommonName();
241         }
242 
243         /**
244          * Gets the required flag.
245          * @return the word "required" or "optional" depending on the state of the required flag.
246          */
247         public String getRequired() {
248             return description.isRequired() ? "required" : "optional";
249         }
250 
251         /**
252          * Get the type of the attribute.
253          * @return the type of the enclosed matcher.
254          */
255         public String getType() {
256             return description.getChildType().getSimpleName();
257         }
258 
259         /**
260          * Gets the description of the attribute or an empty string.
261          * @return the description of the attribute or an empty string.
262          */
263         public String getDescription() {
264             return StringUtils.defaultIfEmpty(description.getDescription(), "");
265         }
266 
267         /**
268          * Gets the value of the enclosed matcher.
269          * <ul>
270          * <li>If the Matcher does not have an {@link IHeaderMatcher} defined this method returns {@code null}.</li>
271          * <li>If the value of the attribute (self) is {@code null}, this method returns {@code null}.</li>
272          * <li>If the value is a string, it is normalized before being returned.</li>
273          * <li>If the attribute is an "id" and the value is a UUID, {@code null} is returned.</li>
274          * <li>otherwise, the string value is returned.</li>
275          * </ul>
276          * @return the value of the enclosed matcher in the provided {@link IHeaderMatcher}.
277          * @see StringUtils#normalizeSpace(String)
278          */
279         public String getValue() {
280             if (self != null) {
281                 try {
282                     Object value = description.getter(self.getClass()).invoke(self);
283                     if (value != null) {
284                         String result = value.toString();
285                         if (description.getCommonName().equals("id")) {
286                             try {
287                                 UUID.fromString(result);
288                                 return null;
289                             } catch (IllegalArgumentException e) {
290                                 // do nothing.
291                             }
292                         }
293                         return result;
294                     }
295                 } catch (IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
296                     throw new RuntimeException(e);
297                 }
298             }
299             return null;
300         }
301     }
302 }