View Javadoc

1   /*
2    * REPOWEB, repository manager.
3    *
4    * Terms of license - http://opensource.org/licenses/apachepl.php
5    */
6   /* Derived from Ant source. */
7   package org.repoweb.model.file.util;
8   import java.io.File;
9   import java.io.FileFilter;
10  import java.io.IOException;
11  import java.util.ArrayList;
12  import java.util.List;
13  
14  /***
15   * Class for scanning a directory for files/directories which match certain
16   * criteria.
17   * <p>
18   * These criteria consist of selectors and patterns which have been specified.
19   * With the selectors you can select which files you want to have included.
20   * Files which are not selected are excluded. With patterns you can include
21   * or exclude files based on their filename.
22   * <p>
23   * The idea is simple. A given directory is recursively scanned for all files
24   * and directories. Each file/directory is matched against a set of selectors,
25   * including special support for matching against filenames with include and
26   * and exclude patterns. Only files/directories which match at least one
27   * pattern of the include pattern list or other file selector, and don't match
28   * any pattern of the exclude pattern list or fail to match against a required
29   * selector will be placed in the list of files/directories found.
30   * <p>
31   * When no list of include patterns is supplied, "**" will be used, which
32   * means that everything will be matched. When no list of exclude patterns is
33   * supplied, an empty list is used, such that nothing will be excluded. When
34   * no selectors are supplied, none are applied.
35   * <p>
36   * The filename pattern matching is done as follows:
37   * The name to be matched is split up in path segments. A path segment is the
38   * name of a directory or file, which is bounded by
39   * <code>File.separator</code> ('/' under UNIX, '\' under Windows).
40   * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
41   * "def","ghi" and "xyz.java".
42   * The same is done for the pattern against which should be matched.
43   * <p>
44   * The segments of the name and the pattern are then matched against each
45   * other. When '**' is used for a path segment in the pattern, it matches
46   * zero or more path segments of the name.
47   * <p>
48   * There is a special case regarding the use of <code>File.separator</code>s
49   * at the beginning of the pattern and the string to match:<br>
50   * When a pattern starts with a <code>File.separator</code>, the string
51   * to match must also start with a <code>File.separator</code>.
52   * When a pattern does not start with a <code>File.separator</code>, the
53   * string to match may not start with a <code>File.separator</code>.
54   * When one of these rules is not obeyed, the string will not
55   * match.
56   * <p>
57   * When a name path segment is matched against a pattern path segment, the
58   * following special characters can be used:<br>
59   * '*' matches zero or more characters<br>
60   * '?' matches one character.
61   * <p>
62   * Examples:
63   * <p>
64   * "**\*.class" matches all .class files/dirs in a directory tree.
65   * <p>
66   * "test\a??.java" matches all files/dirs which start with an 'a', then two
67   * more characters and then ".java", in a directory called test.
68   * <p>
69   * "**" matches everything in a directory tree.
70   * <p>
71   * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
72   * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
73   * <p>
74   * Case sensitivity may be turned off if necessary. By default, it is
75   * turned on.
76   * <p>
77   * Example of usage:
78   * <pre>
79   *   String[] includes = {"**//*.class"};
80   *   String[] excludes = {"modules//*//**"};
81   *   ds.setIncludes(includes);
82   *   ds.setExcludes(excludes);
83   *   ds.setBasedir(new File("test"));
84   *   ds.setCaseSensitive(true);
85   *   ds.scan();
86   *
87   *   LOG.info("FILES:");
88   *   String[] files = ds.getIncludedFiles();
89   *   for (int i = 0; i < files.length; i++) {
90   *     LOG.info(files[i]);
91   *   }
92   * </pre>
93   * This will scan a directory called test for .class files, but excludes all
94   * files in all proper subdirectories of a directory called "modules"
95   *
96   * @author Arnout J. Kuiper
97   * <a href="mailto:ajkuiper@wxs.nl">ajkuiper@wxs.nl</a>
98   * @author Magesh Umasankar
99   * @author <a href="mailto:bruce@callenish.com">Bruce Atherton</a>
100  * @author <a href="mailto:levylambert@tiscali-dsl.de">Antoine Levy-Lambert</a>
101  */
102 public class DirectoryScanner {
103     private static final String EMPTY = "";
104     /***
105      * Patterns which should be excluded by default.
106      *
107      * @see #addDefaultExcludes()
108      */
109     private static final String[] DEFAULTEXCLUDES =
110     {
111         // Miscellaneous typical temporary files
112         "**/*~",
113         "**/#*#",
114         "**/.#*",
115         "**/%*%",
116         "**/._*", // CVS
117         "**/CVS",
118         "**/CVS/**",
119         "**/.cvsignore", // SCCS
120         "**/SCCS",
121         "**/SCCS/**", // Visual SourceSafe
122         "**/vssver.scc", // Subversion
123         "**/.svn",
124         "**/.svn/**", // Mac
125         "**/.DS_Store"
126     };
127 
128     /*** The base directory to be scanned. */
129     private File _basedir;
130 
131     /*** The patterns for the files to be included. */
132     private String[] _includes;
133 
134     /*** The patterns for the files to be excluded. */
135     private String[] _excludes;
136 
137     /*** Selectors that will filter which files are in our candidate list. */
138     private FileFilter[] _selectors = null;
139 
140     /*** The files which matched at least one include and no excludes
141      *  and were selected.
142      */
143     private List _filesIncluded;
144 
145     /*** The files which did not match any includes or selectors. */
146     private List _filesNotIncluded;
147 
148     /***
149      * The files which matched at least one include and at least
150      * one exclude.
151      */
152     private List _filesExcluded;
153 
154     /*** The directories which matched at least one include and no excludes
155      *  and were selected.
156      */
157     private List _dirsIncluded;
158 
159     /*** The directories which were found and did not match any includes. */
160     private List _dirsNotIncluded;
161 
162     /***
163      * The directories which matched at least one include and at least one
164      * exclude.
165      */
166     private List _dirsExcluded;
167 
168     /*** The files which matched at least one include and no excludes and
169      *  which a selector discarded.
170      */
171     private List _filesDeselected;
172 
173     /*** The directories which matched at least one include and no excludes
174      *  but which a selector discarded.
175      */
176     private List _dirsDeselected;
177 
178     /*** Whether or not our results were built by a slow scan. */
179     private boolean _haveSlowResults = false;
180 
181     /***
182      * Whether or not the file system should be treated as a case sensitive
183      * one.
184      */
185     private boolean _isCaseSensitive = true;
186 
187     /***
188      * Whether or not symbolic links should be followed.
189      *
190      * @since Ant 1.5
191      */
192     private boolean _followSymlinks = true;
193 
194     /*** Whether or not everything tested so far has been included. */
195     private boolean _everythingIncluded = true;
196 
197     /***
198      * Sole constructor.
199      */
200     public DirectoryScanner() {}
201 
202     /***
203      * Tests whether or not a given path matches the start of a given
204      * pattern up to the first "**".
205      * <p>
206      * This is not a general purpose test and should only be used if you
207      * can live with false positives. For example, <code>pattern=**\a</code>
208      * and <code>str=b</code> will yield <code>true</code>.
209      *
210      * @param pattern The pattern to match against. Must not be
211      *                <code>null</code>.
212      * @param str     The path to match, as a String. Must not be
213      *                <code>null</code>.
214      * @param isCaseSensitive Whether or not matching should be performed
215      *                        case sensitively.
216      *
217      * @return whether or not a given path matches the start of a given
218      * pattern up to the first "**".
219      */
220     private static boolean matchPatternStart(String pattern, String str,
221         boolean isCaseSensitive) {
222         return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive);
223     }
224 
225 
226     /***
227      * Tests whether or not a given path matches a given pattern.
228      *
229      * @param pattern The pattern to match against. Must not be
230      *                <code>null</code>.
231      * @param str     The path to match, as a String. Must not be
232      *                <code>null</code>.
233      * @param isCaseSensitive Whether or not matching should be performed
234      *                        case sensitively.
235      *
236      * @return <code>true</code> if the pattern matches against the string,
237      *         or <code>false</code> otherwise.
238      */
239     private static boolean matchPath(String pattern, String str, boolean isCaseSensitive) {
240         return SelectorUtils.matchPath(pattern, str, isCaseSensitive);
241     }
242 
243 
244     /***
245      * Sets the base directory to be scanned. This is the directory which is
246      * scanned recursively. All '/' and '\' characters are replaced by
247      * <code>File.separatorChar</code>, so the separator used need not match
248      * <code>File.separatorChar</code>.
249      *
250      * @param basedir The base directory to scan.
251      *                Must not be <code>null</code>.
252      */
253     public void setBasedir(String basedir) {
254         setBasedir(new File(basedir.replace('/', File.separatorChar).replace('//',
255                     File.separatorChar)));
256     }
257 
258 
259     /***
260      * Sets the base directory to be scanned. This is the directory which is
261      * scanned recursively.
262      *
263      * @param basedir The base directory for scanning.
264      *                Should not be <code>null</code>.
265      */
266     public void setBasedir(File basedir) {
267         this._basedir = basedir;
268     }
269 
270 
271     /***
272      * Returns the base directory to be scanned.
273      * This is the directory which is scanned recursively.
274      *
275      * @return the base directory to be scanned
276      */
277     public File getBasedir() {
278         return _basedir;
279     }
280 
281 
282     /***
283      * Sets whether or not the file system should be regarded as case sensitive.
284      *
285      * @param isCaseSensitive whether or not the file system should be
286      *                        regarded as a case sensitive one
287      */
288     public void setCaseSensitive(boolean isCaseSensitive) {
289         this._isCaseSensitive = isCaseSensitive;
290     }
291 
292 
293     /***
294      * Sets whether or not symbolic links should be followed.
295      *
296      * @param followSymlinks whether or not symbolic links should be followed
297      */
298     public void setFollowSymlinks(boolean followSymlinks) {
299         this._followSymlinks = followSymlinks;
300     }
301 
302 
303     /***
304      * Sets the list of include patterns to use. All '/' and '\' characters
305      * are replaced by <code>File.separatorChar</code>, so the separator used
306      * need not match <code>File.separatorChar</code>.
307      * <p>
308      * When a pattern ends with a '/' or '\', "**" is appended.
309      *
310      * @param includes A list of include patterns.
311      *                 May be <code>null</code>, indicating that all files
312      *                 should be included. If a non-<code>null</code>
313      *                 list is given, all elements must be
314      * non-<code>null</code>.
315      */
316     public void setIncludes(String[] includes) {
317         this._includes = copyPatternArray(includes);
318     }
319 
320 
321     /***
322      * Sets the list of exclude patterns to use. All '/' and '\' characters
323      * are replaced by <code>File.separatorChar</code>, so the separator used
324      * need not match <code>File.separatorChar</code>.
325      * <p>
326      * When a pattern ends with a '/' or '\', "**" is appended.
327      *
328      * @param excludes A list of exclude patterns.
329      *                 May be <code>null</code>, indicating that no files
330      *                 should be excluded. If a non-<code>null</code> list is
331      *                 given, all elements must be non-<code>null</code>.
332      */
333     public void setExcludes(String[] excludes) {
334         this._excludes = copyPatternArray(excludes);
335     }
336 
337 
338     /***
339      * Sets the selectors that will select the filelist.
340      *
341      * @param selectors specifies the selectors to be invoked on a scan
342      */
343     public void setSelectors(FileFilter[] selectors) {
344         this._selectors = selectors;
345     }
346 
347 
348     /***
349      * Returns whether or not the scanner has included all the files or
350      * directories it has come across so far.
351      *
352      * @return <code>true</code> if all files and directories which have
353      *         been found so far have been included.
354      */
355     public boolean isEverythingIncluded() {
356         return _everythingIncluded;
357     }
358 
359 
360     /***
361      * Scans the base directory for files which match at least one include
362      * pattern and don't match any exclude patterns. If there are selectors
363      * then the files must pass muster there, as well.
364      *
365      * @exception ScanException if the base directory was set
366      *            incorrectly (i.e. if it is <code>null</code>, doesn't exist,
367      *            or isn't a directory).
368      */
369     public void scan() {
370         if (_basedir == null) {
371             throw new ScanException("No basedir set");
372         }
373         if (!_basedir.exists()) {
374             throw new ScanException("basedir " + _basedir + " does not exist");
375         }
376         if (!_basedir.isDirectory()) {
377             throw new ScanException("basedir " + _basedir + " is not a directory");
378         }
379 
380         if (_includes == null) {
381             // No includes supplied, so set it to 'matches all'
382             _includes = new String[1];
383             _includes[0] = "**";
384         }
385         if (_excludes == null) {
386             _excludes = new String[0];
387         }
388 
389         _filesIncluded = new ArrayList();
390         _filesNotIncluded = new ArrayList();
391         _filesExcluded = new ArrayList();
392         _filesDeselected = new ArrayList();
393         _dirsIncluded = new ArrayList();
394         _dirsNotIncluded = new ArrayList();
395         _dirsExcluded = new ArrayList();
396         _dirsDeselected = new ArrayList();
397 
398         if (isIncluded(EMPTY)) {
399             if (!isExcluded(EMPTY)) {
400                 if (isSelected(_basedir)) {
401                     _dirsIncluded.add(EMPTY);
402                 }
403                 else {
404                     _dirsDeselected.add(EMPTY);
405                 }
406             }
407             else {
408                 _dirsExcluded.add(EMPTY);
409             }
410         }
411         else {
412             _dirsNotIncluded.add(EMPTY);
413         }
414         scandir(_basedir, EMPTY, true);
415     }
416 
417 
418     /***
419      * Top level invocation for a slow scan. A slow scan builds up a full
420      * list of excluded/included files/directories, whereas a fast scan
421      * will only have full results for included files, as it ignores
422      * directories which can't possibly hold any included files/directories.
423      * <p>
424      * Returns immediately if a slow scan has already been completed.
425      */
426     private void slowScan() {
427         if (_haveSlowResults) {
428             return;
429         }
430 
431         String[] excl = (String[])_dirsExcluded.toArray(new String[_dirsExcluded.size()]);
432 
433         String[] notIncl =
434             (String[])_dirsNotIncluded.toArray(new String[_dirsNotIncluded.size()]);
435 
436         for (int i = 0; i < excl.length; i++) {
437             if (!couldHoldIncluded(excl[i])) {
438                 scandir(new File(_basedir, excl[i]), excl[i] + File.separator, false);
439             }
440         }
441 
442         for (int i = 0; i < notIncl.length; i++) {
443             if (!couldHoldIncluded(notIncl[i])) {
444                 scandir(new File(_basedir, notIncl[i]), notIncl[i] + File.separator, false);
445             }
446         }
447 
448         _haveSlowResults = true;
449     }
450 
451 
452     /***
453      * Scans the given directory for files and directories. Found files and
454      * directories are placed in their respective collections, based on the
455      * matching of includes, excludes, and the selectors.  When a directory
456      * is found, it is scanned recursively.
457      *
458      * @param dir   The directory to scan. Must not be <code>null</code>.
459      * @param vpath The path relative to the base directory (needed to
460      *              prevent problems with an absolute path when using
461      *              dir). Must not be <code>null</code>.
462      * @param fast  Whether or not this call is part of a fast scan.
463      *
464      * @see #_filesIncluded
465      * @see #_filesNotIncluded
466      * @see #_filesExcluded
467      * @see #_dirsIncluded
468      * @see #_dirsNotIncluded
469      * @see #_dirsExcluded
470      */
471     private void scandir(File dir, String vpath, boolean fast) {
472         String[] newfiles = dir.list();
473 
474         if (newfiles == null) {
475             /*
476              * two reasons are mentioned in the API docs for File.list
477              * (1) dir is not a directory. This is impossible as
478              *     we wouldn't get here in this case.
479              * (2) an IO error occurred (why doesn't it throw an exception
480              *     then???)
481              */
482             throw new ScanException("IO error scanning directory "
483                 + dir.getAbsolutePath());
484         }
485 
486         if (!_followSymlinks) {
487             List noLinks = new ArrayList();
488             for (int i = 0; i < newfiles.length; i++) {
489                 try {
490                     if (isSymbolicLink(dir, newfiles[i])) {
491                         String name = vpath + newfiles[i];
492                         File file = new File(dir, newfiles[i]);
493                         if (file.isDirectory()) {
494                             _dirsExcluded.add(name);
495                         }
496                         else {
497                             _filesExcluded.add(name);
498                         }
499                     }
500                     else {
501                         noLinks.add(newfiles[i]);
502                     }
503                 }
504                 catch (IOException ioe) {
505                     String msg =
506                         "IOException caught while checking "
507                         + "for links, couldn't get cannonical path!";
508 
509                     // will be caught and redirected to Ant's logging system
510                     System.err.println(msg);
511                     noLinks.add(newfiles[i]);
512                 }
513             }
514             newfiles = (String[])noLinks.toArray(new String[noLinks.size()]);
515         }
516 
517         for (int i = 0; i < newfiles.length; i++) {
518             String name = vpath + newfiles[i];
519             File file = new File(dir, newfiles[i]);
520             if (file.isDirectory()) {
521                 if (isIncluded(name)) {
522                     if (!isExcluded(name)) {
523                         if (isSelected(file)) {
524                             _dirsIncluded.add(file);
525                             if (fast) {
526                                 scandir(file, name + File.separator, fast);
527                             }
528                         }
529                         else {
530                             _everythingIncluded = false;
531                             _dirsDeselected.add(name);
532                             if (fast && couldHoldIncluded(name)) {
533                                 scandir(file, name + File.separator, fast);
534                             }
535                         }
536                     }
537                     else {
538                         _everythingIncluded = false;
539                         _dirsExcluded.add(name);
540                         if (fast && couldHoldIncluded(name)) {
541                             scandir(file, name + File.separator, fast);
542                         }
543                     }
544                 }
545                 else {
546                     _everythingIncluded = false;
547                     _dirsNotIncluded.add(name);
548                     if (fast && couldHoldIncluded(name)) {
549                         scandir(file, name + File.separator, fast);
550                     }
551                 }
552                 if (!fast) {
553                     scandir(file, name + File.separator, fast);
554                 }
555             }
556             else if (file.isFile()) {
557                 if (isIncluded(name)) {
558                     if (!isExcluded(name)) {
559                         if (isSelected(file)) {
560                             _filesIncluded.add(file);
561                         }
562                         else {
563                             _everythingIncluded = false;
564                             _filesDeselected.add(name);
565                         }
566                     }
567                     else {
568                         _everythingIncluded = false;
569                         _filesExcluded.add(name);
570                     }
571                 }
572                 else {
573                     _everythingIncluded = false;
574                     _filesNotIncluded.add(name);
575                 }
576             }
577         }
578     }
579 
580 
581     /***
582      * Tests whether or not a name matches against at least one include
583      * pattern.
584      *
585      * @param name The name to match. Must not be <code>null</code>.
586      * @return <code>true</code> when the name matches against at least one
587      *         include pattern, or <code>false</code> otherwise.
588      */
589     private boolean isIncluded(String name) {
590         for (int i = 0; i < _includes.length; i++) {
591             if (matchPath(_includes[i], name, _isCaseSensitive)) {
592                 return true;
593             }
594         }
595         return false;
596     }
597 
598 
599     /***
600      * Tests whether or not a name matches the start of at least one include
601      * pattern.
602      *
603      * @param name The name to match. Must not be <code>null</code>.
604      * @return <code>true</code> when the name matches against the start of at
605      *         least one include pattern, or <code>false</code> otherwise.
606      */
607     private boolean couldHoldIncluded(String name) {
608         for (int i = 0; i < _includes.length; i++) {
609             if (matchPatternStart(_includes[i], name, _isCaseSensitive)) {
610                 return true;
611             }
612         }
613         return false;
614     }
615 
616 
617     /***
618      * Tests whether or not a name matches against at least one exclude
619      * pattern.
620      *
621      * @param name The name to match. Must not be <code>null</code>.
622      * @return <code>true</code> when the name matches against at least one
623      *         exclude pattern, or <code>false</code> otherwise.
624      */
625     private boolean isExcluded(String name) {
626         for (int i = 0; i < _excludes.length; i++) {
627             if (matchPath(_excludes[i], name, _isCaseSensitive)) {
628                 return true;
629             }
630         }
631         return false;
632     }
633 
634 
635     /***
636      * Tests whether a name should be selected.
637      *
638      * @param file the java.io.File object for this filename
639      * @return <code>false</code> when the selectors says that the file
640      *         should not be selected, <code>true</code> otherwise.
641      */
642     private boolean isSelected(File file) {
643         if (_selectors != null) {
644             for (int i = 0; i < _selectors.length; i++) {
645                 if (!(_selectors[i].accept(file))) {
646                     return false;
647                 }
648             }
649         }
650         return true;
651     }
652 
653 
654     /***
655      * Returns the names of the files which matched at least one of the
656      * include patterns and none of the exclude patterns.
657      * The names are relative to the base directory.
658      *
659      * @return the names of the files which matched at least one of the
660      *         include patterns and none of the exclude patterns.
661      */
662     public File[] getIncludedFiles() {
663         return (File[])_filesIncluded.toArray(new File[_filesIncluded.size()]);
664     }
665 
666 
667     /***
668      * Returns the names of the files which matched none of the include
669      * patterns. The names are relative to the base directory. This involves
670      * performing a slow scan if one has not already been completed.
671      *
672      * @return the names of the files which matched none of the include
673      *         patterns.
674      */
675     public String[] getNotIncludedFiles() {
676         slowScan();
677         return (String[])_filesNotIncluded.toArray(new String[_filesNotIncluded.size()]);
678     }
679 
680 
681     /***
682      * Returns the names of the files which matched at least one of the
683      * include patterns and at least one of the exclude patterns.
684      * The names are relative to the base directory. This involves
685      * performing a slow scan if one has not already been completed.
686      *
687      * @return the names of the files which matched at least one of the
688      *         include patterns and at at least one of the exclude patterns.
689      */
690     public String[] getExcludedFiles() {
691         slowScan();
692         return (String[])_filesExcluded.toArray(new String[_filesExcluded.size()]);
693     }
694 
695 
696     /***
697      * <p>Returns the names of the files which were selected out and
698      * therefore not ultimately included.</p>
699      *
700      * <p>The names are relative to the base directory. This involves
701      * performing a slow scan if one has not already been completed.</p>
702      *
703      * @return the names of the files which were deselected.
704      */
705     public String[] getDeselectedFiles() {
706         slowScan();
707         return (String[])_filesDeselected.toArray(new String[_filesDeselected.size()]);
708     }
709 
710 
711     /***
712      * Returns the names of the directories which matched at least one of the
713      * include patterns and none of the exclude patterns.
714      * The names are relative to the base directory.
715      *
716      * @return the names of the directories which matched at least one of the
717      * include patterns and none of the exclude patterns.
718      */
719     public File[] getIncludedDirectories() {
720         return (File[])_dirsIncluded.toArray(new File[_dirsIncluded.size()]);
721     }
722 
723 
724     /***
725      * Returns the names of the directories which matched none of the include
726      * patterns. The names are relative to the base directory. This involves
727      * performing a slow scan if one has not already been completed.
728      *
729      * @return the names of the directories which matched none of the include
730      * patterns.
731      */
732     public String[] getNotIncludedDirectories() {
733         slowScan();
734         return (String[])_dirsNotIncluded.toArray(new String[_dirsNotIncluded.size()]);
735     }
736 
737 
738     /***
739      * Returns the names of the directories which matched at least one of the
740      * include patterns and at least one of the exclude patterns.
741      * The names are relative to the base directory. This involves
742      * performing a slow scan if one has not already been completed.
743      *
744      * @return the names of the directories which matched at least one of the
745      * include patterns and at least one of the exclude patterns.
746      */
747     public String[] getExcludedDirectories() {
748         slowScan();
749         return (String[])_dirsExcluded.toArray(new String[_dirsExcluded.size()]);
750     }
751 
752 
753     /***
754      * <p>Returns the names of the directories which were selected out and
755      * therefore not ultimately included.</p>
756      *
757      * <p>The names are relative to the base directory. This involves
758      * performing a slow scan if one has not already been completed.</p>
759      *
760      * @return the names of the directories which were deselected.
761      */
762     public String[] getDeselectedDirectories() {
763         slowScan();
764         return (String[])_dirsDeselected.toArray(new String[_dirsDeselected.size()]);
765     }
766 
767 
768     /***
769      * Adds default exclusions to the current exclusions set.
770      */
771     public void addDefaultExcludes() {
772         int excludesLength = _excludes == null ? 0 : _excludes.length;
773         String[] newExcludes;
774         newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
775         if (excludesLength > 0) {
776             System.arraycopy(_excludes, 0, newExcludes, 0, excludesLength);
777         }
778         for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
779             newExcludes[i + excludesLength] =
780                 DEFAULTEXCLUDES[i].replace('/', File.separatorChar).replace('//',
781                     File.separatorChar);
782         }
783         _excludes = newExcludes;
784     }
785 
786 
787     private static boolean isSymbolicLink(File parent, String name)
788         throws IOException {
789         File resolvedParent = new File(parent.getCanonicalPath());
790         File toTest = new File(resolvedParent, name);
791         return !toTest.getAbsolutePath().equals(toTest.getCanonicalPath());
792     }
793 
794 
795     private static final String[] copyPatternArray(String[] source) {
796         if (source == null) {
797             return null;
798         }
799 
800         String[] results = new String[source.length];
801         for (int i = 0; i < source.length; i++) {
802             String pattern;
803             pattern =
804                 source[i].replace('/', File.separatorChar).replace('//',
805                     File.separatorChar);
806             if (pattern.endsWith(File.separator)) {
807                 pattern += "**";
808             }
809             results[i] = pattern;
810         }
811         return results;
812     }
813 }