1 /**
2 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3 */
4 package net.sourceforge.pmd.util.filter;
5
6 import java.util.regex.Matcher;
7 import java.util.regex.Pattern;
8 import java.util.regex.PatternSyntaxException;
9
10 /**
11 * A filter which uses a regular expression to match Strings. Invalid regular
12 * expressions will match nothing.
13 * <p>
14 * Because regular expression matching is slow, and a common usage is to match
15 * some sort of relative file path, the regular expression is checked to see if
16 * it can be evaluated using much faster calls to
17 * {@link String#endsWith(String)}.
18 */
19 public class RegexStringFilter implements Filter<String> {
20 /**
21 * Matches regular expressions begin with an optional {@code ^}, then
22 * {@code .*}, then a literal path, with an optional file extension, and
23 * finally an optional {@code $} at the end. The {@code .} in the extension
24 * may or may not be preceded by a {@code \} escape. The literal path
25 * portion is determine by the absence of any of the following characters:
26 * <code>\ [ ( . * ? + | { $</code>
27 *
28 * There are two capturing groups in the expression. The first is for the
29 * literal path. The second is for the file extension, without the escaping.
30 * The concatenation of these two captures creates the {@link String} which
31 * can be used with {@link String#endsWith(String)}.
32 *
33 * For ease of reference, the non-Java escaped form of this pattern is:
34 * <code>\^?\.\*([^\\\[\(\.\*\?\+\|\{\$]+)(?:\\?(\.\w+))?\$?</code>
35 */
36 private static final Pattern ENDS_WITH = Pattern
37 .compile("\\^?\\.\\*([^\\\\\\[\\(\\.\\*\\?\\+\\|\\{\\$]+)(?:\\\\?(\\.\\w+))?\\$?");
38
39 protected String regex;
40 protected Pattern pattern;
41 protected String endsWith;
42
43 public RegexStringFilter(String regex) {
44 this.regex = regex;
45 optimize();
46 }
47
48 public String getRegex() {
49 return this.regex;
50 }
51
52 public String getEndsWith() {
53 return this.endsWith;
54 }
55
56 protected void optimize() {
57 final Matcher matcher = ENDS_WITH.matcher(this.regex);
58 if (matcher.matches()) {
59 final String literalPath = matcher.group(1);
60 final String fileExtension = matcher.group(2);
61 if (fileExtension != null) {
62 this.endsWith = literalPath + fileExtension;
63 } else {
64 this.endsWith = literalPath;
65 }
66 } else {
67 try {
68 this.pattern = Pattern.compile(this.regex);
69 } catch (PatternSyntaxException e) {
70 // If the regular expression is invalid, then pattern will be null.
71 }
72 }
73 }
74
75 public boolean filter(String obj) {
76 if (this.endsWith != null) {
77 return obj.endsWith(this.endsWith);
78 } else if (this.pattern != null) {
79 return this.pattern.matcher(obj).matches();
80 } else {
81 // The regular expression must have been bad, so it will match nothing.
82 return false;
83 }
84 }
85
86 @Override
87 public String toString() {
88 return "matches " + this.regex;
89 }
90 }