1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.rat.document;
20
21 import java.io.File;
22 import java.io.FileFilter;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Optional;
29 import java.util.Set;
30 import java.util.function.Predicate;
31
32 import org.apache.rat.ConfigurationException;
33 import org.apache.rat.config.exclusion.plexus.MatchPattern;
34 import org.apache.rat.config.exclusion.plexus.MatchPatterns;
35
36 import static java.lang.String.format;
37
38
39
40
41 public final class DocumentNameMatcher {
42
43
44 private final Predicate<DocumentName> predicate;
45
46 private final String name;
47
48 private final boolean isCollection;
49
50
51
52
53 public static final DocumentNameMatcher MATCHES_ALL = new DocumentNameMatcher("TRUE", (Predicate<DocumentName>) x -> true);
54
55
56
57
58 public static final DocumentNameMatcher MATCHES_NONE = new DocumentNameMatcher("FALSE", (Predicate<DocumentName>) x -> false);
59
60
61
62
63
64
65 public DocumentNameMatcher(final String name, final Predicate<DocumentName> predicate) {
66 this.name = name;
67 this.predicate = predicate;
68 this.isCollection = predicate instanceof CollectionPredicate;
69 }
70
71
72
73
74
75
76 public DocumentNameMatcher(final String name, final DocumentNameMatcher delegate) {
77 this(name, delegate::matches);
78 }
79
80
81
82
83
84
85
86 public DocumentNameMatcher(final String name, final MatchPatterns patterns, final DocumentName basedir) {
87 this(name, new MatchPatternsPredicate(basedir, patterns));
88 }
89
90
91
92
93
94
95
96 private static char[][] tokenize(final String name, final String dirSeparator) {
97 String[] tokenizedName = MatchPattern.tokenizePathToString(name, dirSeparator);
98 char[][] tokenizedNameChar = new char[tokenizedName.length][];
99 for (int i = 0; i < tokenizedName.length; i++) {
100 tokenizedNameChar[i] = tokenizedName[i].toCharArray();
101 }
102 return tokenizedNameChar;
103 }
104
105
106
107
108
109
110 public DocumentNameMatcher(final String name, final MatchPatterns matchers) {
111 this(name, new CollectionPredicate() {
112 @Override
113 public Iterable<DocumentNameMatcher> getMatchers() {
114 final List<DocumentNameMatcher> result = new ArrayList<>();
115 matchers.patterns().forEach(p -> result.add(new DocumentNameMatcher(p.source(),
116 (Predicate<DocumentName>) x -> MatchPatterns.from("/", p.source()).matches(x.getName(), x.isCaseSensitive()))));
117 return result;
118 }
119
120 @Override
121 public boolean test(final DocumentName documentName) {
122 return matchers.matches(documentName.getName(), documentName.isCaseSensitive());
123 }
124 });
125 }
126
127
128
129
130
131
132 public DocumentNameMatcher(final String name, final FileFilter fileFilter) {
133 this(name, new FileFilterPredicate(fileFilter));
134 }
135
136
137
138
139
140 public DocumentNameMatcher(final FileFilter fileFilter) {
141 this(fileFilter.toString(), fileFilter);
142 }
143
144 public boolean isCollection() {
145 return isCollection;
146 }
147
148
149
150
151
152 public Predicate<DocumentName> getPredicate() {
153 return predicate;
154 }
155
156 @Override
157 public String toString() {
158 return name;
159 }
160
161
162
163
164
165
166 public List<DecomposeData> decompose(final DocumentName candidate) {
167 final List<DecomposeData> result = new ArrayList<>();
168 decompose(0, this, candidate, result);
169 return result;
170 }
171
172 private void decompose(final int level, final DocumentNameMatcher matcher, final DocumentName candidate, final List<DecomposeData> result) {
173 final Predicate<DocumentName> pred = matcher.getPredicate();
174 result.add(new DecomposeData(level, matcher, candidate, pred.test(candidate)));
175 }
176
177
178
179
180
181
182 public boolean matches(final DocumentName documentName) {
183 return predicate.test(documentName);
184 }
185
186
187
188
189
190
191 public static DocumentNameMatcher not(final DocumentNameMatcher nameMatcher) {
192 if (nameMatcher == MATCHES_ALL) {
193 return MATCHES_NONE;
194 }
195 if (nameMatcher == MATCHES_NONE) {
196 return MATCHES_ALL;
197 }
198
199 return new DocumentNameMatcher(format("not(%s)", nameMatcher), new NotPredicate(nameMatcher));
200 }
201
202
203
204
205
206
207 private static String join(final Collection<DocumentNameMatcher> matchers) {
208 List<String> children = new ArrayList<>();
209 matchers.forEach(s -> children.add(s.toString()));
210 return String.join(", ", children);
211 }
212
213 private static Optional<DocumentNameMatcher> standardCollectionCheck(final Collection<DocumentNameMatcher> matchers, final DocumentNameMatcher override) {
214 if (matchers.isEmpty()) {
215 throw new ConfigurationException("Empty matcher collection");
216 }
217 if (matchers.size() == 1) {
218 return Optional.of(matchers.iterator().next());
219 }
220 if (matchers.contains(override)) {
221 return Optional.of(override);
222 }
223 return Optional.empty();
224 }
225
226
227
228
229
230
231 public static DocumentNameMatcher or(final Collection<DocumentNameMatcher> matchers) {
232 Optional<DocumentNameMatcher> opt = standardCollectionCheck(matchers, MATCHES_ALL);
233 if (opt.isPresent()) {
234 return opt.get();
235 }
236
237
238 Set<DocumentNameMatcher> workingSet = new LinkedHashSet<>();
239 for (DocumentNameMatcher matcher : matchers) {
240
241 if (matcher.predicate instanceof Or) {
242 ((Or) matcher.predicate).getMatchers().forEach(workingSet::add);
243 } else {
244 workingSet.add(matcher);
245 }
246 }
247 return standardCollectionCheck(matchers, MATCHES_ALL)
248 .orElseGet(() -> new DocumentNameMatcher(format("or(%s)", join(workingSet)), new Or(workingSet)));
249 }
250
251
252
253
254
255
256 public static DocumentNameMatcher or(final DocumentNameMatcher... matchers) {
257 return or(Arrays.asList(matchers));
258 }
259
260
261
262
263
264
265 public static DocumentNameMatcher and(final Collection<DocumentNameMatcher> matchers) {
266 Optional<DocumentNameMatcher> opt = standardCollectionCheck(matchers, MATCHES_NONE);
267 if (opt.isPresent()) {
268 return opt.get();
269 }
270
271
272 Set<DocumentNameMatcher> workingSet = new LinkedHashSet<>();
273 for (DocumentNameMatcher matcher : matchers) {
274
275 if (matcher.predicate instanceof And) {
276 ((And) matcher.predicate).getMatchers().forEach(workingSet::add);
277 } else {
278 workingSet.add(matcher);
279 }
280 }
281 opt = standardCollectionCheck(matchers, MATCHES_NONE);
282 return opt.orElseGet(() -> new DocumentNameMatcher(format("and(%s)", join(workingSet)), new And(workingSet)));
283 }
284
285
286
287
288
289
290
291 public static DocumentNameMatcher matcherSet(final DocumentNameMatcher includes,
292 final DocumentNameMatcher excludes) {
293 if (excludes == MATCHES_NONE) {
294 return MATCHES_ALL;
295 } else {
296 if (includes == MATCHES_NONE) {
297 return not(excludes);
298 }
299 }
300 if (includes == MATCHES_ALL) {
301 return MATCHES_ALL;
302 }
303 List<DocumentNameMatcher> workingSet = Arrays.asList(includes, excludes);
304 return new DocumentNameMatcher(format("matcherSet(%s)", join(workingSet)),
305 new DefaultCollectionPredicate(workingSet) {
306 @Override
307 public boolean test(final DocumentName documentName) {
308 if (includes.matches(documentName)) {
309 return true;
310 }
311 return !excludes.matches(documentName);
312 }
313 });
314 }
315
316
317
318
319
320
321 public static DocumentNameMatcher and(final DocumentNameMatcher... matchers) {
322 return and(Arrays.asList(matchers));
323 }
324
325
326
327
328 public static final class MatchPatternsPredicate implements Predicate<DocumentName> {
329
330 private final DocumentName basedir;
331
332 private final MatchPatterns patterns;
333
334 private MatchPatternsPredicate(final DocumentName basedir, final MatchPatterns patterns) {
335 this.basedir = basedir;
336 this.patterns = patterns;
337 }
338
339 @Override
340 public boolean test(final DocumentName documentName) {
341 return patterns.matches(documentName.getName(),
342 tokenize(documentName.getName(), basedir.getDirectorySeparator()),
343 basedir.isCaseSensitive());
344 }
345
346 @Override
347 public String toString() {
348 return patterns.toString();
349 }
350 }
351
352
353
354
355 public static final class NotPredicate implements Predicate<DocumentName> {
356
357 private final DocumentNameMatcher nameMatcher;
358
359 private NotPredicate(final DocumentNameMatcher nameMatcher) {
360 this.nameMatcher = nameMatcher;
361 }
362
363 @Override
364 public boolean test(final DocumentName documentName) {
365 return !nameMatcher.matches(documentName);
366 }
367
368 @Override
369 public String toString() {
370 return nameMatcher.predicate.toString();
371 }
372 }
373
374
375
376
377 public static final class FileFilterPredicate implements Predicate<DocumentName> {
378
379 private final FileFilter fileFilter;
380
381 private FileFilterPredicate(final FileFilter fileFilter) {
382 this.fileFilter = fileFilter;
383 }
384
385 @Override
386 public boolean test(final DocumentName documentName) {
387 return fileFilter.accept(new File(documentName.getName()));
388 }
389
390 @Override
391 public String toString() {
392 return fileFilter.toString();
393 }
394 }
395
396
397
398
399 interface CollectionPredicate extends Predicate<DocumentName> {
400 Iterable<DocumentNameMatcher> getMatchers();
401 }
402
403
404
405 abstract static class DefaultCollectionPredicate implements CollectionPredicate {
406
407 private final Iterable<DocumentNameMatcher> matchers;
408
409
410
411
412
413 protected DefaultCollectionPredicate(final Iterable<DocumentNameMatcher> matchers) {
414 this.matchers = matchers;
415 }
416
417
418
419
420
421 public Iterable<DocumentNameMatcher> getMatchers() {
422 return matchers;
423 }
424
425 @Override
426 public String toString() {
427 StringBuilder builder = new StringBuilder(this.getClass().getName()).append(": ").append(System.lineSeparator());
428 for (DocumentNameMatcher matcher : matchers) {
429 builder.append(matcher.predicate.toString()).append(System.lineSeparator());
430 }
431 return builder.toString();
432 }
433 }
434
435
436
437
438
439 static class And extends DefaultCollectionPredicate {
440 And(final Iterable<DocumentNameMatcher> matchers) {
441 super(matchers);
442 }
443
444 @Override
445 public boolean test(final DocumentName documentName) {
446 for (DocumentNameMatcher matcher : getMatchers()) {
447 if (!matcher.matches(documentName)) {
448 return false;
449 }
450 }
451 return true;
452 }
453 }
454
455
456
457
458
459 static class Or extends DefaultCollectionPredicate {
460 Or(final Iterable<DocumentNameMatcher> matchers) {
461 super(matchers);
462 }
463
464 @Override
465 public boolean test(final DocumentName documentName) {
466 for (DocumentNameMatcher matcher : getMatchers()) {
467 if (matcher.matches(documentName)) {
468 return true;
469 }
470 }
471 return false;
472 }
473 }
474
475
476
477
478 public static final class DecomposeData {
479
480 private final int level;
481
482 private final DocumentNameMatcher matcher;
483
484 private final boolean result;
485
486 private final DocumentName candidate;
487
488 private DecomposeData(final int level, final DocumentNameMatcher matcher, final DocumentName candidate, final boolean result) {
489 this.level = level;
490 this.matcher = matcher;
491 this.result = result;
492 this.candidate = candidate;
493 }
494
495 @Override
496 public String toString() {
497 final String fill = createFill(level);
498 return format("%s%s: >>%s<< %s%n%s",
499 fill, matcher.toString(), result,
500 level == 0 ? candidate.getName() : "",
501 matcher.predicate instanceof CollectionPredicate ?
502 decompose(level + 1, (CollectionPredicate) matcher.predicate, candidate) :
503 String.format("%s%s >>%s<<", createFill(level + 1), matcher.predicate.toString(), matcher.predicate.test(candidate)));
504 }
505
506 private String createFill(final int level) {
507 final char[] chars = new char[level * 2];
508 Arrays.fill(chars, ' ');
509 return new String(chars);
510 }
511
512 private String decompose(final int level, final CollectionPredicate predicate, final DocumentName candidate) {
513 List<DecomposeData> result = new ArrayList<>();
514
515 for (DocumentNameMatcher nameMatcher : predicate.getMatchers()) {
516 nameMatcher.decompose(level, nameMatcher, candidate, result);
517 }
518 StringBuilder sb = new StringBuilder();
519 result.forEach(x -> sb.append(x).append(System.lineSeparator()));
520 return sb.toString();
521 }
522 }
523 }