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: 476049 $ $Date: 2006-11-16 23:35:17 -0500 (Thu, 16 Nov 2006) $
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            int start = 0;
600            int len = path.length();
601            int count = 0;
602            for (int pos = 0; pos < len; pos++) {
603                if (path.charAt(pos) == sep) {
604                    if (pos != start) {
605                        count++;
606                    }
607                    start = pos + 1;
608                }
609            }
610            if (len != start) {
611                count++;
612            }
613            String[] l = new String[count + ((root == null) ? 0 : 1)];
614    
615            if (root != null) {
616                l[0] = root;
617                count = 1;
618            } else {
619                count = 0;
620            }
621            start = 0;
622            for (int pos = 0; pos < len; pos++) {
623                if (path.charAt(pos) == sep) {
624                    if (pos != start) {
625                        String tok = path.substring(start, pos);
626                        l[count++] = tok;
627                    }
628                    start = pos + 1;
629                }
630            }
631            if (len != start) {
632                String tok = path.substring(start);
633                l[count/*++*/] = tok;
634            }
635            return l;
636        }
637    
638    
639        /**
640         * Returns dependency information on these two files. If src has been
641         * modified later than target, it returns true. If target doesn't exist,
642         * it likewise returns true. Otherwise, target is newer than src and
643         * is not out of date, thus the method returns false. It also returns
644         * false if the src file doesn't even exist, since how could the
645         * target then be out of date.
646         *
647         * @param src the original file
648         * @param target the file being compared against
649         * @param granularity the amount in seconds of slack we will give in
650         *        determining out of dateness
651         * @return whether the target is out of date
652         */
653        public static boolean isOutOfDate(File src, File target, int granularity) {
654            if (!src.exists()) {
655                return false;
656            }
657            if (!target.exists()) {
658                return true;
659            }
660            if ((src.lastModified() - granularity) > target.lastModified()) {
661                return true;
662            }
663            return false;
664        }
665    
666        /**
667         * "Flattens" a string by removing all whitespace (space, tab, linefeed,
668         * carriage return, and formfeed). This uses StringTokenizer and the
669         * default set of tokens as documented in the single arguement constructor.
670         *
671         * @param input a String to remove all whitespace.
672         * @return a String that has had all whitespace removed.
673         */
674        public static String removeWhitespace(String input) {
675            StringBuffer result = new StringBuffer();
676            if (input != null) {
677                StringTokenizer st = new StringTokenizer(input);
678                while (st.hasMoreTokens()) {
679                    result.append(st.nextToken());
680                }
681            }
682            return result.toString();
683        }
684    
685        /**
686         * Tests if a string contains stars or question marks
687         * @param input a String which one wants to test for containing wildcard
688         * @return true if the string contains at least a star or a question mark
689         */
690        public static boolean hasWildcards(String input) {
691            return (input.indexOf('*') != -1 || input.indexOf('?') != -1);
692        }
693    
694        /**
695         * removes from a pattern all tokens to the right containing wildcards
696         * @param input the input string
697         * @return the leftmost part of the pattern without wildcards
698         */
699        public static String rtrimWildcardTokens(String input) {
700            Vector v = tokenizePath(input, File.separator);
701            StringBuffer sb = new StringBuffer();
702            for (int counter = 0; counter < v.size(); counter++) {
703                if (hasWildcards((String) v.elementAt(counter))) {
704                    break;
705                }
706                if (counter > 0 && sb.charAt(sb.length() - 1) != File.separatorChar) {
707                    sb.append(File.separator);
708                }
709                sb.append((String) v.elementAt(counter));
710            }
711            return sb.toString();
712        }
713    }
714