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