1 package org.apache.archiva.configuration.util;
2
3 /*
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21
22 import java.io.File;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25
26 /**
27 *
28 * This provides a path matcher like in the SelectorUtils of the ant project.
29 *
30 * Using code from apache ant org.apache.tools.ant.types.selectors.SelectorUtils to remove the ant dependency for code
31 * compilation.
32 * See https://github.com/apache/ant/blob/master/src/main/org/apache/tools/ant/types/selectors/SelectorUtils.java
33 *
34 * @author Martin Stockhammer <martin_s@apache.org>
35 *
36 *
37 */
38 public class PathMatcher
39 {
40
41 private static final String DEEP_TREE_MATCH = "**";
42
43
44 /**
45 * Tests whether or not a string matches against a pattern.
46 * The pattern may contain two special characters:<br>
47 * '*' means zero or more characters<br>
48 * '?' means one and only one character
49 *
50 * @param pattern The pattern to match against.
51 * Must not be <code>null</code>.
52 * @param str The string which must be matched against the pattern.
53 * Must not be <code>null</code>.
54 *
55 * @return <code>true</code> if the string matches against the pattern,
56 * or <code>false</code> otherwise.
57 */
58 public static boolean match(String pattern, String str) {
59 return match(pattern, str, true);
60 }
61
62
63 /**
64 * Tests whether or not a string matches against a pattern.
65 * The pattern may contain two special characters:<br>
66 * '*' means zero or more characters<br>
67 * '?' means one and only one character
68 *
69 * @param pattern The pattern to match against.
70 * Must not be <code>null</code>.
71 * @param str The string which must be matched against the pattern.
72 * Must not be <code>null</code>.
73 * @param caseSensitive Whether or not matching should be performed
74 * case sensitively.
75 *
76 *
77 * @return <code>true</code> if the string matches against the pattern,
78 * or <code>false</code> otherwise.
79 */
80 public static boolean match(String pattern, String str,
81 boolean caseSensitive) {
82 char[] patArr = pattern.toCharArray();
83 char[] strArr = str.toCharArray();
84 int patIdxStart = 0;
85 int patIdxEnd = patArr.length - 1;
86 int strIdxStart = 0;
87 int strIdxEnd = strArr.length - 1;
88
89 boolean containsStar = false;
90 for (char ch : patArr) {
91 if (ch == '*') {
92 containsStar = true;
93 break;
94 }
95 }
96
97 if (!containsStar) {
98 // No '*'s, so we make a shortcut
99 if (patIdxEnd != strIdxEnd) {
100 return false; // Pattern and string do not have the same size
101 }
102 for (int i = 0; i <= patIdxEnd; i++) {
103 char ch = patArr[i];
104 if (ch != '?' && different(caseSensitive, ch, strArr[i])) {
105 return false; // Character mismatch
106 }
107 }
108 return true; // String matches against pattern
109 }
110
111 if (patIdxEnd == 0) {
112 return true; // Pattern contains only '*', which matches anything
113 }
114
115 // Process characters before first star
116 while (true) {
117 char ch = patArr[patIdxStart];
118 if (ch == '*' || strIdxStart > strIdxEnd) {
119 break;
120 }
121 if (ch != '?'
122 && different(caseSensitive, ch, strArr[strIdxStart])) {
123 return false; // Character mismatch
124 }
125 patIdxStart++;
126 strIdxStart++;
127 }
128 if (strIdxStart > strIdxEnd) {
129 // All characters in the string are used. Check if only '*'s are
130 // left in the pattern. If so, we succeeded. Otherwise failure.
131 return allStars(patArr, patIdxStart, patIdxEnd);
132 }
133
134 // Process characters after last star
135 while (true) {
136 char ch = patArr[patIdxEnd];
137 if (ch == '*' || strIdxStart > strIdxEnd) {
138 break;
139 }
140 if (ch != '?' && different(caseSensitive, ch, strArr[strIdxEnd])) {
141 return false; // Character mismatch
142 }
143 patIdxEnd--;
144 strIdxEnd--;
145 }
146 if (strIdxStart > strIdxEnd) {
147 // All characters in the string are used. Check if only '*'s are
148 // left in the pattern. If so, we succeeded. Otherwise failure.
149 return allStars(patArr, patIdxStart, patIdxEnd);
150 }
151
152 // process pattern between stars. padIdxStart and patIdxEnd point
153 // always to a '*'.
154 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
155 int patIdxTmp = -1;
156 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
157 if (patArr[i] == '*') {
158 patIdxTmp = i;
159 break;
160 }
161 }
162 if (patIdxTmp == patIdxStart + 1) {
163 // Two stars next to each other, skip the first one.
164 patIdxStart++;
165 continue;
166 }
167 // Find the pattern between padIdxStart & padIdxTmp in str between
168 // strIdxStart & strIdxEnd
169 int patLength = (patIdxTmp - patIdxStart - 1);
170 int strLength = (strIdxEnd - strIdxStart + 1);
171 int foundIdx = -1;
172 strLoop:
173 for (int i = 0; i <= strLength - patLength; i++) {
174 for (int j = 0; j < patLength; j++) {
175 char ch = patArr[patIdxStart + j + 1];
176 if (ch != '?' && different(caseSensitive, ch,
177 strArr[strIdxStart + i + j])) {
178 continue strLoop;
179 }
180 }
181 foundIdx = strIdxStart + i;
182 break;
183 }
184
185 if (foundIdx == -1) {
186 return false;
187 }
188 patIdxStart = patIdxTmp;
189 strIdxStart = foundIdx + patLength;
190 }
191
192 // All characters in the string are used. Check if only '*'s are left
193 // in the pattern. If so, we succeeded. Otherwise failure.
194 return allStars(patArr, patIdxStart, patIdxEnd);
195 }
196
197 private static boolean allStars(char[] chars, int start, int end) {
198 for (int i = start; i <= end; ++i) {
199 if (chars[i] != '*') {
200 return false;
201 }
202 }
203 return true;
204 }
205
206 private static boolean different(
207 boolean caseSensitive, char ch, char other) {
208 return caseSensitive
209 ? ch != other
210 : Character.toUpperCase(ch) != Character.toUpperCase(other);
211 }
212
213 /**
214 * Tests whether or not a given path matches a given pattern.
215 *
216 * If you need to call this method multiple times with the same
217 * pattern you should rather use TokenizedPath
218 *
219 *
220 * @param pattern The pattern to match against. Must not be
221 * <code>null</code>.
222 * @param str The path to match, as a String. Must not be
223 * <code>null</code>.
224 *
225 * @return <code>true</code> if the pattern matches against the string,
226 * or <code>false</code> otherwise.
227 */
228 public static boolean matchPath(String pattern, String str) {
229 String[] patDirs = tokenizePathAsArray(pattern);
230 return matchPath(patDirs, tokenizePathAsArray(str), true);
231 }
232
233 /**
234 * Tests whether or not a given path matches a given pattern.
235 *
236 * If you need to call this method multiple times with the same
237 * pattern you should rather use TokenizedPattern
238 *
239 * @param pattern The pattern to match against. Must not be
240 * <code>null</code>.
241 * @param str The path to match, as a String. Must not be
242 * <code>null</code>.
243 * @param isCaseSensitive Whether or not matching should be performed
244 * case sensitively.
245 *
246 * @return <code>true</code> if the pattern matches against the string,
247 * or <code>false</code> otherwise.
248 */
249 public static boolean matchPath(String pattern, String str,
250 boolean isCaseSensitive) {
251 String[] patDirs = tokenizePathAsArray(pattern);
252 return matchPath(patDirs, tokenizePathAsArray(str), isCaseSensitive);
253 }
254
255
256 static String[] tokenizePathAsArray(String path) {
257 Path root = null;
258 Path fsPath = Paths.get( path );
259
260 if ( fsPath.isAbsolute()) {
261 root = fsPath.getRoot( );
262 path = root.relativize( fsPath ).toString();
263 }
264 char sep = File.separatorChar;
265 int start = 0;
266 int len = path.length();
267 int count = 0;
268 for (int pos = 0; pos < len; pos++) {
269 if (path.charAt(pos) == sep) {
270 if (pos != start) {
271 count++;
272 }
273 start = pos + 1;
274 }
275 }
276 if (len != start) {
277 count++;
278 }
279 String[] l = new String[count + ((root == null) ? 0 : 1)];
280
281 if (root != null) {
282 l[0] = root.toString();
283 count = 1;
284 } else {
285 count = 0;
286 }
287 start = 0;
288 for (int pos = 0; pos < len; pos++) {
289 if (path.charAt(pos) == sep) {
290 if (pos != start) {
291 String tok = path.substring(start, pos);
292 l[count++] = tok;
293 }
294 start = pos + 1;
295 }
296 }
297 if (len != start) {
298 String tok = path.substring(start);
299 l[count/*++*/] = tok;
300 }
301 return l;
302 }
303
304 /**
305 * Core implementation of matchPath. It is isolated so that it
306 * can be called from TokenizedPattern.
307 */
308 public static boolean matchPath( String[] tokenizedPattern, String[] strDirs,
309 boolean isCaseSensitive ) {
310 int patIdxStart = 0;
311 int patIdxEnd = tokenizedPattern.length - 1;
312 int strIdxStart = 0;
313 int strIdxEnd = strDirs.length - 1;
314
315 // up to first '**'
316 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
317 String patDir = tokenizedPattern[patIdxStart];
318 if (patDir.equals(DEEP_TREE_MATCH)) {
319 break;
320 }
321 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
322 return false;
323 }
324 patIdxStart++;
325 strIdxStart++;
326 }
327 if (strIdxStart > strIdxEnd) {
328 // String is exhausted
329 for (int i = patIdxStart; i <= patIdxEnd; i++) {
330 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
331 return false;
332 }
333 }
334 return true;
335 }
336 if (patIdxStart > patIdxEnd) {
337 // String not exhausted, but pattern is. Failure.
338 return false;
339 }
340
341 // up to last '**'
342 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
343 String patDir = tokenizedPattern[patIdxEnd];
344 if (patDir.equals(DEEP_TREE_MATCH)) {
345 break;
346 }
347 if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
348 return false;
349 }
350 patIdxEnd--;
351 strIdxEnd--;
352 }
353 if (strIdxStart > strIdxEnd) {
354 // String is exhausted
355 for (int i = patIdxStart; i <= patIdxEnd; i++) {
356 if (!tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
357 return false;
358 }
359 }
360 return true;
361 }
362
363 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
364 int patIdxTmp = -1;
365 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
366 if (tokenizedPattern[i].equals(DEEP_TREE_MATCH)) {
367 patIdxTmp = i;
368 break;
369 }
370 }
371 if (patIdxTmp == patIdxStart + 1) {
372 // '**/**' situation, so skip one
373 patIdxStart++;
374 continue;
375 }
376 // Find the pattern between padIdxStart & padIdxTmp in str between
377 // strIdxStart & strIdxEnd
378 int patLength = (patIdxTmp - patIdxStart - 1);
379 int strLength = (strIdxEnd - strIdxStart + 1);
380 int foundIdx = -1;
381 strLoop:
382 for (int i = 0; i <= strLength - patLength; i++) {
383 for (int j = 0; j < patLength; j++) {
384 String subPat = tokenizedPattern[patIdxStart + j + 1];
385 String subStr = strDirs[strIdxStart + i + j];
386 if (!match(subPat, subStr, isCaseSensitive)) {
387 continue strLoop;
388 }
389 }
390 foundIdx = strIdxStart + i;
391 break;
392 }
393 if (foundIdx == -1) {
394 return false;
395 }
396
397 patIdxStart = patIdxTmp;
398 strIdxStart = foundIdx + patLength;
399 }
400
401 for (int i = patIdxStart; i <= patIdxEnd; i++) {
402 if (!DEEP_TREE_MATCH.equals(tokenizedPattern[i])) {
403 return false;
404 }
405 }
406 return true;
407 }
408
409
410 }