This project has retired. For details please refer to its Attic page.
Source code
001package org.apache.archiva.configuration.util;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 *
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.File;
023import java.nio.file.Path;
024import java.nio.file.Paths;
025
026/**
027 *
028 * This provides a path matcher like in the SelectorUtils of the ant project.
029 *
030 * Using code from apache ant org.apache.tools.ant.types.selectors.SelectorUtils to remove the ant dependency for code
031 * compilation.
032 * See https://github.com/apache/ant/blob/master/src/main/org/apache/tools/ant/types/selectors/SelectorUtils.java
033 *
034 * @author Martin Stockhammer <martin_s@apache.org>
035 *
036 *
037 */
038public class PathMatcher
039{
040
041    private static final String DEEP_TREE_MATCH = "**";
042
043
044    /**
045     * Tests whether or not a string matches against a pattern.
046     * The pattern may contain two special characters:<br>
047     * '*' means zero or more characters<br>
048     * '?' means one and only one character
049     *
050     * @param pattern The pattern to match against.
051     *                Must not be <code>null</code>.
052     * @param str     The string which must be matched against the pattern.
053     *                Must not be <code>null</code>.
054     *
055     * @return <code>true</code> if the string matches against the pattern,
056     *         or <code>false</code> otherwise.
057     */
058    public static boolean match(String pattern, String str) {
059        return match(pattern, str, true);
060    }
061
062
063    /**
064     * Tests whether or not a string matches against a pattern.
065     * The pattern may contain two special characters:<br>
066     * '*' means zero or more characters<br>
067     * '?' means one and only one character
068     *
069     * @param pattern The pattern to match against.
070     *                Must not be <code>null</code>.
071     * @param str     The string which must be matched against the pattern.
072     *                Must not be <code>null</code>.
073     * @param caseSensitive Whether or not matching should be performed
074     *                        case sensitively.
075     *
076     *
077     * @return <code>true</code> if the string matches against the pattern,
078     *         or <code>false</code> otherwise.
079     */
080    public static boolean match(String pattern, String str,
081                                boolean caseSensitive) {
082        char[] patArr = pattern.toCharArray();
083        char[] strArr = str.toCharArray();
084        int patIdxStart = 0;
085        int patIdxEnd = patArr.length - 1;
086        int strIdxStart = 0;
087        int strIdxEnd = strArr.length - 1;
088
089        boolean containsStar = false;
090        for (char ch : patArr) {
091            if (ch == '*') {
092                containsStar = true;
093                break;
094            }
095        }
096
097        if (!containsStar) {
098            // No '*'s, so we make a shortcut
099            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}