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