View Javadoc

1   /**
2    *
3    * Copyright 2005 The Apache Software Foundation
4    *
5    *  Licensed under the Apache License, Version 2.0 (the "License");
6    *  you may not use this file except in compliance with the License.
7    *  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.geronimo.kernel.config;
18  
19  import java.io.File;
20  import java.util.StringTokenizer;
21  import java.util.Vector;
22  
23  /**
24   * <p>This is a utility class used by selectors and DirectoryScanner. The
25   * functionality more properly belongs just to selectors, but unfortunately
26   * DirectoryScanner exposed these as protected methods. Thus we have to
27   * support any subclasses of DirectoryScanner that may access these methods.
28   * </p>
29   * <p>This is a Singleton.</p>
30   *
31   * @version $Rev: 410741 $ $Date: 2006-05-31 21:35:48 -0700 (Wed, 31 May 2006) $
32   */
33  public final class SelectorUtils {
34      private static SelectorUtils instance = new SelectorUtils();
35  
36      private static boolean onNetWare = Os.isFamily("netware");
37      private static boolean onDos = Os.isFamily("dos");
38  
39      /**
40       * Private Constructor
41       */
42      private SelectorUtils() {
43      }
44  
45      /**
46       * Retrieves the instance of the Singleton.
47       * @return singleton instance
48       */
49      public static SelectorUtils getInstance() {
50          return instance;
51      }
52  
53      /**
54       * Tests whether or not a given path matches the start of a given
55       * pattern up to the first "**".
56       * <p>
57       * This is not a general purpose test and should only be used if you
58       * can live with false positives. For example, <code>pattern=**\a</code>
59       * and <code>str=b</code> will yield <code>true</code>.
60       *
61       * @param pattern The pattern to match against. Must not be
62       *                <code>null</code>.
63       * @param str     The path to match, as a String. Must not be
64       *                <code>null</code>.
65       *
66       * @return whether or not a given path matches the start of a given
67       * pattern up to the first "**".
68       */
69      public static boolean matchPatternStart(String pattern, String str) {
70          return matchPatternStart(pattern, str, true);
71      }
72  
73      /**
74       * Tests whether or not a given path matches the start of a given
75       * pattern up to the first "**".
76       * <p>
77       * This is not a general purpose test and should only be used if you
78       * can live with false positives. For example, <code>pattern=**\a</code>
79       * and <code>str=b</code> will yield <code>true</code>.
80       *
81       * @param pattern The pattern to match against. Must not be
82       *                <code>null</code>.
83       * @param str     The path to match, as a String. Must not be
84       *                <code>null</code>.
85       * @param isCaseSensitive Whether or not matching should be performed
86       *                        case sensitively.
87       *
88       * @return whether or not a given path matches the start of a given
89       * pattern up to the first "**".
90       */
91      public static boolean matchPatternStart(String pattern, String str,
92                                              boolean isCaseSensitive) {
93          // When str starts with a File.separator, pattern has to start with a
94          // File.separator.
95          // When pattern starts with a File.separator, str has to start with a
96          // File.separator.
97          if (str.startsWith(File.separator)
98                  != pattern.startsWith(File.separator)) {
99              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