This project has retired. For details please refer to its Attic page.
PathMatcher xref
View Javadoc
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 }