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