001    package org.codehaus.mojo.wagon.shared;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
005     * agreements. See the NOTICE file distributed with this work for additional information regarding
006     * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance with the License. You may obtain a
008     * copy of the License at
009     *
010     * http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software distributed under the License
013     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
014     * or implied. See the License for the specific language governing permissions and limitations under
015     * the License.
016     */
017    
018    import java.util.StringTokenizer;
019    import java.util.Vector;
020    
021    /**
022     * A copy of plexus-util's SelectorUtils to deal with unix file separator only.
023     */
024    public final class SelectorUtils {
025    
026        /**
027         * Tests whether or not a given path matches the start of a given pattern up to the first "**".
028         * <p>
029         * This is not a general purpose test and should only be used if you can live with false positives. For example,
030         * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
031         *
032         * @param pattern
033         *            The pattern to match against. Must not be <code>null</code>.
034         * @param str
035         *            The path to match, as a String. Must not be <code>null</code>.
036         *
037         * @return whether or not a given path matches the start of a given pattern up to the first "**".
038         */
039        public static boolean matchPatternStart(String pattern, String str) {
040            return matchPatternStart(pattern, str, true);
041        }
042    
043        /**
044         * Tests whether or not a given path matches the start of a given pattern up to the first "**".
045         * <p>
046         * This is not a general purpose test and should only be used if you can live with false positives. For example,
047         * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.
048         *
049         * @param pattern
050         *            The pattern to match against. Must not be <code>null</code>.
051         * @param str
052         *            The path to match, as a String. Must not be <code>null</code>.
053         * @param isCaseSensitive
054         *            Whether or not matching should be performed case sensitively.
055         *
056         * @return whether or not a given path matches the start of a given pattern up to the first "**".
057         */
058        public static boolean matchPatternStart(String pattern, String str, boolean isCaseSensitive) {
059            // When str starts with a separator, pattern has to start with a
060            // separator.
061            // When pattern starts with a separator, str has to start with a
062            // separator.
063            if (str.startsWith("/") != pattern.startsWith("/")) {
064                return false;
065            }
066    
067            Vector<?> patDirs = tokenizePath(pattern);
068            Vector<?> strDirs = tokenizePath(str);
069    
070            int patIdxStart = 0;
071            int patIdxEnd = patDirs.size() - 1;
072            int strIdxStart = 0;
073            int strIdxEnd = strDirs.size() - 1;
074    
075            // up to first '**'
076            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
077                String patDir = (String) patDirs.elementAt(patIdxStart);
078                if (patDir.equals("**")) {
079                    break;
080                }
081                if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
082                    return false;
083                }
084                patIdxStart++;
085                strIdxStart++;
086            }
087    
088            if (strIdxStart > strIdxEnd) {
089                // String is exhausted
090                return true;
091            } else if (patIdxStart > patIdxEnd) {
092                // String not exhausted, but pattern is. Failure.
093                return false;
094            } else {
095                // pattern now holds ** while string is not exhausted
096                // this will generate false positives but we can live with that.
097                return true;
098            }
099        }
100    
101        /**
102         * Tests whether or not a given path matches a given pattern.
103         *
104         * @param pattern
105         *            The pattern to match against. Must not be <code>null</code>.
106         * @param str
107         *            The path to match, as a String. Must not be <code>null</code>.
108         *
109         * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
110         */
111        public static boolean matchPath(String pattern, String str) {
112            return matchPath(pattern, str, true);
113        }
114    
115        /**
116         * Tests whether or not a given path matches a given pattern.
117         *
118         * @param pattern
119         *            The pattern to match against. Must not be <code>null</code>.
120         * @param str
121         *            The path to match, as a String. Must not be <code>null</code>.
122         * @param isCaseSensitive
123         *            Whether or not matching should be performed case sensitively.
124         *
125         * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
126         */
127        public static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
128            // When str starts with a separator, pattern has to start with a
129            // separator.
130            // When pattern starts with a separator, str has to start with a
131            // separator.
132            if (str.startsWith("/") != pattern.startsWith("/")) {
133                return false;
134            }
135    
136            Vector<?> patDirs = tokenizePath(pattern);
137            Vector<?> strDirs = tokenizePath(str);
138    
139            int patIdxStart = 0;
140            int patIdxEnd = patDirs.size() - 1;
141            int strIdxStart = 0;
142            int strIdxEnd = strDirs.size() - 1;
143    
144            // up to first '**'
145            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
146                String patDir = (String) patDirs.elementAt(patIdxStart);
147                if (patDir.equals("**")) {
148                    break;
149                }
150                if (!match(patDir, (String) strDirs.elementAt(strIdxStart), isCaseSensitive)) {
151                    patDirs = null;
152                    strDirs = null;
153                    return false;
154                }
155                patIdxStart++;
156                strIdxStart++;
157            }
158            if (strIdxStart > strIdxEnd) {
159                // String is exhausted
160                for (int i = patIdxStart; i <= patIdxEnd; i++) {
161                    if (!patDirs.elementAt(i).equals("**")) {
162                        patDirs = null;
163                        strDirs = null;
164                        return false;
165                    }
166                }
167                return true;
168            } else {
169                if (patIdxStart > patIdxEnd) {
170                    // String not exhausted, but pattern is. Failure.
171                    patDirs = null;
172                    strDirs = null;
173                    return false;
174                }
175            }
176    
177            // up to last '**'
178            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
179                String patDir = (String) patDirs.elementAt(patIdxEnd);
180                if (patDir.equals("**")) {
181                    break;
182                }
183                if (!match(patDir, (String) strDirs.elementAt(strIdxEnd), isCaseSensitive)) {
184                    patDirs = null;
185                    strDirs = null;
186                    return false;
187                }
188                patIdxEnd--;
189                strIdxEnd--;
190            }
191            if (strIdxStart > strIdxEnd) {
192                // String is exhausted
193                for (int i = patIdxStart; i <= patIdxEnd; i++) {
194                    if (!patDirs.elementAt(i).equals("**")) {
195                        patDirs = null;
196                        strDirs = null;
197                        return false;
198                    }
199                }
200                return true;
201            }
202    
203            while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
204                int patIdxTmp = -1;
205                for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
206                    if (patDirs.elementAt(i).equals("**")) {
207                        patIdxTmp = i;
208                        break;
209                    }
210                }
211                if (patIdxTmp == patIdxStart + 1) {
212                    // '**/**' situation, so skip one
213                    patIdxStart++;
214                    continue;
215                }
216                // Find the pattern between padIdxStart & padIdxTmp in str between
217                // strIdxStart & strIdxEnd
218                int patLength = (patIdxTmp - patIdxStart - 1);
219                int strLength = (strIdxEnd - strIdxStart + 1);
220                int foundIdx = -1;
221                strLoop: for (int i = 0; i <= strLength - patLength; i++) {
222                    for (int j = 0; j < patLength; j++) {
223                        String subPat = (String) patDirs.elementAt(patIdxStart + j + 1);
224                        String subStr = (String) strDirs.elementAt(strIdxStart + i + j);
225                        if (!match(subPat, subStr, isCaseSensitive)) {
226                            continue strLoop;
227                        }
228                    }
229    
230                    foundIdx = strIdxStart + i;
231                    break;
232                }
233    
234                if (foundIdx == -1) {
235                    patDirs = null;
236                    strDirs = null;
237                    return false;
238                }
239    
240                patIdxStart = patIdxTmp;
241                strIdxStart = foundIdx + patLength;
242            }
243    
244            for (int i = patIdxStart; i <= patIdxEnd; i++) {
245                if (!patDirs.elementAt(i).equals("**")) {
246                    patDirs = null;
247                    strDirs = null;
248                    return false;
249                }
250            }
251    
252            return true;
253        }
254    
255        /**
256         * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
257         * '*' means zero or more characters<br>
258         * '?' means one and only one character
259         *
260         * @param pattern
261         *            The pattern to match against. Must not be <code>null</code>.
262         * @param str
263         *            The string which must be matched against the pattern. Must not be <code>null</code>.
264         *
265         * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
266         */
267        public static boolean match(String pattern, String str) {
268            return match(pattern, str, true);
269        }
270    
271        /**
272         * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
273         * '*' means zero or more characters<br>
274         * '?' means one and only one character
275         *
276         * @param pattern
277         *            The pattern to match against. Must not be <code>null</code>.
278         * @param str
279         *            The string which must be matched against the pattern. Must not be <code>null</code>.
280         * @param isCaseSensitive
281         *            Whether or not matching should be performed case sensitively.
282         *
283         *
284         * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
285         */
286        public static boolean match(String pattern, String str, boolean isCaseSensitive) {
287            char[] patArr = pattern.toCharArray();
288            char[] strArr = str.toCharArray();
289            int patIdxStart = 0;
290            int patIdxEnd = patArr.length - 1;
291            int strIdxStart = 0;
292            int strIdxEnd = strArr.length - 1;
293            char ch;
294    
295            boolean containsStar = false;
296            for (int i = 0; i < patArr.length; i++) {
297                if (patArr[i] == '*') {
298                    containsStar = true;
299                    break;
300                }
301            }
302    
303            if (!containsStar) {
304                // No '*'s, so we make a shortcut
305                if (patIdxEnd != strIdxEnd) {
306                    return false; // Pattern and string do not have the same size
307                }
308                for (int i = 0; i <= patIdxEnd; i++) {
309                    ch = patArr[i];
310                    if (ch != '?' && !equals(ch, strArr[i], isCaseSensitive)) {
311                        return false; // Character mismatch
312                    }
313                }
314                return true; // String matches against pattern
315            }
316    
317            if (patIdxEnd == 0) {
318                return true; // Pattern contains only '*', which matches anything
319            }
320    
321            // Process characters before first star
322            while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
323                if (ch != '?' && !equals(ch, strArr[strIdxStart], isCaseSensitive)) {
324                    return false; // Character mismatch
325                }
326                patIdxStart++;
327                strIdxStart++;
328            }
329            if (strIdxStart > strIdxEnd) {
330                // All characters in the string are used. Check if only '*'s are
331                // left in the pattern. If so, we succeeded. Otherwise failure.
332                for (int i = patIdxStart; i <= patIdxEnd; i++) {
333                    if (patArr[i] != '*') {
334                        return false;
335                    }
336                }
337                return true;
338            }
339    
340            // Process characters after last star
341            while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
342                if (ch != '?' && !equals(ch, strArr[strIdxEnd], isCaseSensitive)) {
343                    return false; // Character mismatch
344                }
345                patIdxEnd--;
346                strIdxEnd--;
347            }
348            if (strIdxStart > strIdxEnd) {
349                // All characters in the string are used. Check if only '*'s are
350                // left in the pattern. If so, we succeeded. Otherwise failure.
351                for (int i = patIdxStart; i <= patIdxEnd; i++) {
352                    if (patArr[i] != '*') {
353                        return false;
354                    }
355                }
356                return true;
357            }
358    
359            // process pattern between stars. padIdxStart and patIdxEnd point
360            // always to a '*'.
361            while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
362                int patIdxTmp = -1;
363                for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
364                    if (patArr[i] == '*') {
365                        patIdxTmp = i;
366                        break;
367                    }
368                }
369                if (patIdxTmp == patIdxStart + 1) {
370                    // Two stars next to each other, skip the first one.
371                    patIdxStart++;
372                    continue;
373                }
374                // Find the pattern between padIdxStart & padIdxTmp in str between
375                // strIdxStart & strIdxEnd
376                int patLength = (patIdxTmp - patIdxStart - 1);
377                int strLength = (strIdxEnd - strIdxStart + 1);
378                int foundIdx = -1;
379                strLoop: for (int i = 0; i <= strLength - patLength; i++) {
380                    for (int j = 0; j < patLength; j++) {
381                        ch = patArr[patIdxStart + j + 1];
382                        if (ch != '?' && !equals(ch, strArr[strIdxStart + i + j], isCaseSensitive)) {
383                            continue strLoop;
384                        }
385                    }
386    
387                    foundIdx = strIdxStart + i;
388                    break;
389                }
390    
391                if (foundIdx == -1) {
392                    return false;
393                }
394    
395                patIdxStart = patIdxTmp;
396                strIdxStart = foundIdx + patLength;
397            }
398    
399            // All characters in the string are used. Check if only '*'s are left
400            // in the pattern. If so, we succeeded. Otherwise failure.
401            for (int i = patIdxStart; i <= patIdxEnd; i++) {
402                if (patArr[i] != '*') {
403                    return false;
404                }
405            }
406            return true;
407        }
408    
409        /**
410         * Tests whether two characters are equal.
411         */
412        private static boolean equals(char c1, char c2, boolean isCaseSensitive) {
413            if (c1 == c2) {
414                return true;
415            }
416            if (!isCaseSensitive) {
417                // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
418                if (Character.toUpperCase(c1) == Character.toUpperCase(c2)
419                        || Character.toLowerCase(c1) == Character.toLowerCase(c2)) {
420                    return true;
421                }
422            }
423            return false;
424        }
425    
426        /**
427         * Breaks a path up into a Vector of path elements, tokenizing on <code>File.separator</code>.
428         *
429         * @param path
430         *            Path to tokenize. Must not be <code>null</code>.
431         *
432         * @return a Vector of path elements from the tokenized path
433         */
434        public static Vector<String> tokenizePath(String path) {
435            Vector<String> ret = new Vector<String>();
436            StringTokenizer st = new StringTokenizer(path, "/");
437            while (st.hasMoreTokens()) {
438                ret.addElement(st.nextToken());
439            }
440            return ret;
441        }
442    
443    }