source: other-projects/trunk/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/types/selectors/SelectorUtils.java@ 14627

Last change on this file since 14627 was 14627, checked in by oranfry, 17 years ago

initial import of the gs3-release-maker

File size: 23.4 KB
Line 
1/*
2 * Copyright 2002-2004 The Apache Software Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18package org.apache.tools.ant.types.selectors;
19
20import java.io.File;
21import java.util.StringTokenizer;
22import java.util.Vector;
23
24import org.apache.tools.ant.types.Resource;
25
26/**
27 * <p>This is a utility class used by selectors and DirectoryScanner. The
28 * functionality more properly belongs just to selectors, but unfortunately
29 * DirectoryScanner exposed these as protected methods. Thus we have to
30 * support any subclasses of DirectoryScanner that may access these methods.
31 * </p>
32 * <p>This is a Singleton.</p>
33 *
34 * @since 1.5
35 */
36public final class SelectorUtils {
37
38 private static SelectorUtils instance = new SelectorUtils();
39
40 /**
41 * Private Constructor
42 */
43 private SelectorUtils() {
44 }
45
46 /**
47 * Retrieves the instance of the Singleton.
48 * @return singleton instance
49 */
50 public static SelectorUtils getInstance() {
51 return instance;
52 }
53
54 /**
55 * Tests whether or not a given path matches the start of a given
56 * pattern up to the first "**".
57 * <p>
58 * This is not a general purpose test and should only be used if you
59 * can live with false positives. For example, <code>pattern=**\a</code>
60 * and <code>str=b</code> will yield <code>true</code>.
61 *
62 * @param pattern The pattern to match against. Must not be
63 * <code>null</code>.
64 * @param str The path to match, as a String. Must not be
65 * <code>null</code>.
66 *
67 * @return whether or not a given path matches the start of a given
68 * pattern up to the first "**".
69 */
70 public static boolean matchPatternStart(String pattern, String str) {
71 return matchPatternStart(pattern, str, true);
72 }
73
74 /**
75 * Tests whether or not a given path matches the start of a given
76 * pattern up to the first "**".
77 * <p>
78 * This is not a general purpose test and should only be used if you
79 * can live with false positives. For example, <code>pattern=**\a</code>
80 * and <code>str=b</code> will yield <code>true</code>.
81 *
82 * @param pattern The pattern to match against. Must not be
83 * <code>null</code>.
84 * @param str The path to match, as a String. Must not be
85 * <code>null</code>.
86 * @param isCaseSensitive Whether or not matching should be performed
87 * case sensitively.
88 *
89 * @return whether or not a given path matches the start of a given
90 * pattern up to the first "**".
91 */
92 public static boolean matchPatternStart(String pattern, String str,
93 boolean isCaseSensitive) {
94 // When str starts with a File.separator, pattern has to start with a
95 // File.separator.
96 // When pattern starts with a File.separator, str has to start with a
97 // File.separator.
98 if (str.startsWith(File.separator)
99 != pattern.startsWith(File.separator)) {
100 return false;
101 }
102
103 String[] patDirs = tokenizePathAsArray(pattern);
104 String[] strDirs = tokenizePathAsArray(str);
105
106 int patIdxStart = 0;
107 int patIdxEnd = patDirs.length - 1;
108 int strIdxStart = 0;
109 int strIdxEnd = strDirs.length - 1;
110
111 // up to first '**'
112 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
113 String patDir = patDirs[patIdxStart];
114 if (patDir.equals("**")) {
115 break;
116 }
117 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
118 return false;
119 }
120 patIdxStart++;
121 strIdxStart++;
122 }
123
124 if (strIdxStart > strIdxEnd) {
125 // String is exhausted
126 return true;
127 } else if (patIdxStart > patIdxEnd) {
128 // String not exhausted, but pattern is. Failure.
129 return false;
130 } else {
131 // pattern now holds ** while string is not exhausted
132 // this will generate false positives but we can live with that.
133 return true;
134 }
135 }
136
137 /**
138 * Tests whether or not a given path matches a given pattern.
139 *
140 * @param pattern The pattern to match against. Must not be
141 * <code>null</code>.
142 * @param str The path to match, as a String. Must not be
143 * <code>null</code>.
144 *
145 * @return <code>true</code> if the pattern matches against the string,
146 * or <code>false</code> otherwise.
147 */
148 public static boolean matchPath(String pattern, String str) {
149 return matchPath(pattern, str, true);
150 }
151
152 /**
153 * Tests whether or not a given path matches a given pattern.
154 *
155 * @param pattern The pattern to match against. Must not be
156 * <code>null</code>.
157 * @param str The path to match, as a String. Must not be
158 * <code>null</code>.
159 * @param isCaseSensitive Whether or not matching should be performed
160 * case sensitively.
161 *
162 * @return <code>true</code> if the pattern matches against the string,
163 * or <code>false</code> otherwise.
164 */
165 public static boolean matchPath(String pattern, String str,
166 boolean isCaseSensitive) {
167 // When str starts with a File.separator, pattern has to start with a
168 // File.separator.
169 // When pattern starts with a File.separator, str has to start with a
170 // File.separator.
171 if (str.startsWith(File.separator)
172 != pattern.startsWith(File.separator)) {
173 return false;
174 }
175
176 String[] patDirs = tokenizePathAsArray(pattern);
177 String[] strDirs = tokenizePathAsArray(str);
178
179 int patIdxStart = 0;
180 int patIdxEnd = patDirs.length - 1;
181 int strIdxStart = 0;
182 int strIdxEnd = strDirs.length - 1;
183
184 // up to first '**'
185 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
186 String patDir = patDirs[patIdxStart];
187 if (patDir.equals("**")) {
188 break;
189 }
190 if (!match(patDir, strDirs[strIdxStart], isCaseSensitive)) {
191 patDirs = null;
192 strDirs = null;
193 return false;
194 }
195 patIdxStart++;
196 strIdxStart++;
197 }
198 if (strIdxStart > strIdxEnd) {
199 // String is exhausted
200 for (int i = patIdxStart; i <= patIdxEnd; i++) {
201 if (!patDirs[i].equals("**")) {
202 patDirs = null;
203 strDirs = null;
204 return false;
205 }
206 }
207 return true;
208 } else {
209 if (patIdxStart > patIdxEnd) {
210 // String not exhausted, but pattern is. Failure.
211 patDirs = null;
212 strDirs = null;
213 return false;
214 }
215 }
216
217 // up to last '**'
218 while (patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd) {
219 String patDir = patDirs[patIdxEnd];
220 if (patDir.equals("**")) {
221 break;
222 }
223 if (!match(patDir, strDirs[strIdxEnd], isCaseSensitive)) {
224 patDirs = null;
225 strDirs = null;
226 return false;
227 }
228 patIdxEnd--;
229 strIdxEnd--;
230 }
231 if (strIdxStart > strIdxEnd) {
232 // String is exhausted
233 for (int i = patIdxStart; i <= patIdxEnd; i++) {
234 if (!patDirs[i].equals("**")) {
235 patDirs = null;
236 strDirs = null;
237 return false;
238 }
239 }
240 return true;
241 }
242
243 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
244 int patIdxTmp = -1;
245 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
246 if (patDirs[i].equals("**")) {
247 patIdxTmp = i;
248 break;
249 }
250 }
251 if (patIdxTmp == patIdxStart + 1) {
252 // '**/**' situation, so skip one
253 patIdxStart++;
254 continue;
255 }
256 // Find the pattern between padIdxStart & padIdxTmp in str between
257 // strIdxStart & strIdxEnd
258 int patLength = (patIdxTmp - patIdxStart - 1);
259 int strLength = (strIdxEnd - strIdxStart + 1);
260 int foundIdx = -1;
261 strLoop:
262 for (int i = 0; i <= strLength - patLength; i++) {
263 for (int j = 0; j < patLength; j++) {
264 String subPat = patDirs[patIdxStart + j + 1];
265 String subStr = strDirs[strIdxStart + i + j];
266 if (!match(subPat, subStr, isCaseSensitive)) {
267 continue strLoop;
268 }
269 }
270
271 foundIdx = strIdxStart + i;
272 break;
273 }
274
275 if (foundIdx == -1) {
276 patDirs = null;
277 strDirs = null;
278 return false;
279 }
280
281 patIdxStart = patIdxTmp;
282 strIdxStart = foundIdx + patLength;
283 }
284
285 for (int i = patIdxStart; i <= patIdxEnd; i++) {
286 if (!patDirs[i].equals("**")) {
287 patDirs = null;
288 strDirs = null;
289 return false;
290 }
291 }
292
293 return true;
294 }
295
296 /**
297 * Tests whether or not a string matches against a pattern.
298 * The pattern may contain two special characters:<br>
299 * '*' means zero or more characters<br>
300 * '?' means one and only one character
301 *
302 * @param pattern The pattern to match against.
303 * Must not be <code>null</code>.
304 * @param str The string which must be matched against the pattern.
305 * Must not be <code>null</code>.
306 *
307 * @return <code>true</code> if the string matches against the pattern,
308 * or <code>false</code> otherwise.
309 */
310 public static boolean match(String pattern, String str) {
311 return match(pattern, str, true);
312 }
313
314 /**
315 * Tests whether or not a string matches against a pattern.
316 * The pattern may contain two special characters:<br>
317 * '*' means zero or more characters<br>
318 * '?' means one and only one character
319 *
320 * @param pattern The pattern to match against.
321 * Must not be <code>null</code>.
322 * @param str The string which must be matched against the pattern.
323 * Must not be <code>null</code>.
324 * @param isCaseSensitive Whether or not matching should be performed
325 * case sensitively.
326 *
327 *
328 * @return <code>true</code> if the string matches against the pattern,
329 * or <code>false</code> otherwise.
330 */
331 public static boolean match(String pattern, String str,
332 boolean isCaseSensitive) {
333 char[] patArr = pattern.toCharArray();
334 char[] strArr = str.toCharArray();
335 int patIdxStart = 0;
336 int patIdxEnd = patArr.length - 1;
337 int strIdxStart = 0;
338 int strIdxEnd = strArr.length - 1;
339 char ch;
340
341 boolean containsStar = false;
342 for (int i = 0; i < patArr.length; i++) {
343 if (patArr[i] == '*') {
344 containsStar = true;
345 break;
346 }
347 }
348
349 if (!containsStar) {
350 // No '*'s, so we make a shortcut
351 if (patIdxEnd != strIdxEnd) {
352 return false; // Pattern and string do not have the same size
353 }
354 for (int i = 0; i <= patIdxEnd; i++) {
355 ch = patArr[i];
356 if (ch != '?') {
357 if (isCaseSensitive && ch != strArr[i]) {
358 return false; // Character mismatch
359 }
360 if (!isCaseSensitive && Character.toUpperCase(ch)
361 != Character.toUpperCase(strArr[i])) {
362 return false; // Character mismatch
363 }
364 }
365 }
366 return true; // String matches against pattern
367 }
368
369 if (patIdxEnd == 0) {
370 return true; // Pattern contains only '*', which matches anything
371 }
372
373 // Process characters before first star
374 while ((ch = patArr[patIdxStart]) != '*' && strIdxStart <= strIdxEnd) {
375 if (ch != '?') {
376 if (isCaseSensitive && ch != strArr[strIdxStart]) {
377 return false; // Character mismatch
378 }
379 if (!isCaseSensitive && Character.toUpperCase(ch)
380 != Character.toUpperCase(strArr[strIdxStart])) {
381 return false; // Character mismatch
382 }
383 }
384 patIdxStart++;
385 strIdxStart++;
386 }
387 if (strIdxStart > strIdxEnd) {
388 // All characters in the string are used. Check if only '*'s are
389 // left in the pattern. If so, we succeeded. Otherwise failure.
390 for (int i = patIdxStart; i <= patIdxEnd; i++) {
391 if (patArr[i] != '*') {
392 return false;
393 }
394 }
395 return true;
396 }
397
398 // Process characters after last star
399 while ((ch = patArr[patIdxEnd]) != '*' && strIdxStart <= strIdxEnd) {
400 if (ch != '?') {
401 if (isCaseSensitive && ch != strArr[strIdxEnd]) {
402 return false; // Character mismatch
403 }
404 if (!isCaseSensitive && Character.toUpperCase(ch)
405 != Character.toUpperCase(strArr[strIdxEnd])) {
406 return false; // Character mismatch
407 }
408 }
409 patIdxEnd--;
410 strIdxEnd--;
411 }
412 if (strIdxStart > strIdxEnd) {
413 // All characters in the string are used. Check if only '*'s are
414 // left in the pattern. If so, we succeeded. Otherwise failure.
415 for (int i = patIdxStart; i <= patIdxEnd; i++) {
416 if (patArr[i] != '*') {
417 return false;
418 }
419 }
420 return true;
421 }
422
423 // process pattern between stars. padIdxStart and patIdxEnd point
424 // always to a '*'.
425 while (patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd) {
426 int patIdxTmp = -1;
427 for (int i = patIdxStart + 1; i <= patIdxEnd; i++) {
428 if (patArr[i] == '*') {
429 patIdxTmp = i;
430 break;
431 }
432 }
433 if (patIdxTmp == patIdxStart + 1) {
434 // Two stars next to each other, skip the first one.
435 patIdxStart++;
436 continue;
437 }
438 // Find the pattern between padIdxStart & padIdxTmp in str between
439 // strIdxStart & strIdxEnd
440 int patLength = (patIdxTmp - patIdxStart - 1);
441 int strLength = (strIdxEnd - strIdxStart + 1);
442 int foundIdx = -1;
443 strLoop:
444 for (int i = 0; i <= strLength - patLength; i++) {
445 for (int j = 0; j < patLength; j++) {
446 ch = patArr[patIdxStart + j + 1];
447 if (ch != '?') {
448 if (isCaseSensitive && ch != strArr[strIdxStart + i
449 + j]) {
450 continue strLoop;
451 }
452 if (!isCaseSensitive
453 && Character.toUpperCase(ch)
454 != Character.toUpperCase(strArr[strIdxStart + i + j])) {
455 continue strLoop;
456 }
457 }
458 }
459
460 foundIdx = strIdxStart + i;
461 break;
462 }
463
464 if (foundIdx == -1) {
465 return false;
466 }
467
468 patIdxStart = patIdxTmp;
469 strIdxStart = foundIdx + patLength;
470 }
471
472 // All characters in the string are used. Check if only '*'s are left
473 // in the pattern. If so, we succeeded. Otherwise failure.
474 for (int i = patIdxStart; i <= patIdxEnd; i++) {
475 if (patArr[i] != '*') {
476 return false;
477 }
478 }
479 return true;
480 }
481
482 /**
483 * Breaks a path up into a Vector of path elements, tokenizing on
484 * <code>File.separator</code>.
485 *
486 * @param path Path to tokenize. Must not be <code>null</code>.
487 *
488 * @return a Vector of path elements from the tokenized path
489 */
490 public static Vector tokenizePath (String path) {
491 return tokenizePath(path, File.separator);
492 }
493
494 /**
495 * Breaks a path up into a Vector of path elements, tokenizing on
496 *
497 * @param path Path to tokenize. Must not be <code>null</code>.
498 * @param separator the separator against which to tokenize.
499 *
500 * @return a Vector of path elements from the tokenized path
501 * @since Ant 1.6
502 */
503 public static Vector tokenizePath (String path, String separator) {
504 Vector ret = new Vector();
505 StringTokenizer st = new StringTokenizer(path, separator);
506 while (st.hasMoreTokens()) {
507 ret.addElement(st.nextToken());
508 }
509 return ret;
510 }
511
512 /**
513 * Same as {@link #tokenizePath tokenizePath} but hopefully faster.
514 */
515 private static String[] tokenizePathAsArray(String path) {
516 char sep = File.separatorChar;
517 int start = 0;
518 int len = path.length();
519 int count = 0;
520 for (int pos = 0; pos < len; pos++) {
521 if (path.charAt(pos) == sep) {
522 if (pos != start) {
523 count++;
524 }
525 start = pos + 1;
526 }
527 }
528 if (len != start) {
529 count++;
530 }
531 String[] l = new String[count];
532 count = 0;
533 start = 0;
534 for (int pos = 0; pos < len; pos++) {
535 if (path.charAt(pos) == sep) {
536 if (pos != start) {
537 String tok = path.substring(start, pos);
538 l[count++] = tok;
539 }
540 start = pos + 1;
541 }
542 }
543 if (len != start) {
544 String tok = path.substring(start);
545 l[count/*++*/] = tok;
546 }
547 return l;
548 }
549
550
551 /**
552 * Returns dependency information on these two files. If src has been
553 * modified later than target, it returns true. If target doesn't exist,
554 * it likewise returns true. Otherwise, target is newer than src and
555 * is not out of date, thus the method returns false. It also returns
556 * false if the src file doesn't even exist, since how could the
557 * target then be out of date.
558 *
559 * @param src the original file
560 * @param target the file being compared against
561 * @param granularity the amount in seconds of slack we will give in
562 * determining out of dateness
563 * @return whether the target is out of date
564 */
565 public static boolean isOutOfDate(File src, File target, int granularity) {
566 if (!src.exists()) {
567 return false;
568 }
569 if (!target.exists()) {
570 return true;
571 }
572 if ((src.lastModified() - granularity) > target.lastModified()) {
573 return true;
574 }
575 return false;
576 }
577
578 /**
579 * Returns dependency information on these two resources. If src has been
580 * modified later than target, it returns true. If target doesn't exist,
581 * it likewise returns true. Otherwise, target is newer than src and
582 * is not out of date, thus the method returns false. It also returns
583 * false if the src file doesn't even exist, since how could the
584 * target then be out of date.
585 *
586 * @param src the original resource
587 * @param target the resource being compared against
588 * @param granularity the amount in seconds of slack we will give in
589 * determining out of dateness
590 * @return whether the target is out of date
591 */
592 public static boolean isOutOfDate(Resource src, Resource target,
593 int granularity) {
594 if (!src.isExists()) {
595 return false;
596 }
597 if (!target.isExists()) {
598 return true;
599 }
600 if ((src.getLastModified() - granularity) > target.getLastModified()) {
601 return true;
602 }
603 return false;
604 }
605
606 /**
607 * "Flattens" a string by removing all whitespace (space, tab, linefeed,
608 * carriage return, and formfeed). This uses StringTokenizer and the
609 * default set of tokens as documented in the single arguement constructor.
610 *
611 * @param input a String to remove all whitespace.
612 * @return a String that has had all whitespace removed.
613 */
614 public static String removeWhitespace(String input) {
615 StringBuffer result = new StringBuffer();
616 if (input != null) {
617 StringTokenizer st = new StringTokenizer(input);
618 while (st.hasMoreTokens()) {
619 result.append(st.nextToken());
620 }
621 }
622 return result.toString();
623 }
624
625 /**
626 * Tests if a string contains stars or question marks
627 * @param input a String which one wants to test for containing wildcard
628 * @return true if the string contains at least a star or a question mark
629 */
630 public static boolean hasWildcards(String input) {
631 return (input.indexOf('*') != -1 || input.indexOf('?') != -1);
632 }
633
634 /**
635 * removes from a pattern all tokens to the right containing wildcards
636 * @param input the input string
637 * @return the leftmost part of the pattern without wildcards
638 */
639 public static String rtrimWildcardTokens(String input) {
640 Vector v = tokenizePath(input, File.separator);
641 StringBuffer sb = new StringBuffer();
642 for (int counter = 0; counter < v.size(); counter++) {
643 if (hasWildcards((String) v.elementAt(counter))) {
644 break;
645 }
646 if (counter > 0) {
647 sb.append(File.separator);
648 }
649 sb.append((String) v.elementAt(counter));
650 }
651 return sb.toString();
652 }
653}
654
Note: See TracBrowser for help on using the repository browser.