OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / packages / apps / Email / src / org / apache / commons / io / FilenameUtils.java
1 /*\r
2  * Licensed to the Apache Software Foundation (ASF) under one or more\r
3  * contributor license agreements.  See the NOTICE file distributed with\r
4  * this work for additional information regarding copyright ownership.\r
5  * The ASF licenses this file to You under the Apache License, Version 2.0\r
6  * (the "License"); you may not use this file except in compliance with\r
7  * the License.  You may obtain a copy of the License at\r
8  * \r
9  *      http://www.apache.org/licenses/LICENSE-2.0\r
10  * \r
11  * Unless required by applicable law or agreed to in writing, software\r
12  * distributed under the License is distributed on an "AS IS" BASIS,\r
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
14  * See the License for the specific language governing permissions and\r
15  * limitations under the License.\r
16  */\r
17 package org.apache.commons.io;\r
18 \r
19 import java.io.File;\r
20 import java.util.ArrayList;\r
21 import java.util.Collection;\r
22 import java.util.Iterator;\r
23 import java.util.Stack;\r
24 \r
25 /**\r
26  * General filename and filepath manipulation utilities.\r
27  * <p>\r
28  * When dealing with filenames you can hit problems when moving from a Windows\r
29  * based development machine to a Unix based production machine.\r
30  * This class aims to help avoid those problems.\r
31  * <p>\r
32  * <b>NOTE</b>: You may be able to avoid using this class entirely simply by\r
33  * using JDK {@link java.io.File File} objects and the two argument constructor\r
34  * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.\r
35  * <p>\r
36  * Most methods on this class are designed to work the same on both Unix and Windows.\r
37  * Those that don't include 'System', 'Unix' or 'Windows' in their name.\r
38  * <p>\r
39  * Most methods recognise both separators (forward and back), and both\r
40  * sets of prefixes. See the javadoc of each method for details.\r
41  * <p>\r
42  * This class defines six components within a filename\r
43  * (example C:\dev\project\file.txt):\r
44  * <ul>\r
45  * <li>the prefix - C:\</li>\r
46  * <li>the path - dev\project\</li>\r
47  * <li>the full path - C:\dev\project\</li>\r
48  * <li>the name - file.txt</li>\r
49  * <li>the base name - file</li>\r
50  * <li>the extension - txt</li>\r
51  * </ul>\r
52  * Note that this class works best if directory filenames end with a separator.\r
53  * If you omit the last separator, it is impossible to determine if the filename\r
54  * corresponds to a file or a directory. As a result, we have chosen to say\r
55  * it corresponds to a file.\r
56  * <p>\r
57  * This class only supports Unix and Windows style names.\r
58  * Prefixes are matched as follows:\r
59  * <pre>\r
60  * Windows:\r
61  * a\b\c.txt           --> ""          --> relative\r
62  * \a\b\c.txt          --> "\"         --> current drive absolute\r
63  * C:a\b\c.txt         --> "C:"        --> drive relative\r
64  * C:\a\b\c.txt        --> "C:\"       --> absolute\r
65  * \\server\a\b\c.txt  --> "\\server\" --> UNC\r
66  *\r
67  * Unix:\r
68  * a/b/c.txt           --> ""          --> relative\r
69  * /a/b/c.txt          --> "/"         --> absolute\r
70  * ~/a/b/c.txt         --> "~/"        --> current user\r
71  * ~                   --> "~/"        --> current user (slash added)\r
72  * ~user/a/b/c.txt     --> "~user/"    --> named user\r
73  * ~user               --> "~user/"    --> named user (slash added)\r
74  * </pre>\r
75  * Both prefix styles are matched always, irrespective of the machine that you are\r
76  * currently running on.\r
77  * <p>\r
78  * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.\r
79  *\r
80  * @author <a href="mailto:burton@relativity.yi.org">Kevin A. Burton</A>\r
81  * @author <a href="mailto:sanders@apache.org">Scott Sanders</a>\r
82  * @author <a href="mailto:dlr@finemaltcoding.com">Daniel Rall</a>\r
83  * @author <a href="mailto:Christoph.Reck@dlr.de">Christoph.Reck</a>\r
84  * @author <a href="mailto:peter@apache.org">Peter Donald</a>\r
85  * @author <a href="mailto:jefft@apache.org">Jeff Turner</a>\r
86  * @author Matthew Hawthorne\r
87  * @author Martin Cooper\r
88  * @author <a href="mailto:jeremias@apache.org">Jeremias Maerki</a>\r
89  * @author Stephen Colebourne\r
90  * @version $Id: FilenameUtils.java 609870 2008-01-08 04:46:26Z niallp $\r
91  * @since Commons IO 1.1\r
92  */\r
93 public class FilenameUtils {\r
94 \r
95     /**\r
96      * The extension separator character.\r
97      * @since Commons IO 1.4\r
98      */\r
99     public static final char EXTENSION_SEPARATOR = '.';\r
100 \r
101     /**\r
102      * The extension separator String.\r
103      * @since Commons IO 1.4\r
104      */\r
105     public static final String EXTENSION_SEPARATOR_STR = (new Character(EXTENSION_SEPARATOR)).toString();\r
106 \r
107     /**\r
108      * The Unix separator character.\r
109      */\r
110     private static final char UNIX_SEPARATOR = '/';\r
111 \r
112     /**\r
113      * The Windows separator character.\r
114      */\r
115     private static final char WINDOWS_SEPARATOR = '\\';\r
116 \r
117     /**\r
118      * The system separator character.\r
119      */\r
120     private static final char SYSTEM_SEPARATOR = File.separatorChar;\r
121 \r
122     /**\r
123      * The separator character that is the opposite of the system separator.\r
124      */\r
125     private static final char OTHER_SEPARATOR;\r
126     static {\r
127         if (isSystemWindows()) {\r
128             OTHER_SEPARATOR = UNIX_SEPARATOR;\r
129         } else {\r
130             OTHER_SEPARATOR = WINDOWS_SEPARATOR;\r
131         }\r
132     }\r
133 \r
134     /**\r
135      * Instances should NOT be constructed in standard programming.\r
136      */\r
137     public FilenameUtils() {\r
138         super();\r
139     }\r
140 \r
141     //-----------------------------------------------------------------------\r
142     /**\r
143      * Determines if Windows file system is in use.\r
144      * \r
145      * @return true if the system is Windows\r
146      */\r
147     static boolean isSystemWindows() {\r
148         return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;\r
149     }\r
150 \r
151     //-----------------------------------------------------------------------\r
152     /**\r
153      * Checks if the character is a separator.\r
154      * \r
155      * @param ch  the character to check\r
156      * @return true if it is a separator character\r
157      */\r
158     private static boolean isSeparator(char ch) {\r
159         return (ch == UNIX_SEPARATOR) || (ch == WINDOWS_SEPARATOR);\r
160     }\r
161 \r
162     //-----------------------------------------------------------------------\r
163     /**\r
164      * Normalizes a path, removing double and single dot path steps.\r
165      * <p>\r
166      * This method normalizes a path to a standard format.\r
167      * The input may contain separators in either Unix or Windows format.\r
168      * The output will contain separators in the format of the system.\r
169      * <p>\r
170      * A trailing slash will be retained.\r
171      * A double slash will be merged to a single slash (but UNC names are handled).\r
172      * A single dot path segment will be removed.\r
173      * A double dot will cause that path segment and the one before to be removed.\r
174      * If the double dot has no parent path segment to work with, <code>null</code>\r
175      * is returned.\r
176      * <p>\r
177      * The output will be the same on both Unix and Windows except\r
178      * for the separator character.\r
179      * <pre>\r
180      * /foo//               -->   /foo/\r
181      * /foo/./              -->   /foo/\r
182      * /foo/../bar          -->   /bar\r
183      * /foo/../bar/         -->   /bar/\r
184      * /foo/../bar/../baz   -->   /baz\r
185      * //foo//./bar         -->   /foo/bar\r
186      * /../                 -->   null\r
187      * ../foo               -->   null\r
188      * foo/bar/..           -->   foo/\r
189      * foo/../../bar        -->   null\r
190      * foo/../bar           -->   bar\r
191      * //server/foo/../bar  -->   //server/bar\r
192      * //server/../bar      -->   null\r
193      * C:\foo\..\bar        -->   C:\bar\r
194      * C:\..\bar            -->   null\r
195      * ~/foo/../bar/        -->   ~/bar/\r
196      * ~/../bar             -->   null\r
197      * </pre>\r
198      * (Note the file separator returned will be correct for Windows/Unix)\r
199      *\r
200      * @param filename  the filename to normalize, null returns null\r
201      * @return the normalized filename, or null if invalid\r
202      */\r
203     public static String normalize(String filename) {\r
204         return doNormalize(filename, true);\r
205     }\r
206 \r
207     //-----------------------------------------------------------------------\r
208     /**\r
209      * Normalizes a path, removing double and single dot path steps,\r
210      * and removing any final directory separator.\r
211      * <p>\r
212      * This method normalizes a path to a standard format.\r
213      * The input may contain separators in either Unix or Windows format.\r
214      * The output will contain separators in the format of the system.\r
215      * <p>\r
216      * A trailing slash will be removed.\r
217      * A double slash will be merged to a single slash (but UNC names are handled).\r
218      * A single dot path segment will be removed.\r
219      * A double dot will cause that path segment and the one before to be removed.\r
220      * If the double dot has no parent path segment to work with, <code>null</code>\r
221      * is returned.\r
222      * <p>\r
223      * The output will be the same on both Unix and Windows except\r
224      * for the separator character.\r
225      * <pre>\r
226      * /foo//               -->   /foo\r
227      * /foo/./              -->   /foo\r
228      * /foo/../bar          -->   /bar\r
229      * /foo/../bar/         -->   /bar\r
230      * /foo/../bar/../baz   -->   /baz\r
231      * //foo//./bar         -->   /foo/bar\r
232      * /../                 -->   null\r
233      * ../foo               -->   null\r
234      * foo/bar/..           -->   foo\r
235      * foo/../../bar        -->   null\r
236      * foo/../bar           -->   bar\r
237      * //server/foo/../bar  -->   //server/bar\r
238      * //server/../bar      -->   null\r
239      * C:\foo\..\bar        -->   C:\bar\r
240      * C:\..\bar            -->   null\r
241      * ~/foo/../bar/        -->   ~/bar\r
242      * ~/../bar             -->   null\r
243      * </pre>\r
244      * (Note the file separator returned will be correct for Windows/Unix)\r
245      *\r
246      * @param filename  the filename to normalize, null returns null\r
247      * @return the normalized filename, or null if invalid\r
248      */\r
249     public static String normalizeNoEndSeparator(String filename) {\r
250         return doNormalize(filename, false);\r
251     }\r
252 \r
253     /**\r
254      * Internal method to perform the normalization.\r
255      *\r
256      * @param filename  the filename\r
257      * @param keepSeparator  true to keep the final separator\r
258      * @return the normalized filename\r
259      */\r
260     private static String doNormalize(String filename, boolean keepSeparator) {\r
261         if (filename == null) {\r
262             return null;\r
263         }\r
264         int size = filename.length();\r
265         if (size == 0) {\r
266             return filename;\r
267         }\r
268         int prefix = getPrefixLength(filename);\r
269         if (prefix < 0) {\r
270             return null;\r
271         }\r
272         \r
273         char[] array = new char[size + 2];  // +1 for possible extra slash, +2 for arraycopy\r
274         filename.getChars(0, filename.length(), array, 0);\r
275         \r
276         // fix separators throughout\r
277         for (int i = 0; i < array.length; i++) {\r
278             if (array[i] == OTHER_SEPARATOR) {\r
279                 array[i] = SYSTEM_SEPARATOR;\r
280             }\r
281         }\r
282         \r
283         // add extra separator on the end to simplify code below\r
284         boolean lastIsDirectory = true;\r
285         if (array[size - 1] != SYSTEM_SEPARATOR) {\r
286             array[size++] = SYSTEM_SEPARATOR;\r
287             lastIsDirectory = false;\r
288         }\r
289         \r
290         // adjoining slashes\r
291         for (int i = prefix + 1; i < size; i++) {\r
292             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == SYSTEM_SEPARATOR) {\r
293                 System.arraycopy(array, i, array, i - 1, size - i);\r
294                 size--;\r
295                 i--;\r
296             }\r
297         }\r
298         \r
299         // dot slash\r
300         for (int i = prefix + 1; i < size; i++) {\r
301             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' &&\r
302                     (i == prefix + 1 || array[i - 2] == SYSTEM_SEPARATOR)) {\r
303                 if (i == size - 1) {\r
304                     lastIsDirectory = true;\r
305                 }\r
306                 System.arraycopy(array, i + 1, array, i - 1, size - i);\r
307                 size -=2;\r
308                 i--;\r
309             }\r
310         }\r
311         \r
312         // double dot slash\r
313         outer:\r
314         for (int i = prefix + 2; i < size; i++) {\r
315             if (array[i] == SYSTEM_SEPARATOR && array[i - 1] == '.' && array[i - 2] == '.' &&\r
316                     (i == prefix + 2 || array[i - 3] == SYSTEM_SEPARATOR)) {\r
317                 if (i == prefix + 2) {\r
318                     return null;\r
319                 }\r
320                 if (i == size - 1) {\r
321                     lastIsDirectory = true;\r
322                 }\r
323                 int j;\r
324                 for (j = i - 4 ; j >= prefix; j--) {\r
325                     if (array[j] == SYSTEM_SEPARATOR) {\r
326                         // remove b/../ from a/b/../c\r
327                         System.arraycopy(array, i + 1, array, j + 1, size - i);\r
328                         size -= (i - j);\r
329                         i = j + 1;\r
330                         continue outer;\r
331                     }\r
332                 }\r
333                 // remove a/../ from a/../c\r
334                 System.arraycopy(array, i + 1, array, prefix, size - i);\r
335                 size -= (i + 1 - prefix);\r
336                 i = prefix + 1;\r
337             }\r
338         }\r
339         \r
340         if (size <= 0) {  // should never be less than 0\r
341             return "";\r
342         }\r
343         if (size <= prefix) {  // should never be less than prefix\r
344             return new String(array, 0, size);\r
345         }\r
346         if (lastIsDirectory && keepSeparator) {\r
347             return new String(array, 0, size);  // keep trailing separator\r
348         }\r
349         return new String(array, 0, size - 1);  // lose trailing separator\r
350     }\r
351 \r
352     //-----------------------------------------------------------------------\r
353     /**\r
354      * Concatenates a filename to a base path using normal command line style rules.\r
355      * <p>\r
356      * The effect is equivalent to resultant directory after changing\r
357      * directory to the first argument, followed by changing directory to\r
358      * the second argument.\r
359      * <p>\r
360      * The first argument is the base path, the second is the path to concatenate.\r
361      * The returned path is always normalized via {@link #normalize(String)},\r
362      * thus <code>..</code> is handled.\r
363      * <p>\r
364      * If <code>pathToAdd</code> is absolute (has an absolute prefix), then\r
365      * it will be normalized and returned.\r
366      * Otherwise, the paths will be joined, normalized and returned.\r
367      * <p>\r
368      * The output will be the same on both Unix and Windows except\r
369      * for the separator character.\r
370      * <pre>\r
371      * /foo/ + bar          -->   /foo/bar\r
372      * /foo + bar           -->   /foo/bar\r
373      * /foo + /bar          -->   /bar\r
374      * /foo + C:/bar        -->   C:/bar\r
375      * /foo + C:bar         -->   C:bar (*)\r
376      * /foo/a/ + ../bar     -->   foo/bar\r
377      * /foo/ + ../../bar    -->   null\r
378      * /foo/ + /bar         -->   /bar\r
379      * /foo/.. + /bar       -->   /bar\r
380      * /foo + bar/c.txt     -->   /foo/bar/c.txt\r
381      * /foo/c.txt + bar     -->   /foo/c.txt/bar (!)\r
382      * </pre>\r
383      * (*) Note that the Windows relative drive prefix is unreliable when\r
384      * used with this method.\r
385      * (!) Note that the first parameter must be a path. If it ends with a name, then\r
386      * the name will be built into the concatenated path. If this might be a problem,\r
387      * use {@link #getFullPath(String)} on the base path argument.\r
388      *\r
389      * @param basePath  the base path to attach to, always treated as a path\r
390      * @param fullFilenameToAdd  the filename (or path) to attach to the base\r
391      * @return the concatenated path, or null if invalid\r
392      */\r
393     public static String concat(String basePath, String fullFilenameToAdd) {\r
394         int prefix = getPrefixLength(fullFilenameToAdd);\r
395         if (prefix < 0) {\r
396             return null;\r
397         }\r
398         if (prefix > 0) {\r
399             return normalize(fullFilenameToAdd);\r
400         }\r
401         if (basePath == null) {\r
402             return null;\r
403         }\r
404         int len = basePath.length();\r
405         if (len == 0) {\r
406             return normalize(fullFilenameToAdd);\r
407         }\r
408         char ch = basePath.charAt(len - 1);\r
409         if (isSeparator(ch)) {\r
410             return normalize(basePath + fullFilenameToAdd);\r
411         } else {\r
412             return normalize(basePath + '/' + fullFilenameToAdd);\r
413         }\r
414     }\r
415 \r
416     //-----------------------------------------------------------------------\r
417     /**\r
418      * Converts all separators to the Unix separator of forward slash.\r
419      * \r
420      * @param path  the path to be changed, null ignored\r
421      * @return the updated path\r
422      */\r
423     public static String separatorsToUnix(String path) {\r
424         if (path == null || path.indexOf(WINDOWS_SEPARATOR) == -1) {\r
425             return path;\r
426         }\r
427         return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);\r
428     }\r
429 \r
430     /**\r
431      * Converts all separators to the Windows separator of backslash.\r
432      * \r
433      * @param path  the path to be changed, null ignored\r
434      * @return the updated path\r
435      */\r
436     public static String separatorsToWindows(String path) {\r
437         if (path == null || path.indexOf(UNIX_SEPARATOR) == -1) {\r
438             return path;\r
439         }\r
440         return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);\r
441     }\r
442 \r
443     /**\r
444      * Converts all separators to the system separator.\r
445      * \r
446      * @param path  the path to be changed, null ignored\r
447      * @return the updated path\r
448      */\r
449     public static String separatorsToSystem(String path) {\r
450         if (path == null) {\r
451             return null;\r
452         }\r
453         if (isSystemWindows()) {\r
454             return separatorsToWindows(path);\r
455         } else {\r
456             return separatorsToUnix(path);\r
457         }\r
458     }\r
459 \r
460     //-----------------------------------------------------------------------\r
461     /**\r
462      * Returns the length of the filename prefix, such as <code>C:/</code> or <code>~/</code>.\r
463      * <p>\r
464      * This method will handle a file in either Unix or Windows format.\r
465      * <p>\r
466      * The prefix length includes the first slash in the full filename\r
467      * if applicable. Thus, it is possible that the length returned is greater\r
468      * than the length of the input string.\r
469      * <pre>\r
470      * Windows:\r
471      * a\b\c.txt           --> ""          --> relative\r
472      * \a\b\c.txt          --> "\"         --> current drive absolute\r
473      * C:a\b\c.txt         --> "C:"        --> drive relative\r
474      * C:\a\b\c.txt        --> "C:\"       --> absolute\r
475      * \\server\a\b\c.txt  --> "\\server\" --> UNC\r
476      *\r
477      * Unix:\r
478      * a/b/c.txt           --> ""          --> relative\r
479      * /a/b/c.txt          --> "/"         --> absolute\r
480      * ~/a/b/c.txt         --> "~/"        --> current user\r
481      * ~                   --> "~/"        --> current user (slash added)\r
482      * ~user/a/b/c.txt     --> "~user/"    --> named user\r
483      * ~user               --> "~user/"    --> named user (slash added)\r
484      * </pre>\r
485      * <p>\r
486      * The output will be the same irrespective of the machine that the code is running on.\r
487      * ie. both Unix and Windows prefixes are matched regardless.\r
488      *\r
489      * @param filename  the filename to find the prefix in, null returns -1\r
490      * @return the length of the prefix, -1 if invalid or null\r
491      */\r
492     public static int getPrefixLength(String filename) {\r
493         if (filename == null) {\r
494             return -1;\r
495         }\r
496         int len = filename.length();\r
497         if (len == 0) {\r
498             return 0;\r
499         }\r
500         char ch0 = filename.charAt(0);\r
501         if (ch0 == ':') {\r
502             return -1;\r
503         }\r
504         if (len == 1) {\r
505             if (ch0 == '~') {\r
506                 return 2;  // return a length greater than the input\r
507             }\r
508             return (isSeparator(ch0) ? 1 : 0);\r
509         } else {\r
510             if (ch0 == '~') {\r
511                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 1);\r
512                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 1);\r
513                 if (posUnix == -1 && posWin == -1) {\r
514                     return len + 1;  // return a length greater than the input\r
515                 }\r
516                 posUnix = (posUnix == -1 ? posWin : posUnix);\r
517                 posWin = (posWin == -1 ? posUnix : posWin);\r
518                 return Math.min(posUnix, posWin) + 1;\r
519             }\r
520             char ch1 = filename.charAt(1);\r
521             if (ch1 == ':') {\r
522                 ch0 = Character.toUpperCase(ch0);\r
523                 if (ch0 >= 'A' && ch0 <= 'Z') {\r
524                     if (len == 2 || isSeparator(filename.charAt(2)) == false) {\r
525                         return 2;\r
526                     }\r
527                     return 3;\r
528                 }\r
529                 return -1;\r
530                 \r
531             } else if (isSeparator(ch0) && isSeparator(ch1)) {\r
532                 int posUnix = filename.indexOf(UNIX_SEPARATOR, 2);\r
533                 int posWin = filename.indexOf(WINDOWS_SEPARATOR, 2);\r
534                 if ((posUnix == -1 && posWin == -1) || posUnix == 2 || posWin == 2) {\r
535                     return -1;\r
536                 }\r
537                 posUnix = (posUnix == -1 ? posWin : posUnix);\r
538                 posWin = (posWin == -1 ? posUnix : posWin);\r
539                 return Math.min(posUnix, posWin) + 1;\r
540             } else {\r
541                 return (isSeparator(ch0) ? 1 : 0);\r
542             }\r
543         }\r
544     }\r
545 \r
546     /**\r
547      * Returns the index of the last directory separator character.\r
548      * <p>\r
549      * This method will handle a file in either Unix or Windows format.\r
550      * The position of the last forward or backslash is returned.\r
551      * <p>\r
552      * The output will be the same irrespective of the machine that the code is running on.\r
553      * \r
554      * @param filename  the filename to find the last path separator in, null returns -1\r
555      * @return the index of the last separator character, or -1 if there\r
556      * is no such character\r
557      */\r
558     public static int indexOfLastSeparator(String filename) {\r
559         if (filename == null) {\r
560             return -1;\r
561         }\r
562         int lastUnixPos = filename.lastIndexOf(UNIX_SEPARATOR);\r
563         int lastWindowsPos = filename.lastIndexOf(WINDOWS_SEPARATOR);\r
564         return Math.max(lastUnixPos, lastWindowsPos);\r
565     }\r
566 \r
567     /**\r
568      * Returns the index of the last extension separator character, which is a dot.\r
569      * <p>\r
570      * This method also checks that there is no directory separator after the last dot.\r
571      * To do this it uses {@link #indexOfLastSeparator(String)} which will\r
572      * handle a file in either Unix or Windows format.\r
573      * <p>\r
574      * The output will be the same irrespective of the machine that the code is running on.\r
575      * \r
576      * @param filename  the filename to find the last path separator in, null returns -1\r
577      * @return the index of the last separator character, or -1 if there\r
578      * is no such character\r
579      */\r
580     public static int indexOfExtension(String filename) {\r
581         if (filename == null) {\r
582             return -1;\r
583         }\r
584         int extensionPos = filename.lastIndexOf(EXTENSION_SEPARATOR);\r
585         int lastSeparator = indexOfLastSeparator(filename);\r
586         return (lastSeparator > extensionPos ? -1 : extensionPos);\r
587     }\r
588 \r
589     //-----------------------------------------------------------------------\r
590     /**\r
591      * Gets the prefix from a full filename, such as <code>C:/</code>\r
592      * or <code>~/</code>.\r
593      * <p>\r
594      * This method will handle a file in either Unix or Windows format.\r
595      * The prefix includes the first slash in the full filename where applicable.\r
596      * <pre>\r
597      * Windows:\r
598      * a\b\c.txt           --> ""          --> relative\r
599      * \a\b\c.txt          --> "\"         --> current drive absolute\r
600      * C:a\b\c.txt         --> "C:"        --> drive relative\r
601      * C:\a\b\c.txt        --> "C:\"       --> absolute\r
602      * \\server\a\b\c.txt  --> "\\server\" --> UNC\r
603      *\r
604      * Unix:\r
605      * a/b/c.txt           --> ""          --> relative\r
606      * /a/b/c.txt          --> "/"         --> absolute\r
607      * ~/a/b/c.txt         --> "~/"        --> current user\r
608      * ~                   --> "~/"        --> current user (slash added)\r
609      * ~user/a/b/c.txt     --> "~user/"    --> named user\r
610      * ~user               --> "~user/"    --> named user (slash added)\r
611      * </pre>\r
612      * <p>\r
613      * The output will be the same irrespective of the machine that the code is running on.\r
614      * ie. both Unix and Windows prefixes are matched regardless.\r
615      *\r
616      * @param filename  the filename to query, null returns null\r
617      * @return the prefix of the file, null if invalid\r
618      */\r
619     public static String getPrefix(String filename) {\r
620         if (filename == null) {\r
621             return null;\r
622         }\r
623         int len = getPrefixLength(filename);\r
624         if (len < 0) {\r
625             return null;\r
626         }\r
627         if (len > filename.length()) {\r
628             return filename + UNIX_SEPARATOR;  // we know this only happens for unix\r
629         }\r
630         return filename.substring(0, len);\r
631     }\r
632 \r
633     /**\r
634      * Gets the path from a full filename, which excludes the prefix.\r
635      * <p>\r
636      * This method will handle a file in either Unix or Windows format.\r
637      * The method is entirely text based, and returns the text before and\r
638      * including the last forward or backslash.\r
639      * <pre>\r
640      * C:\a\b\c.txt --> a\b\\r
641      * ~/a/b/c.txt  --> a/b/\r
642      * a.txt        --> ""\r
643      * a/b/c        --> a/b/\r
644      * a/b/c/       --> a/b/c/\r
645      * </pre>\r
646      * <p>\r
647      * The output will be the same irrespective of the machine that the code is running on.\r
648      * <p>\r
649      * This method drops the prefix from the result.\r
650      * See {@link #getFullPath(String)} for the method that retains the prefix.\r
651      *\r
652      * @param filename  the filename to query, null returns null\r
653      * @return the path of the file, an empty string if none exists, null if invalid\r
654      */\r
655     public static String getPath(String filename) {\r
656         return doGetPath(filename, 1);\r
657     }\r
658 \r
659     /**\r
660      * Gets the path from a full filename, which excludes the prefix, and\r
661      * also excluding the final directory separator.\r
662      * <p>\r
663      * This method will handle a file in either Unix or Windows format.\r
664      * The method is entirely text based, and returns the text before the\r
665      * last forward or backslash.\r
666      * <pre>\r
667      * C:\a\b\c.txt --> a\b\r
668      * ~/a/b/c.txt  --> a/b\r
669      * a.txt        --> ""\r
670      * a/b/c        --> a/b\r
671      * a/b/c/       --> a/b/c\r
672      * </pre>\r
673      * <p>\r
674      * The output will be the same irrespective of the machine that the code is running on.\r
675      * <p>\r
676      * This method drops the prefix from the result.\r
677      * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix.\r
678      *\r
679      * @param filename  the filename to query, null returns null\r
680      * @return the path of the file, an empty string if none exists, null if invalid\r
681      */\r
682     public static String getPathNoEndSeparator(String filename) {\r
683         return doGetPath(filename, 0);\r
684     }\r
685 \r
686     /**\r
687      * Does the work of getting the path.\r
688      * \r
689      * @param filename  the filename\r
690      * @param separatorAdd  0 to omit the end separator, 1 to return it\r
691      * @return the path\r
692      */\r
693     private static String doGetPath(String filename, int separatorAdd) {\r
694         if (filename == null) {\r
695             return null;\r
696         }\r
697         int prefix = getPrefixLength(filename);\r
698         if (prefix < 0) {\r
699             return null;\r
700         }\r
701         int index = indexOfLastSeparator(filename);\r
702         if (prefix >= filename.length() || index < 0) {\r
703             return "";\r
704         }\r
705         return filename.substring(prefix, index + separatorAdd);\r
706     }\r
707 \r
708     /**\r
709      * Gets the full path from a full filename, which is the prefix + path.\r
710      * <p>\r
711      * This method will handle a file in either Unix or Windows format.\r
712      * The method is entirely text based, and returns the text before and\r
713      * including the last forward or backslash.\r
714      * <pre>\r
715      * C:\a\b\c.txt --> C:\a\b\\r
716      * ~/a/b/c.txt  --> ~/a/b/\r
717      * a.txt        --> ""\r
718      * a/b/c        --> a/b/\r
719      * a/b/c/       --> a/b/c/\r
720      * C:           --> C:\r
721      * C:\          --> C:\\r
722      * ~            --> ~/\r
723      * ~/           --> ~/\r
724      * ~user        --> ~user/\r
725      * ~user/       --> ~user/\r
726      * </pre>\r
727      * <p>\r
728      * The output will be the same irrespective of the machine that the code is running on.\r
729      *\r
730      * @param filename  the filename to query, null returns null\r
731      * @return the path of the file, an empty string if none exists, null if invalid\r
732      */\r
733     public static String getFullPath(String filename) {\r
734         return doGetFullPath(filename, true);\r
735     }\r
736 \r
737     /**\r
738      * Gets the full path from a full filename, which is the prefix + path,\r
739      * and also excluding the final directory separator.\r
740      * <p>\r
741      * This method will handle a file in either Unix or Windows format.\r
742      * The method is entirely text based, and returns the text before the\r
743      * last forward or backslash.\r
744      * <pre>\r
745      * C:\a\b\c.txt --> C:\a\b\r
746      * ~/a/b/c.txt  --> ~/a/b\r
747      * a.txt        --> ""\r
748      * a/b/c        --> a/b\r
749      * a/b/c/       --> a/b/c\r
750      * C:           --> C:\r
751      * C:\          --> C:\\r
752      * ~            --> ~\r
753      * ~/           --> ~\r
754      * ~user        --> ~user\r
755      * ~user/       --> ~user\r
756      * </pre>\r
757      * <p>\r
758      * The output will be the same irrespective of the machine that the code is running on.\r
759      *\r
760      * @param filename  the filename to query, null returns null\r
761      * @return the path of the file, an empty string if none exists, null if invalid\r
762      */\r
763     public static String getFullPathNoEndSeparator(String filename) {\r
764         return doGetFullPath(filename, false);\r
765     }\r
766 \r
767     /**\r
768      * Does the work of getting the path.\r
769      * \r
770      * @param filename  the filename\r
771      * @param includeSeparator  true to include the end separator\r
772      * @return the path\r
773      */\r
774     private static String doGetFullPath(String filename, boolean includeSeparator) {\r
775         if (filename == null) {\r
776             return null;\r
777         }\r
778         int prefix = getPrefixLength(filename);\r
779         if (prefix < 0) {\r
780             return null;\r
781         }\r
782         if (prefix >= filename.length()) {\r
783             if (includeSeparator) {\r
784                 return getPrefix(filename);  // add end slash if necessary\r
785             } else {\r
786                 return filename;\r
787             }\r
788         }\r
789         int index = indexOfLastSeparator(filename);\r
790         if (index < 0) {\r
791             return filename.substring(0, prefix);\r
792         }\r
793         int end = index + (includeSeparator ?  1 : 0);\r
794         return filename.substring(0, end);\r
795     }\r
796 \r
797     /**\r
798      * Gets the name minus the path from a full filename.\r
799      * <p>\r
800      * This method will handle a file in either Unix or Windows format.\r
801      * The text after the last forward or backslash is returned.\r
802      * <pre>\r
803      * a/b/c.txt --> c.txt\r
804      * a.txt     --> a.txt\r
805      * a/b/c     --> c\r
806      * a/b/c/    --> ""\r
807      * </pre>\r
808      * <p>\r
809      * The output will be the same irrespective of the machine that the code is running on.\r
810      *\r
811      * @param filename  the filename to query, null returns null\r
812      * @return the name of the file without the path, or an empty string if none exists\r
813      */\r
814     public static String getName(String filename) {\r
815         if (filename == null) {\r
816             return null;\r
817         }\r
818         int index = indexOfLastSeparator(filename);\r
819         return filename.substring(index + 1);\r
820     }\r
821 \r
822     /**\r
823      * Gets the base name, minus the full path and extension, from a full filename.\r
824      * <p>\r
825      * This method will handle a file in either Unix or Windows format.\r
826      * The text after the last forward or backslash and before the last dot is returned.\r
827      * <pre>\r
828      * a/b/c.txt --> c\r
829      * a.txt     --> a\r
830      * a/b/c     --> c\r
831      * a/b/c/    --> ""\r
832      * </pre>\r
833      * <p>\r
834      * The output will be the same irrespective of the machine that the code is running on.\r
835      *\r
836      * @param filename  the filename to query, null returns null\r
837      * @return the name of the file without the path, or an empty string if none exists\r
838      */\r
839     public static String getBaseName(String filename) {\r
840         return removeExtension(getName(filename));\r
841     }\r
842 \r
843     /**\r
844      * Gets the extension of a filename.\r
845      * <p>\r
846      * This method returns the textual part of the filename after the last dot.\r
847      * There must be no directory separator after the dot.\r
848      * <pre>\r
849      * foo.txt      --> "txt"\r
850      * a/b/c.jpg    --> "jpg"\r
851      * a/b.txt/c    --> ""\r
852      * a/b/c        --> ""\r
853      * </pre>\r
854      * <p>\r
855      * The output will be the same irrespective of the machine that the code is running on.\r
856      *\r
857      * @param filename the filename to retrieve the extension of.\r
858      * @return the extension of the file or an empty string if none exists.\r
859      */\r
860     public static String getExtension(String filename) {\r
861         if (filename == null) {\r
862             return null;\r
863         }\r
864         int index = indexOfExtension(filename);\r
865         if (index == -1) {\r
866             return "";\r
867         } else {\r
868             return filename.substring(index + 1);\r
869         }\r
870     }\r
871 \r
872     //-----------------------------------------------------------------------\r
873     /**\r
874      * Removes the extension from a filename.\r
875      * <p>\r
876      * This method returns the textual part of the filename before the last dot.\r
877      * There must be no directory separator after the dot.\r
878      * <pre>\r
879      * foo.txt    --> foo\r
880      * a\b\c.jpg  --> a\b\c\r
881      * a\b\c      --> a\b\c\r
882      * a.b\c      --> a.b\c\r
883      * </pre>\r
884      * <p>\r
885      * The output will be the same irrespective of the machine that the code is running on.\r
886      *\r
887      * @param filename  the filename to query, null returns null\r
888      * @return the filename minus the extension\r
889      */\r
890     public static String removeExtension(String filename) {\r
891         if (filename == null) {\r
892             return null;\r
893         }\r
894         int index = indexOfExtension(filename);\r
895         if (index == -1) {\r
896             return filename;\r
897         } else {\r
898             return filename.substring(0, index);\r
899         }\r
900     }\r
901 \r
902     //-----------------------------------------------------------------------\r
903     /**\r
904      * Checks whether two filenames are equal exactly.\r
905      * <p>\r
906      * No processing is performed on the filenames other than comparison,\r
907      * thus this is merely a null-safe case-sensitive equals.\r
908      *\r
909      * @param filename1  the first filename to query, may be null\r
910      * @param filename2  the second filename to query, may be null\r
911      * @return true if the filenames are equal, null equals null\r
912      * @see IOCase#SENSITIVE\r
913      */\r
914     public static boolean equals(String filename1, String filename2) {\r
915         return equals(filename1, filename2, false, IOCase.SENSITIVE);\r
916     }\r
917 \r
918     /**\r
919      * Checks whether two filenames are equal using the case rules of the system.\r
920      * <p>\r
921      * No processing is performed on the filenames other than comparison.\r
922      * The check is case-sensitive on Unix and case-insensitive on Windows.\r
923      *\r
924      * @param filename1  the first filename to query, may be null\r
925      * @param filename2  the second filename to query, may be null\r
926      * @return true if the filenames are equal, null equals null\r
927      * @see IOCase#SYSTEM\r
928      */\r
929     public static boolean equalsOnSystem(String filename1, String filename2) {\r
930         return equals(filename1, filename2, false, IOCase.SYSTEM);\r
931     }\r
932 \r
933     //-----------------------------------------------------------------------\r
934     /**\r
935      * Checks whether two filenames are equal after both have been normalized.\r
936      * <p>\r
937      * Both filenames are first passed to {@link #normalize(String)}.\r
938      * The check is then performed in a case-sensitive manner.\r
939      *\r
940      * @param filename1  the first filename to query, may be null\r
941      * @param filename2  the second filename to query, may be null\r
942      * @return true if the filenames are equal, null equals null\r
943      * @see IOCase#SENSITIVE\r
944      */\r
945     public static boolean equalsNormalized(String filename1, String filename2) {\r
946         return equals(filename1, filename2, true, IOCase.SENSITIVE);\r
947     }\r
948 \r
949     /**\r
950      * Checks whether two filenames are equal after both have been normalized\r
951      * and using the case rules of the system.\r
952      * <p>\r
953      * Both filenames are first passed to {@link #normalize(String)}.\r
954      * The check is then performed case-sensitive on Unix and\r
955      * case-insensitive on Windows.\r
956      *\r
957      * @param filename1  the first filename to query, may be null\r
958      * @param filename2  the second filename to query, may be null\r
959      * @return true if the filenames are equal, null equals null\r
960      * @see IOCase#SYSTEM\r
961      */\r
962     public static boolean equalsNormalizedOnSystem(String filename1, String filename2) {\r
963         return equals(filename1, filename2, true, IOCase.SYSTEM);\r
964     }\r
965 \r
966     /**\r
967      * Checks whether two filenames are equal, optionally normalizing and providing\r
968      * control over the case-sensitivity.\r
969      *\r
970      * @param filename1  the first filename to query, may be null\r
971      * @param filename2  the second filename to query, may be null\r
972      * @param normalized  whether to normalize the filenames\r
973      * @param caseSensitivity  what case sensitivity rule to use, null means case-sensitive\r
974      * @return true if the filenames are equal, null equals null\r
975      * @since Commons IO 1.3\r
976      */\r
977     public static boolean equals(\r
978             String filename1, String filename2,\r
979             boolean normalized, IOCase caseSensitivity) {\r
980         \r
981         if (filename1 == null || filename2 == null) {\r
982             return filename1 == filename2;\r
983         }\r
984         if (normalized) {\r
985             filename1 = normalize(filename1);\r
986             filename2 = normalize(filename2);\r
987             if (filename1 == null || filename2 == null) {\r
988                 throw new NullPointerException(\r
989                     "Error normalizing one or both of the file names");\r
990             }\r
991         }\r
992         if (caseSensitivity == null) {\r
993             caseSensitivity = IOCase.SENSITIVE;\r
994         }\r
995         return caseSensitivity.checkEquals(filename1, filename2);\r
996     }\r
997 \r
998     //-----------------------------------------------------------------------\r
999     /**\r
1000      * Checks whether the extension of the filename is that specified.\r
1001      * <p>\r
1002      * This method obtains the extension as the textual part of the filename\r
1003      * after the last dot. There must be no directory separator after the dot.\r
1004      * The extension check is case-sensitive on all platforms.\r
1005      *\r
1006      * @param filename  the filename to query, null returns false\r
1007      * @param extension  the extension to check for, null or empty checks for no extension\r
1008      * @return true if the filename has the specified extension\r
1009      */\r
1010     public static boolean isExtension(String filename, String extension) {\r
1011         if (filename == null) {\r
1012             return false;\r
1013         }\r
1014         if (extension == null || extension.length() == 0) {\r
1015             return (indexOfExtension(filename) == -1);\r
1016         }\r
1017         String fileExt = getExtension(filename);\r
1018         return fileExt.equals(extension);\r
1019     }\r
1020 \r
1021     /**\r
1022      * Checks whether the extension of the filename is one of those specified.\r
1023      * <p>\r
1024      * This method obtains the extension as the textual part of the filename\r
1025      * after the last dot. There must be no directory separator after the dot.\r
1026      * The extension check is case-sensitive on all platforms.\r
1027      *\r
1028      * @param filename  the filename to query, null returns false\r
1029      * @param extensions  the extensions to check for, null checks for no extension\r
1030      * @return true if the filename is one of the extensions\r
1031      */\r
1032     public static boolean isExtension(String filename, String[] extensions) {\r
1033         if (filename == null) {\r
1034             return false;\r
1035         }\r
1036         if (extensions == null || extensions.length == 0) {\r
1037             return (indexOfExtension(filename) == -1);\r
1038         }\r
1039         String fileExt = getExtension(filename);\r
1040         for (int i = 0; i < extensions.length; i++) {\r
1041             if (fileExt.equals(extensions[i])) {\r
1042                 return true;\r
1043             }\r
1044         }\r
1045         return false;\r
1046     }\r
1047 \r
1048     /**\r
1049      * Checks whether the extension of the filename is one of those specified.\r
1050      * <p>\r
1051      * This method obtains the extension as the textual part of the filename\r
1052      * after the last dot. There must be no directory separator after the dot.\r
1053      * The extension check is case-sensitive on all platforms.\r
1054      *\r
1055      * @param filename  the filename to query, null returns false\r
1056      * @param extensions  the extensions to check for, null checks for no extension\r
1057      * @return true if the filename is one of the extensions\r
1058      */\r
1059     public static boolean isExtension(String filename, Collection extensions) {\r
1060         if (filename == null) {\r
1061             return false;\r
1062         }\r
1063         if (extensions == null || extensions.isEmpty()) {\r
1064             return (indexOfExtension(filename) == -1);\r
1065         }\r
1066         String fileExt = getExtension(filename);\r
1067         for (Iterator it = extensions.iterator(); it.hasNext();) {\r
1068             if (fileExt.equals(it.next())) {\r
1069                 return true;\r
1070             }\r
1071         }\r
1072         return false;\r
1073     }\r
1074 \r
1075     //-----------------------------------------------------------------------\r
1076     /**\r
1077      * Checks a filename to see if it matches the specified wildcard matcher,\r
1078      * always testing case-sensitive.\r
1079      * <p>\r
1080      * The wildcard matcher uses the characters '?' and '*' to represent a\r
1081      * single or multiple wildcard characters.\r
1082      * This is the same as often found on Dos/Unix command lines.\r
1083      * The check is case-sensitive always.\r
1084      * <pre>\r
1085      * wildcardMatch("c.txt", "*.txt")      --> true\r
1086      * wildcardMatch("c.txt", "*.jpg")      --> false\r
1087      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true\r
1088      * wildcardMatch("c.txt", "*.???")      --> true\r
1089      * wildcardMatch("c.txt", "*.????")     --> false\r
1090      * </pre>\r
1091      * \r
1092      * @param filename  the filename to match on\r
1093      * @param wildcardMatcher  the wildcard string to match against\r
1094      * @return true if the filename matches the wilcard string\r
1095      * @see IOCase#SENSITIVE\r
1096      */\r
1097     public static boolean wildcardMatch(String filename, String wildcardMatcher) {\r
1098         return wildcardMatch(filename, wildcardMatcher, IOCase.SENSITIVE);\r
1099     }\r
1100 \r
1101     /**\r
1102      * Checks a filename to see if it matches the specified wildcard matcher\r
1103      * using the case rules of the system.\r
1104      * <p>\r
1105      * The wildcard matcher uses the characters '?' and '*' to represent a\r
1106      * single or multiple wildcard characters.\r
1107      * This is the same as often found on Dos/Unix command lines.\r
1108      * The check is case-sensitive on Unix and case-insensitive on Windows.\r
1109      * <pre>\r
1110      * wildcardMatch("c.txt", "*.txt")      --> true\r
1111      * wildcardMatch("c.txt", "*.jpg")      --> false\r
1112      * wildcardMatch("a/b/c.txt", "a/b/*")  --> true\r
1113      * wildcardMatch("c.txt", "*.???")      --> true\r
1114      * wildcardMatch("c.txt", "*.????")     --> false\r
1115      * </pre>\r
1116      * \r
1117      * @param filename  the filename to match on\r
1118      * @param wildcardMatcher  the wildcard string to match against\r
1119      * @return true if the filename matches the wilcard string\r
1120      * @see IOCase#SYSTEM\r
1121      */\r
1122     public static boolean wildcardMatchOnSystem(String filename, String wildcardMatcher) {\r
1123         return wildcardMatch(filename, wildcardMatcher, IOCase.SYSTEM);\r
1124     }\r
1125 \r
1126     /**\r
1127      * Checks a filename to see if it matches the specified wildcard matcher\r
1128      * allowing control over case-sensitivity.\r
1129      * <p>\r
1130      * The wildcard matcher uses the characters '?' and '*' to represent a\r
1131      * single or multiple wildcard characters.\r
1132      * \r
1133      * @param filename  the filename to match on\r
1134      * @param wildcardMatcher  the wildcard string to match against\r
1135      * @param caseSensitivity  what case sensitivity rule to use, null means case-sensitive\r
1136      * @return true if the filename matches the wilcard string\r
1137      * @since Commons IO 1.3\r
1138      */\r
1139     public static boolean wildcardMatch(String filename, String wildcardMatcher, IOCase caseSensitivity) {\r
1140         if (filename == null && wildcardMatcher == null) {\r
1141             return true;\r
1142         }\r
1143         if (filename == null || wildcardMatcher == null) {\r
1144             return false;\r
1145         }\r
1146         if (caseSensitivity == null) {\r
1147             caseSensitivity = IOCase.SENSITIVE;\r
1148         }\r
1149         filename = caseSensitivity.convertCase(filename);\r
1150         wildcardMatcher = caseSensitivity.convertCase(wildcardMatcher);\r
1151         String[] wcs = splitOnTokens(wildcardMatcher);\r
1152         boolean anyChars = false;\r
1153         int textIdx = 0;\r
1154         int wcsIdx = 0;\r
1155         Stack backtrack = new Stack();\r
1156         \r
1157         // loop around a backtrack stack, to handle complex * matching\r
1158         do {\r
1159             if (backtrack.size() > 0) {\r
1160                 int[] array = (int[]) backtrack.pop();\r
1161                 wcsIdx = array[0];\r
1162                 textIdx = array[1];\r
1163                 anyChars = true;\r
1164             }\r
1165             \r
1166             // loop whilst tokens and text left to process\r
1167             while (wcsIdx < wcs.length) {\r
1168       \r
1169                 if (wcs[wcsIdx].equals("?")) {\r
1170                     // ? so move to next text char\r
1171                     textIdx++;\r
1172                     anyChars = false;\r
1173                     \r
1174                 } else if (wcs[wcsIdx].equals("*")) {\r
1175                     // set any chars status\r
1176                     anyChars = true;\r
1177                     if (wcsIdx == wcs.length - 1) {\r
1178                         textIdx = filename.length();\r
1179                     }\r
1180                     \r
1181                 } else {\r
1182                     // matching text token\r
1183                     if (anyChars) {\r
1184                         // any chars then try to locate text token\r
1185                         textIdx = filename.indexOf(wcs[wcsIdx], textIdx);\r
1186                         if (textIdx == -1) {\r
1187                             // token not found\r
1188                             break;\r
1189                         }\r
1190                         int repeat = filename.indexOf(wcs[wcsIdx], textIdx + 1);\r
1191                         if (repeat >= 0) {\r
1192                             backtrack.push(new int[] {wcsIdx, repeat});\r
1193                         }\r
1194                     } else {\r
1195                         // matching from current position\r
1196                         if (!filename.startsWith(wcs[wcsIdx], textIdx)) {\r
1197                             // couldnt match token\r
1198                             break;\r
1199                         }\r
1200                     }\r
1201       \r
1202                     // matched text token, move text index to end of matched token\r
1203                     textIdx += wcs[wcsIdx].length();\r
1204                     anyChars = false;\r
1205                 }\r
1206       \r
1207                 wcsIdx++;\r
1208             }\r
1209             \r
1210             // full match\r
1211             if (wcsIdx == wcs.length && textIdx == filename.length()) {\r
1212                 return true;\r
1213             }\r
1214             \r
1215         } while (backtrack.size() > 0);\r
1216   \r
1217         return false;\r
1218     }\r
1219 \r
1220     /**\r
1221      * Splits a string into a number of tokens.\r
1222      * \r
1223      * @param text  the text to split\r
1224      * @return the tokens, never null\r
1225      */\r
1226     static String[] splitOnTokens(String text) {\r
1227         // used by wildcardMatch\r
1228         // package level so a unit test may run on this\r
1229         \r
1230         if (text.indexOf("?") == -1 && text.indexOf("*") == -1) {\r
1231             return new String[] { text };\r
1232         }\r
1233 \r
1234         char[] array = text.toCharArray();\r
1235         ArrayList list = new ArrayList();\r
1236         StringBuffer buffer = new StringBuffer();\r
1237         for (int i = 0; i < array.length; i++) {\r
1238             if (array[i] == '?' || array[i] == '*') {\r
1239                 if (buffer.length() != 0) {\r
1240                     list.add(buffer.toString());\r
1241                     buffer.setLength(0);\r
1242                 }\r
1243                 if (array[i] == '?') {\r
1244                     list.add("?");\r
1245                 } else if (list.size() == 0 ||\r
1246                         (i > 0 && list.get(list.size() - 1).equals("*") == false)) {\r
1247                     list.add("*");\r
1248                 }\r
1249             } else {\r
1250                 buffer.append(array[i]);\r
1251             }\r
1252         }\r
1253         if (buffer.length() != 0) {\r
1254             list.add(buffer.toString());\r
1255         }\r
1256 \r
1257         return (String[]) list.toArray( new String[ list.size() ] );\r
1258     }\r
1259 \r
1260 }\r