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