1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.config.exclusion;
20
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Collection;
24 import java.util.HashSet;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Objects;
28 import java.util.Set;
29 import java.util.TreeSet;
30 import java.util.function.Predicate;
31 import java.util.stream.Collectors;
32
33 import org.apache.commons.lang3.NotImplementedException;
34 import org.apache.rat.configuration.XMLConfigurationReader;
35 import org.apache.rat.document.DocumentName;
36 import org.apache.rat.document.DocumentNameMatcher;
37 import org.apache.rat.report.xml.writer.XmlWriter;
38 import org.apache.rat.utils.DefaultLog;
39 import org.apache.rat.utils.ExtendedIterator;
40 import org.w3c.dom.Node;
41 import org.w3c.dom.NodeList;
42
43 import static java.lang.String.format;
44
45
46
47
48
49 public class ExclusionProcessor {
50
51 private final Set<String> excludedPatterns;
52
53 private final List<DocumentNameMatcher> excludedPaths;
54
55 private final Set<String> includedPatterns;
56
57 private final List<DocumentNameMatcher> includedPaths;
58
59
60
61
62 private final Set<StandardCollection> fileProcessors;
63
64 private final Set<StandardCollection> includedCollections;
65
66 private final Set<StandardCollection> excludedCollections;
67
68 private DocumentNameMatcher lastMatcher;
69
70 private DocumentName lastMatcherBaseDir;
71
72
73
74
75 public ExclusionProcessor() {
76 excludedPatterns = new HashSet<>();
77 excludedPaths = new ArrayList<>();
78 includedPatterns = new HashSet<>();
79 includedPaths = new ArrayList<>();
80 fileProcessors = new HashSet<>();
81 includedCollections = new HashSet<>();
82 excludedCollections = new HashSet<>();
83 }
84
85 public SerDes serDes() {
86 return new SerDes();
87 }
88
89
90
91 Set<String> getExcludedPatterns() {
92 return new HashSet<>(excludedPatterns);
93 }
94
95 Collection<DocumentNameMatcher> getExcludedPaths() {
96 return new ArrayList<>(excludedPaths);
97 }
98
99 Set<String> getIncludedPatterns() {
100 return new HashSet<>(includedPatterns);
101 }
102
103 Collection<DocumentNameMatcher> getIncludedPaths() {
104 return new ArrayList<>(includedPaths);
105 }
106
107 Collection<StandardCollection> getFileProcessors() {
108 return new HashSet<>(fileProcessors);
109 }
110
111 Set<StandardCollection> getIncludedCollections() {
112 return new HashSet<>(includedCollections);
113 }
114
115 Set<StandardCollection> getExcludedCollections() {
116 return new HashSet<>(excludedCollections);
117 }
118
119 DocumentNameMatcher getLastMatcher() {
120 return lastMatcher;
121 }
122
123 DocumentName getLastMatcherBaseDir() {
124 return lastMatcherBaseDir;
125 }
126
127
128
129
130
131 private void resetLastMatcher() {
132 lastMatcher = null;
133 lastMatcherBaseDir = null;
134 }
135
136
137
138
139
140
141 public ExclusionProcessor addIncludedPatterns(final Iterable<String> patterns) {
142 if (patterns != null) {
143 DefaultLog.getInstance().debug(format("Including patterns: %s", String.join(", ", patterns)));
144 patterns.forEach(includedPatterns::add);
145 resetLastMatcher();
146 }
147 return this;
148 }
149
150
151
152
153
154
155 public ExclusionProcessor addIncludedMatcher(final DocumentNameMatcher matcher) {
156 if (matcher != null) {
157 includedPaths.add(matcher);
158 resetLastMatcher();
159 }
160 return this;
161 }
162
163
164
165
166
167
168 public ExclusionProcessor addFileProcessor(final StandardCollection collection) {
169 if (collection != null) {
170 DefaultLog.getInstance().debug(format("Processing exclude file from %s.", collection));
171 fileProcessors.add(collection);
172 resetLastMatcher();
173 }
174 return this;
175 }
176
177
178
179
180
181
182 public ExclusionProcessor addIncludedCollection(final StandardCollection collection) {
183 if (collection != null) {
184 DefaultLog.getInstance().debug(format("Including %s collection.", collection));
185 includedCollections.add(collection);
186 resetLastMatcher();
187 }
188 return this;
189 }
190
191
192
193
194
195
196 public ExclusionProcessor addExcludedPatterns(final Iterable<String> patterns) {
197 if (patterns != null) {
198 DefaultLog.getInstance().debug(format("Excluding patterns: %s", String.join(", ", patterns)));
199 patterns.forEach(excludedPatterns::add);
200 resetLastMatcher();
201 }
202 return this;
203 }
204
205
206
207
208
209
210 public ExclusionProcessor addExcludedMatcher(final DocumentNameMatcher matcher) {
211 if (matcher != null) {
212 excludedPaths.add(matcher);
213 resetLastMatcher();
214 }
215 return this;
216 }
217
218
219
220
221
222 public void reportExclusions(final Appendable appendable) throws IOException {
223 appendable.append(format("Excluding patterns: %s%n", String.join(", ", excludedPatterns)));
224 appendable.append(format("Including patterns: %s%n", String.join(", ", includedPatterns)));
225 for (StandardCollection sc : excludedCollections) {
226 appendable.append(format("Excluding %s collection.%n", sc.name()));
227 }
228 for (StandardCollection sc : includedCollections) {
229 appendable.append(format("Including %s collection.%n", sc.name()));
230 }
231 for (StandardCollection sc : fileProcessors) {
232 appendable.append(format("Processing exclude file from %s.%n", sc.name()));
233 }
234 for (DocumentNameMatcher nameMatcher : excludedPaths) {
235 appendable.append(format("Excluding %s.%n", nameMatcher.toString()));
236 }
237 for (DocumentNameMatcher nameMatcher : includedPaths) {
238 appendable.append(format("Including %s.%n", nameMatcher.toString()));
239 }
240 }
241
242
243
244
245
246
247 public ExclusionProcessor addExcludedCollection(final StandardCollection collection) {
248 if (collection != null) {
249 DefaultLog.getInstance().debug(format("Excluding %s collection.", collection));
250 excludedCollections.add(collection);
251 resetLastMatcher();
252 }
253 return this;
254 }
255
256
257
258
259
260
261
262 public DocumentNameMatcher getNameMatcher(final DocumentName basedir) {
263
264
265
266 if (lastMatcher == null || !basedir.equals(lastMatcherBaseDir)) {
267 lastMatcherBaseDir = basedir;
268
269
270 final List<MatcherSet> matchers = extractFileProcessors(basedir);
271 final MatcherSet.Builder fromCommandLine = new MatcherSet.Builder();
272 DocumentName.Builder nameBuilder = DocumentName.builder(basedir).setBaseName(basedir);
273 extractPatterns(nameBuilder, fromCommandLine);
274 extractCollectionPatterns(nameBuilder, fromCommandLine);
275 extractCollectionMatchers(fromCommandLine);
276 extractPaths(fromCommandLine);
277 matchers.add(fromCommandLine.build());
278
279 lastMatcher = MatcherSet.merge(matchers).createMatcher();
280 DefaultLog.getInstance().debug(format("Created matcher set for %s%n%s", basedir.getName(),
281 lastMatcher));
282 }
283 return lastMatcher;
284 }
285
286
287
288
289
290
291 private List<MatcherSet> extractFileProcessors(final DocumentName basedir) {
292 final List<MatcherSet> fileProcessorList = new ArrayList<>();
293 for (StandardCollection sc : fileProcessors) {
294 ExtendedIterator<List<MatcherSet>> iter = sc.fileProcessorBuilder().map(builder -> builder.build(basedir));
295 if (iter.hasNext()) {
296 iter.forEachRemaining(fileProcessorList::addAll);
297 } else {
298 DefaultLog.getInstance().debug(String.format("%s does not have a fileProcessor.", sc));
299 }
300 }
301 return fileProcessorList;
302 }
303
304
305
306
307
308
309
310
311 private String preparePattern(final DocumentName documentName, final String pattern) {
312 return ExclusionUtils.qualifyPattern(documentName,
313 ExclusionUtils.convertSeparator(pattern, "/", documentName.getDirectorySeparator()));
314 }
315
316
317
318
319
320
321 private void extractPatterns(final DocumentName.Builder nameBuilder, final MatcherSet.Builder matcherBuilder) {
322 DocumentName name = nameBuilder.setName("Patterns").build();
323 if (!excludedPatterns.isEmpty()) {
324 matcherBuilder.addExcluded(name, excludedPatterns.stream()
325 .map(s -> preparePattern(name, s))
326 .collect(Collectors.toSet()));
327 }
328 if (!includedPatterns.isEmpty()) {
329 matcherBuilder.addIncluded(name, includedPatterns.stream()
330 .map(s -> preparePattern(name, s)).collect(Collectors.toSet()));
331 }
332 }
333
334
335
336
337
338
339 private void extractCollectionPatterns(final DocumentName.Builder nameBuilder, final MatcherSet.Builder matcherBuilder) {
340 final Set<String> incl = new TreeSet<>();
341 final Set<String> excl = new TreeSet<>();
342 for (StandardCollection sc : includedCollections) {
343 Set<String> patterns = sc.patterns();
344 if (patterns.isEmpty()) {
345 DefaultLog.getInstance().debug(String.format("%s does not have a defined collection for inclusion.", sc));
346 } else {
347 MatcherSet.Builder.segregateList(incl, excl, sc.patterns());
348 }
349 }
350 for (StandardCollection sc : excludedCollections) {
351 Set<String> patterns = sc.patterns();
352 if (patterns.isEmpty()) {
353 DefaultLog.getInstance().debug(String.format("%s does not have a defined collection for exclusion.", sc));
354 } else {
355 MatcherSet.Builder.segregateList(excl, incl, sc.patterns());
356 }
357 }
358 DocumentName name = nameBuilder.setName("Collections").build();
359 matcherBuilder
360 .addExcluded(name, excl.stream().map(s -> preparePattern(name.getBaseDocumentName(), s)).collect(Collectors.toSet()))
361 .addIncluded(name, incl.stream().map(s -> preparePattern(name.getBaseDocumentName(), s)).collect(Collectors.toSet()));
362 }
363
364
365
366
367
368 private void extractCollectionMatchers(final MatcherSet.Builder matcherBuilder) {
369 ExtendedIterator.create(includedCollections.iterator())
370 .map(StandardCollection::staticDocumentNameMatcher)
371 .filter(Objects::nonNull)
372 .forEachRemaining(matcherBuilder::addIncluded);
373
374 ExtendedIterator.create(excludedCollections.iterator())
375 .map(StandardCollection::staticDocumentNameMatcher)
376 .filter(Objects::nonNull)
377 .forEachRemaining(matcherBuilder::addExcluded);
378 }
379
380
381
382
383
384 private void extractPaths(final MatcherSet.Builder matcherBuilder) {
385 if (!includedPaths.isEmpty()) {
386 for (DocumentNameMatcher matcher : includedPaths) {
387 DefaultLog.getInstance().debug(format("Including path matcher %s", matcher));
388 matcherBuilder.addIncluded(matcher);
389 }
390 }
391 if (!excludedPaths.isEmpty()) {
392 for (DocumentNameMatcher matcher : excludedPaths) {
393 DefaultLog.getInstance().debug(format("Excluding path matcher %s", matcher));
394 matcherBuilder.addExcluded(matcher);
395 }
396 }
397 }
398
399
400
401
402 public class SerDes {
403
404 private static final String PATTERN = "pattern";
405
406 private static final String NAME = "name";
407
408
409
410
411
412
413 public void serialize(final XmlWriter writer) throws IOException {
414 writer.startElement("ExclusionProcessor");
415
416 for (String pattern : excludedPatterns) {
417 writer.startElement("excludedPattern").attribute(PATTERN, pattern).closeElement();
418 }
419 for (StandardCollection obj : excludedCollections) {
420 writer.startElement("excludedCollection").attribute(NAME, obj.name()).closeElement();
421 }
422 for (DocumentNameMatcher obj : excludedPaths) {
423 writer.startElement("excludedPath").attribute(NAME, obj.toString()).closeElement();
424 }
425
426 for (String pattern : includedPatterns) {
427 writer.startElement("includedPattern").attribute(PATTERN, pattern).closeElement();
428 }
429 for (StandardCollection obj : includedCollections) {
430 writer.startElement("includedCollection").attribute(NAME, obj.name()).closeElement();
431 }
432 for (DocumentNameMatcher obj : includedPaths) {
433 writer.startElement("includedPath").attribute(NAME, obj.toString()).closeElement();
434 }
435
436 for (StandardCollection obj : fileProcessors) {
437 writer.startElement("fileProcessor").attribute(NAME, obj.name()).closeElement();
438 }
439 writer.closeElement();
440 }
441
442
443
444
445
446 public void deserialize(final Node xmlNode) {
447 final NodeList children = xmlNode.getChildNodes();
448 for (int i = 0; i < children.getLength(); i++) {
449 Node child = children.item(i);
450 Map<String, String> attributes = XMLConfigurationReader.attributes(child);
451 switch (child.getNodeName()) {
452 case "excludedPattern" ->
453 excludedPatterns.add(attributes.get(PATTERN));
454
455 case "excludedCollection" ->
456 excludedCollections.add(StandardCollection.valueOf(attributes.get(NAME)));
457
458 case "excludedPath" ->
459 excludedPaths.add(new DocumentNameMatcher(attributes.get(NAME),
460 (Predicate<DocumentName>) x -> {
461 throw new NotImplementedException("Deserialized ExclusionProcessor can not evaluate excluded paths");
462 }));
463
464 case "includedPattern" ->
465 includedPatterns.add(attributes.get(PATTERN));
466
467 case "includedCollection" ->
468 includedCollections.add(StandardCollection.valueOf(attributes.get(NAME)));
469
470 case "includedPath" ->
471 includedPaths.add(new DocumentNameMatcher(attributes.get(NAME),
472 (Predicate<DocumentName>) x -> {
473 throw new NotImplementedException("Deserialized ExclusionProcessor can not evaluate included paths");
474 }));
475
476 case "fileProcessor" ->
477 fileProcessors.add(StandardCollection.valueOf(attributes.get(NAME)));
478
479 default ->
480 DefaultLog.getInstance().error(String.format("Unknown child node '%s'", child.getNodeName()));
481 }
482 }
483 }
484 }
485 }