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