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
94
95
96
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
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
125 return true;
126 } else if (patIdxStart > patIdxEnd) {
127
128 return false;
129 } else {
130
131
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
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
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
201 patDirs = null;
202 strDirs = null;
203 return false;
204 }
205 }
206
207
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
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
341 if (patIdxEnd != strIdxEnd) {
342 return false;
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;
349 }
350 if (!isCaseSensitive && Character.toUpperCase(ch)
351 != Character.toUpperCase(strArr[i])) {
352 return false;
353 }
354 }
355 }
356 return true;
357 }
358
359 if (patIdxEnd == 0) {
360 return true;
361 }
362
363
364 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
365 if (ch != '?') {
366 if (isCaseSensitive && ch != strArr[strIdxStart]) {
367 return false;
368 }
369 if (!isCaseSensitive && Character.toUpperCase(ch)
370 != Character.toUpperCase(strArr[strIdxStart])) {
371 return false;
372 }
373 }
374 patIdxStart++;
375 strIdxStart++;
376 }
377 if (strIdxStart > strIdxEnd) {
378
379
380 for (int i = patIdxStart; i <= patIdxEnd; i++) {
381 if (patArr[i] != '*') {
382 return false;
383 }
384 }
385 return true;
386 }
387
388
389 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
390 if (ch != '?') {
391 if (isCaseSensitive && ch != strArr[strIdxEnd]) {
392 return false;
393 }
394 if (!isCaseSensitive && Character.toUpperCase(ch)
395 != Character.toUpperCase(strArr[strIdxEnd])) {
396 return false;
397 }
398 }
399 patIdxEnd--;
400 strIdxEnd--;
401 }
402 if (strIdxStart > strIdxEnd) {
403
404
405 for (int i = patIdxStart; i <= patIdxEnd; i++) {
406 if (patArr[i] != '*') {
407 return false;
408 }
409 }
410 return true;
411 }
412
413
414
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
425 patIdxStart++;
426 continue;
427 }
428
429
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
463
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
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
541 next = (ca[next] == sep) ? next + 1 : next;
542
543 StringBuffer sbPath = new StringBuffer();
544
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
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
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