-
Notifications
You must be signed in to change notification settings - Fork 0
/
AntPathMatcher.java
160 lines (135 loc) · 5.31 KB
/
AntPathMatcher.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package io.github.azagniotov.matcher;
/**
* Path matcher implementation for Ant-style path patterns. This implementation matches URLs using the following rules:
* <p/>
* '?' - matches one character
* '*' - matches zero or more characters
* '**' - matches zero or more directories in a path
* <p/>
* The instances of this class can be configured via its {@link Builder} to:
* (1) Use a custom path separator. The default is '/' character
* (2) Ignore character case during comparison. The default is {@code false}
* (3) Match start. Determines whether the pattern at least matches as far as the given base path goes,
* assuming that a full path may then match as well. The default is {@code false}
* <p/>
* (4) Specify whether to trim tokenized paths. The default is {@code false}
* The custom path separator & ignoring character case options were inspired by Spring's AntPathMatcher
*/
@SuppressWarnings("WeakerAccess")
public class AntPathMatcher {
private static final char ASTERISK = '*';
private static final char QUESTION = '?';
private static final char BLANK = ' ';
private static final int ASCII_CASE_DIFFERENCE_VALUE = 32;
private final char pathSeparator;
private final boolean ignoreCase;
private final boolean matchStart;
private final boolean trimTokens;
private AntPathMatcher(final char pathSeparator, boolean ignoreCase, boolean matchStart, boolean trimTokens) {
this.pathSeparator = pathSeparator;
this.ignoreCase = ignoreCase;
this.matchStart = matchStart;
this.trimTokens = trimTokens;
}
public boolean isMatch(final String pattern, final String path) {
if (pattern.isEmpty()) {
return path.isEmpty();
} else if (path.isEmpty() && pattern.charAt(0) == pathSeparator) {
if (matchStart) {
return true;
} else if (pattern.length() == 2 && pattern.charAt(1) == ASTERISK) {
return false;
}
return isMatch(pattern.substring(1), path);
}
final char patternStart = pattern.charAt(0);
if (patternStart == ASTERISK) {
if (pattern.length() == 1) {
return path.isEmpty() || path.charAt(0) != pathSeparator && isMatch(pattern, path.substring(1));
} else if (doubleAsteriskMatch(pattern, path)) {
return true;
}
int start = 0;
while (start < path.length()) {
if (isMatch(pattern.substring(1), path.substring(start))) {
return true;
}
start++;
}
return isMatch(pattern.substring(1), path.substring(start));
}
int pointer = skipBlanks(path);
return !path.isEmpty() && (equal(path.charAt(pointer), patternStart) || patternStart == QUESTION)
&& isMatch(pattern.substring(1), path.substring(pointer + 1));
}
private boolean doubleAsteriskMatch(final String pattern, final String path) {
if (pattern.charAt(1) != ASTERISK) {
return false;
} else if (pattern.length() > 2) {
return isMatch(pattern.substring(3), path);
}
return false;
}
/*
private boolean doubleAsteriskMatch(final String pattern, final String path) {
if (pattern.charAt(1) != ASTERISK) {
return false;
} else if (pattern.length() > 2 && isMatch(pattern.substring(3), path)) {
return true;
}
int pointer = 0;
for (int idx = 0; idx < path.length(); idx++) {
if (path.charAt(idx) == pathSeparator) {
pointer = idx;
break;
}
}
return isMatch(pattern.substring(2), path.substring(pointer));
}
*/
private int skipBlanks(final String path) {
int pointer = 0;
if (trimTokens) {
while (!path.isEmpty() && pointer < path.length() && path.charAt(pointer) == BLANK) {
pointer++;
}
}
return pointer;
}
private boolean equal(final char pathChar, final char patternChar) {
if (ignoreCase) {
return pathChar == patternChar ||
((pathChar > patternChar) ?
pathChar == patternChar + ASCII_CASE_DIFFERENCE_VALUE :
pathChar == patternChar - ASCII_CASE_DIFFERENCE_VALUE);
}
return pathChar == patternChar;
}
public static final class Builder {
private char pathSeparator = '/';
private boolean ignoreCase = false;
private boolean matchStart = false;
private boolean trimTokens = false;
public Builder() {
}
public Builder withPathSeparator(final char pathSeparator) {
this.pathSeparator = pathSeparator;
return this;
}
public Builder withIgnoreCase() {
this.ignoreCase = true;
return this;
}
public Builder withMatchStart() {
this.matchStart = true;
return this;
}
public Builder withTrimTokens() {
this.trimTokens = true;
return this;
}
public AntPathMatcher build() {
return new AntPathMatcher(pathSeparator, ignoreCase, matchStart, trimTokens);
}
}
}