001    /**
002     *  Licensed to the Apache Software Foundation (ASF) under one or more
003     *  contributor license agreements.  See the NOTICE file distributed with
004     *  this work for additional information regarding copyright ownership.
005     *  The ASF licenses this file to You under the Apache License, Version 2.0
006     *  (the "License"); you may not use this file except in compliance with
007     *  the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.geronimo.kernel.config;
018    
019    import java.io.File;
020    import java.util.StringTokenizer;
021    import java.util.Vector;
022    
023    /**
024     * <p>This is a utility class used by selectors and DirectoryScanner. The
025     * functionality more properly belongs just to selectors, but unfortunately
026     * DirectoryScanner exposed these as protected methods. Thus we have to
027     * support any subclasses of DirectoryScanner that may access these methods.
028     * </p>
029     * <p>This is a Singleton.</p>
030     *
031     * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $
032     */
033    public final class SelectorUtils {
034        private static SelectorUtils instance = new SelectorUtils();
035    
036        private static boolean onNetWare = Os.isFamily("netware");
037        private static boolean onDos = Os.isFamily("dos");
038    
039        /**
040         * Private Constructor
041         */
042        private SelectorUtils() {
043        }
044    
045        /**
046         * Retrieves the instance of the Singleton.
047         * @return singleton instance
048         */
049        public static SelectorUtils getInstance() {
050            return instance;
051        }
052    
053        /**
054         * Tests whether or not a given path matches the start of a given
055         * pattern up to the first "**".
056         * <p>
057         * This is not a general purpose test and should only be used if you
058         * can live with false positives. For example, <code>pattern=**\a</code>
059         * and <code>str=b</code> will yield <code>true</code>.
060         *
061         * @param pattern The pattern to match against. Must not be
062         *                <code>null</code>.
063         * @param str     The path to match, as a String. Must not be
064         *                <code>null</code>.
065         *
066         * @return whether or not a given path matches the start of a given
067         * pattern up to the first "**".
068         */
069        public static boolean matchPatternStart(String pattern, String str) {
070            return matchPatternStart(pattern, str, true);
071        }
072    
073        /**
074         * Tests whether or not a given path matches the start of a given
075         * pattern up to the first "**".
076         * <p>
077         * This is not a general purpose test and should only be used if you
078         * can live with false positives. For example, <code>pattern=**\a</code>
079         * and <code>str=b</code> will yield <code>true</code>.
080         *
081         * @param pattern The pattern to match against. Must not be
082         *                <code>null</code>.
083         * @param str     The path to match, as a String. Must not be
084         *                <code>null</code>.
085         * @param isCaseSensitive Whether or not matching should be performed
086         *                        case sensitively.
087         *
088         * @return whether or not a given path matches the start of a given
089         * pattern up to the first "**".
090         */
091        public static boolean matchPatternStart(String pattern, String str,
092                                                boolean isCaseSensitive) {
093            // When str starts with a File.separator, pattern has to start with a
094            // File.separator.
095            // When pattern starts with a File.separator, str has to start with a
096            // File.separator.
097            if (str.startsWith(File.separator)
098                    != pattern.startsWith(File.separator)) {
099                return false;
100            }
101    
102            String[] patDirs = tokenizePathAsArray(pattern);
103            String[] strDirs = tokenizePathAsArray(str);
104    
105            int patIdxStart = 0;
106            int patIdxEnd = patDirs.length - 1;
107            int strIdxStart = 0;
108            int strIdxEnd = strDirs.length - 1;
109    
110            // up to first '**'
111            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
112                String patDir = patDirs[patIdxStart];
113                if (patDir.equals("**")) {
114                    break;
115                }
116                if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
117                    return false;
118                }
119                patIdxStart++;
120                strIdxStart++;
121            }
122    
123            if (strIdxStart > strIdxEnd) {
124                // String is exhausted
125                return true;
126            } else if (patIdxStart > patIdxEnd) {
127                // String not exhausted, but pattern is. Failure.
128                return false;
129            } else {
130                // pattern now holds ** while string is not exhausted
131                // this will generate false positives but we can live with that.
132                return true;
133            }
134        }
135    
136        /**
137         * Tests whether or not a given path matches a given pattern.
138         *
139         * @param pattern The pattern to match against. Must not be
140         *                <code>null</code>.
141         * @param str     The path to match, as a String. Must not be
142         *                <code>null</code>.
143         *
144         * @return <code>true</code> if the pattern matches against the string,
145         *         or <code>false</code> otherwise.
146         */
147        public static boolean matchPath(String pattern, String str) {
148            return matchPath(pattern, str, true);
149        }
150    
151        /**
152         * Tests whether or not a given path matches a given pattern.
153         *
154         * @param pattern The pattern to match against. Must not be
155         *                <code>null</code>.
156         * @param str     The path to match, as a String. Must not be
157         *                <code>null</code>.
158         * @param isCaseSensitive Whether or not matching should be performed
159         *                        case sensitively.
160         *
161         * @return <code>true</code> if the pattern matches against the string,
162         *         or <code>false</code> otherwise.
163         */
164        public static boolean matchPath(String pattern, String str,
165                                        boolean isCaseSensitive) {
166            String[] patDirs = tokenizePathAsArray(pattern);
167            String[] strDirs = tokenizePathAsArray(str);
168    
169            int patIdxStart = 0;
170            int patIdxEnd = patDirs.length - 1;
171            int strIdxStart = 0;
172            int strIdxEnd = strDirs.length - 1;
173    
174            // up to first '**'
175            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
176                String patDir = patDirs[patIdxStart];
177                if (patDir.equals("**")) {
178                    break;
179                }
180                if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
181                    patDirs = null;
182                    strDirs = null;
183                    return false;
184                }
185                patIdxStart++;
186                strIdxStart++;
187            }
188            if (strIdxStart > strIdxEnd) {
189                // String is exhausted
190                for (int i = patIdxStart; i <= patIdxEnd; i++) {
191                    if (!patDirs[i].equals("**")) {
192                        patDirs = null;
193                        strDirs = null;
194                        return false;
195                    }
196                }
197                return true;
198            } else {
199                if (patIdxStart > patIdxEnd) {
200                    // String not exhausted, but pattern is. Failure.
201                    patDirs = null;
202                    strDirs = null;
203                    return false;
204                }
205            }
206    
207            // up to last '**'
208            while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
209                String patDir = patDirs[patIdxEnd];
210                if (patDir.equals("**")) {
211                    break;
212                }
213                if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
214                    patDirs = null;
215                    strDirs = null;
216                    return false;
217                }
218                patIdxEnd--;
219                strIdxEnd--;
220            }
221            if (strIdxStart > strIdxEnd) {
222                // String is exhausted
223                for (int i = patIdxStart; i <= patIdxEnd; i++) {
224                    if (!patDirs[i].equals("**")) {
225                        patDirs = null;
226                        strDirs = null;
227                        return false;
228                    }
229                }
230                return true;
231            }
232    
233            while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
234                int patIdxTmp = -1;
235                for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
236                    if (patDirs[i].equals("**")) {
237                        patIdxTmp = i;
238                        break;
239                    }
240                }
241                if (patIdxTmp == patIdxStart + 1) {
242                    // '**/**' situation, so skip one
243                    patIdxStart++;
244                    continue;
245                }
246                // Find the pattern between padIdxStart & padIdxTmp in str between
247                // strIdxStart & strIdxEnd
248                int patLength = (patIdxTmp - patIdxStart - 1);
249                int strLength = (strIdxEnd - strIdxStart + 1);
250                int foundIdx = -1;
251                strLoop:
252                            for (int i = 0; i <= strLength - patLength; i++) {
253                                for (int j = 0; j < patLength; j++) {
254                                    String subPat = patDirs[patIdxStart + j + 1];
255                                    String subStr = strDirs[strIdxStart + i + j];
256                                    if (!match(subPat, subStr, isCaseSensitive)) {
257                                        continue strLoop;
258                                    }
259                                }
260    
261                                foundIdx = strIdxStart + i;
262                                break;
263                            }
264    
265                if (foundIdx == -1) {
266                    patDirs = null;
267                    strDirs = null;
268                    return false;
269                }
270    
271                patIdxStart = patIdxTmp;
272                strIdxStart = foundIdx + patLength;
273            }
274    
275            for (int i = patIdxStart; i <= patIdxEnd; i++) {
276                if (!patDirs[i].equals("**")) {
277                    patDirs = null;
278                    strDirs = null;
279                    return false;
280                }
281            }
282    
283            return true;
284        }
285    
286        /**
287         * Tests whether or not a string matches against a pattern.
288         * The pattern may contain two special characters:<br>
289         * '*' means zero or more characters<br>
290         * '?' means one and only one character
291         *
292         * @param pattern The pattern to match against.
293         *                Must not be <code>null</code>.
294         * @param str     The string which must be matched against the pattern.
295         *                Must not be <code>null</code>.
296         *
297         * @return <code>true</code> if the string matches against the pattern,
298         *         or <code>false</code> otherwise.
299         */
300        public static boolean match(String pattern, String str) {
301            return match(pattern, str, true);
302        }
303    
304        /**
305         * Tests whether or not a string matches against a pattern.
306         * The pattern may contain two special characters:<br>
307         * '*' means zero or more characters<br>
308         * '?' means one and only one character
309         *
310         * @param pattern The pattern to match against.
311         *                Must not be <code>null</code>.
312         * @param str     The string which must be matched against the pattern.
313         *                Must not be <code>null</code>.
314         * @param isCaseSensitive Whether or not matching should be performed
315         *                        case sensitively.
316         *
317         *
318         * @return <code>true</code> if the string matches against the pattern,
319         *         or <code>false</code> otherwise.
320         */
321        public static boolean match(String pattern, String str,
322                                    boolean isCaseSensitive) {
323            char[] patArr = pattern.toCharArray();
324            char[] strArr = str.toCharArray();
325            int patIdxStart = 0;
326            int patIdxEnd = patArr.length - 1;
327            int strIdxStart = 0;
328            int strIdxEnd = strArr.length - 1;
329            char ch;
330    
331            boolean containsStar = false;
332            for (int i = 0; i < patArr.length; i++) {
333                if (patArr[i] == '*') {
334                    containsStar = true;
335                    break;
336                }
337            }
338    
339            if (!containsStar) {
340                // No '*'s, so we make a shortcut
341                if (patIdxEnd != strIdxEnd) {
342                    return false; // Pattern and string do not have the same size
343                }
344                for (int i = 0; i <= patIdxEnd; i++) {
345                    ch = patArr[i];
346                    if (ch != '?') {
347                        if (isCaseSensitive && ch != strArr[i]) {
348                            return false; // Character mismatch
349                        }
350                        if (!isCaseSensitive && Character.toUpperCase(ch)
351                                != Character.toUpperCase(strArr[i])) {
352                            return false;  // Character mismatch
353                        }
354                    }
355                }
356                return true; // String matches against pattern
357            }
358    
359            if (patIdxEnd == 0) {
360                return true; // Pattern contains only '*', which matches anything
361            }
362    
363            // Process characters before first star
364            while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
365                if (ch != '?') {
366                    if (isCaseSensitive && ch != strArr[strIdxStart]) {
367                        return false; // Character mismatch
368                    }
369                    if (!isCaseSensitive && Character.toUpperCase(ch)
370                            != Character.toUpperCase(strArr[strIdxStart])) {
371                        return false; // Character mismatch
372                    }
373                }
374                patIdxStart++;
375                strIdxStart++;
376            }
377            if (strIdxStart > strIdxEnd) {
378                // All characters in the string are used. Check if only '*'s are
379                // left in the pattern. If so, we succeeded. Otherwise failure.
380                for (int i = patIdxStart; i <= patIdxEnd; i++) {
381                    if (patArr[i] != '*') {
382                        return false;
383                    }
384                }
385                return true;
386            }
387    
388            // Process characters after last star
389            while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
390                if (ch != '?') {
391                    if (isCaseSensitive && ch != strArr[strIdxEnd]) {
392                        return false; // Character mismatch
393                    }
394                    if (!isCaseSensitive && Character.toUpperCase(ch)
395                            != Character.toUpperCase(strArr[strIdxEnd])) {
396                        return false; // Character mismatch
397                    }
398                }
399                patIdxEnd--;
400                strIdxEnd--;
401            }
402            if (strIdxStart > strIdxEnd) {
403                // All characters in the string are used. Check if only '*'s are
404                // left in the pattern. If so, we succeeded. Otherwise failure.
405                for (int i = patIdxStart; i <= patIdxEnd; i++) {
406                    if (patArr[i] != '*') {
407                        return false;
408                    }
409                }
410                return true;
411            }
412    
413            // process pattern between stars. padIdxStart and patIdxEnd point
414            // always to a '*'.
415            while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
416                int patIdxTmp = -1;
417                for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
418                    if (patArr[i] == '*') {
419                        patIdxTmp = i;
420                        break;
421                    }
422                }
423                if (patIdxTmp == patIdxStart + 1) {
424                    // Two stars next to each other, skip the first one.
425                    patIdxStart++;
426                    continue;
427                }
428                // Find the pattern between padIdxStart & padIdxTmp in str between
429                // strIdxStart & strIdxEnd
430                int patLength = (patIdxTmp - patIdxStart - 1);
431                int strLength = (strIdxEnd - strIdxStart + 1);
432                int foundIdx = -1;
433                strLoop:
434                for (int i = 0; i <= strLength - patLength; i++) {
435                    for (int j = 0; j < patLength; j++) {
436                        ch = patArr[patIdxStart + j + 1];
437                        if (ch != '?') {
438                            if (isCaseSensitive && ch != strArr[strIdxStart + i
439                                    + j]) {
440                                continue strLoop;
441                            }
442                            if (!isCaseSensitive
443                                && Character.toUpperCase(ch)
444                                    != Character.toUpperCase(strArr[strIdxStart + i + j])) {
445                                continue strLoop;
446                            }
447                        }
448                    }
449    
450                    foundIdx = strIdxStart + i;
451                    break;
452                }
453    
454                if (foundIdx == -1) {
455                    return false;
456                }
457    
458                patIdxStart = patIdxTmp;
459                strIdxStart = foundIdx + patLength;
460            }
461    
462            // All characters in the string are used. Check if only '*'s are left
463            // in the pattern. If so, we succeeded. Otherwise failure.
464            for (int i = patIdxStart; i <= patIdxEnd; i++) {
465                if (patArr[i] != '*') {
466                    return false;
467                }
468            }
469            return true;
470        }
471    
472        /**
473         * Breaks a path up into a Vector of path elements, tokenizing on
474         * <code>File.separator</code>.
475         *
476         * @param path Path to tokenize. Must not be <code>null</code>.
477         *
478         * @return a Vector of path elements from the tokenized path
479         */
480        public static Vector tokenizePath (String path) {
481            return tokenizePath(path, File.separator);
482        }
483    
484        /**
485         * Verifies that the specified filename represents an absolute path.
486         * Differs from new java.io.File("filename").isAbsolute() in that a path
487         * beginning with a double file separator--signifying a Windows UNC--must
488         * at minimum match "\\a\b" to be considered an absolute path.
489         * @param filename the filename to be checked.
490         * @return true if the filename represents an absolute path.
491         * @throws java.lang.NullPointerException if filename is null.
492         * @since Ant 1.6.3
493         */
494        public static boolean isAbsolutePath(String filename) {
495            int len = filename.length();
496            if (len == 0) {
497                return false;
498            }
499            char sep = File.separatorChar;
500            filename = filename.replace('/', sep).replace('\\', sep);
501            char c = filename.charAt(0);
502            if (!(onDos || onNetWare)) {
503                return (c == sep);
504            }
505            if (c == sep) {
506                if (!(onDos && len > 4 && filename.charAt(1) == sep)) {
507                    return false;
508                }
509                int nextsep = filename.indexOf(sep, 2);
510                return nextsep > 2 && nextsep + 1 < len;
511            }
512            int colon = filename.indexOf(':');
513            return (Character.isLetter(c) && colon == 1
514                && filename.length() > 2 && filename.charAt(2) == sep)
515                || (onNetWare && colon > 0);
516        }
517    
518        /**
519         * Dissect the specified absolute path.
520         * @param path the path to dissect.
521         * @return String[] {root, remaining path}.
522         * @throws java.lang.NullPointerException if path is null.
523         */
524        public static String[] dissect(String path) {
525            char sep = File.separatorChar;
526            path = path.replace('/', sep).replace('\\', sep);
527    
528            // make sure we are dealing with an absolute path
529            if (!isAbsolutePath(path)) {
530                throw new IllegalArgumentException(path + " is not an absolute path");
531            }
532            String root = null;
533            int colon = path.indexOf(':');
534            if (colon > 0 && (onDos || onNetWare)) {
535    
536                int next = colon + 1;
537                root = path.substring(0, next).toUpperCase();
538                char[] ca = path.toCharArray();
539                root += sep;
540                //remove the initial separator; the root has it.
541                next = (ca[next] == sep) ? next + 1 : next;
542    
543                StringBuffer sbPath = new StringBuffer();
544                // Eliminate consecutive slashes after the drive spec:
545                for (int i = next; i < ca.length; i++) {
546                    if (ca[i] != sep || ca[i - 1] != sep) {
547                        sbPath.append(ca[i]);
548                    }
549                }
550                path = sbPath.toString();
551            } else if (path.length() > 1 && path.charAt(1) == sep) {
552                // UNC drive
553                int nextsep = path.indexOf(sep, 2);
554                nextsep = path.indexOf(sep, nextsep + 1);
555                root = (nextsep > 2) ? path.substring(0, nextsep + 1) : path;
556                path = path.substring(root.length());
557            } else {
558                root = File.separator;
559                path = path.substring(1);
560            }
561            return new String[] {root, path};
562        }
563    
564    
565        /**
566         * Breaks a path up into a Vector of path elements, tokenizing on
567         *
568         * @param path Path to tokenize. Must not be <code>null</code>.
569         * @param separator the separator against which to tokenize.
570         *
571         * @return a Vector of path elements from the tokenized path
572         * @since Ant 1.6
573         */
574        public static Vector tokenizePath (String path, String separator) {
575            Vector ret = new Vector();
576            if (isAbsolutePath(path)) {
577                String[] s = dissect(path);
578                ret.add(s[0]);
579                path = s[1];
580            }
581            StringTokenizer st = new StringTokenizer(path, separator);
582            while (st.hasMoreTokens()) {
583                ret.addElement(st.nextToken());
584            }
585            return ret;
586        }
587    
588        /**
589         * Same as {@link #tokenizePath tokenizePath} but hopefully faster.
590         */
591        private static String[] tokenizePathAsArray(String path) {
592            String root = null;
593            if (isAbsolutePath(path)) {
594                String[] s = dissect(path);
595                root = s[0];
596                path = s[1];
597            }
598            char sep = File.separatorChar;
599            path = path.replace('/', sep).replace('\\', sep);
600            
601            int start = 0;
602            int len = path.length();
603            int count = 0;
604            for (int pos = 0; pos < len; pos++) {
605                if (path.charAt(pos) == sep) {
606                    if (pos != start) {
607                        count++;
608                    }
609                    start = pos + 1;
610                }
611            }
612            if (len != start) {
613                count++;
614            }
615            String[] l = new String[count + ((root == null) ? 0 : 1)];
616    
617            if (root != null) {
618                l[0] = root;
619                count = 1;
620            } else {
621                count = 0;
622            }
623            start = 0;
624            for (int pos = 0; pos < len; pos++) {
625                if (path.charAt(pos) == sep) {
626                    if (pos != start) {
627                        String tok = path.substring(start, pos);
628                        l[count++] = tok;
629                    }
630                    start = pos + 1;
631                }
632            }
633            if (len != start) {
634                String tok = path.substring(start);
635                l[count/*++*/] = tok;
636            }
637            return l;
638        }
639    
640    
641        /**
642         * Returns dependency information on these two files. If src has been
643         * modified later than target, it returns true. If target doesn't exist,
644         * it likewise returns true. Otherwise, target is newer than src and
645         * is not out of date, thus the method returns false. It also returns
646         * false if the src file doesn't even exist, since how could the
647         * target then be out of date.
648         *
649         * @param src the original file
650         * @param target the file being compared against
651         * @param granularity the amount in seconds of slack we will give in
652         *        determining out of dateness
653         * @return whether the target is out of date
654         */
655        public static boolean isOutOfDate(File src, File target, int granularity) {
656            if (!src.exists()) {
657                return false;
658            }
659            if (!target.exists()) {
660                return true;
661            }
662            if ((src.lastModified() - granularity) > target.lastModified()) {
663                return true;
664            }
665            return false;
666        }
667    
668        /**
669         * "Flattens" a string by removing all whitespace (space, tab, linefeed,
670         * carriage return, and formfeed). This uses StringTokenizer and the
671         * default set of tokens as documented in the single arguement constructor.
672         *
673         * @param input a String to remove all whitespace.
674         * @return a String that has had all whitespace removed.
675         */
676        public static String removeWhitespace(String input) {
677            StringBuffer result = new StringBuffer();
678            if (input != null) {
679                StringTokenizer st = new StringTokenizer(input);
680                while (st.hasMoreTokens()) {
681                    result.append(st.nextToken());
682                }
683            }
684            return result.toString();
685        }
686    
687        /**
688         * Tests if a string contains stars or question marks
689         * @param input a String which one wants to test for containing wildcard
690         * @return true if the string contains at least a star or a question mark
691         */
692        public static boolean hasWildcards(String input) {
693            return (input.indexOf('*') != -1 || input.indexOf('?') != -1);
694        }
695    
696        /**
697         * removes from a pattern all tokens to the right containing wildcards
698         * @param input the input string
699         * @return the leftmost part of the pattern without wildcards
700         */
701        public static String rtrimWildcardTokens(String input) {
702            Vector v = tokenizePath(input, File.separator);
703            StringBuffer sb = new StringBuffer();
704            for (int counter = 0; counter < v.size(); counter++) {
705                if (hasWildcards((String) v.elementAt(counter))) {
706                    break;
707                }
708                if (counter > 0 && sb.charAt(sb.length() - 1) != File.separatorChar) {
709                    sb.append(File.separator);
710                }
711                sb.append((String) v.elementAt(counter));
712            }
713            return sb.toString();
714        }
715    }
716