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 }