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 }