View Javadoc
1   package org.codehaus.plexus.util;
2   
3   /*
4    * Copyright The Codehaus Foundation.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  import java.io.File;
20  import java.util.ArrayList;
21  import java.util.Comparator;
22  import java.util.List;
23  
24  /**
25   * Scan a directory tree for files, with specified inclusions and exclusions.
26   */
27  public abstract class AbstractScanner
28      implements Scanner
29  {
30      /**
31       * Patterns which should be excluded by default, like SCM files
32       * <ul>
33       * <li>Misc: &#42;&#42;/&#42;~, &#42;&#42;/#&#42;#, &#42;&#42;/.#&#42;, &#42;&#42;/%&#42;%, &#42;&#42;/._&#42;</li>
34       * <li>CVS: &#42;&#42;/CVS, &#42;&#42;/CVS/&#42;&#42;, &#42;&#42;/.cvsignore</li>
35       * <li>RCS: &#42;&#42;/RCS, &#42;&#42;/RCS/&#42;&#42;</li>
36       * <li>SCCS: &#42;&#42;/SCCS, &#42;&#42;/SCCS/&#42;&#42;</li>
37       * <li>VSSercer: &#42;&#42;/vssver.scc</li>
38       * <li>MKS: &#42;&#42;/project.pj</li>
39       * <li>SVN: &#42;&#42;/.svn, &#42;&#42;/.svn/&#42;&#42;</li>
40       * <li>GNU: &#42;&#42;/.arch-ids, &#42;&#42;/.arch-ids/&#42;&#42;</li>
41       * <li>Bazaar: &#42;&#42;/.bzr, &#42;&#42;/.bzr/&#42;&#42;</li>
42       * <li>SurroundSCM: &#42;&#42;/.MySCMServerInfo</li>
43       * <li>Mac: &#42;&#42;/.DS_Store</li>
44       * <li>Serena Dimension: &#42;&#42;/.metadata, &#42;&#42;/.metadata/&#42;&#42;</li>
45       * <li>Mercurial: &#42;&#42;/.hg, &#42;&#42;/.hg/&#42;&#42;</li>
46       * <li>Git: &#42;&#42;/.git, &#42;&#42;/.git/&#42;&#42;</li>
47       * <li>Bitkeeper: &#42;&#42;/BitKeeper, &#42;&#42;/BitKeeper/&#42;&#42;, &#42;&#42;/ChangeSet,
48       * &#42;&#42;/ChangeSet/&#42;&#42;</li>
49       * <li>Darcs: &#42;&#42;/_darcs, &#42;&#42;/_darcs/&#42;&#42;, &#42;&#42;/.darcsrepo,
50       * &#42;&#42;/.darcsrepo/&#42;&#42;&#42;&#42;/-darcs-backup&#42;, &#42;&#42;/.darcs-temp-mail
51       * </ul>
52       *
53       * @see #addDefaultExcludes()
54       */
55      public static final String[] DEFAULTEXCLUDES = {
56          // Miscellaneous typical temporary files
57          "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/._*",
58  
59          // CVS
60          "**/CVS", "**/CVS/**", "**/.cvsignore",
61  
62          // RCS
63          "**/RCS", "**/RCS/**",
64  
65          // SCCS
66          "**/SCCS", "**/SCCS/**",
67  
68          // Visual SourceSafe
69          "**/vssver.scc",
70  
71          // MKS
72          "**/project.pj",
73  
74          // Subversion
75          "**/.svn", "**/.svn/**",
76  
77          // Arch
78          "**/.arch-ids", "**/.arch-ids/**",
79  
80          // Bazaar
81          "**/.bzr", "**/.bzr/**",
82  
83          // SurroundSCM
84          "**/.MySCMServerInfo",
85  
86          // Mac
87          "**/.DS_Store",
88  
89          // Serena Dimensions Version 10
90          "**/.metadata", "**/.metadata/**",
91  
92          // Mercurial
93          "**/.hg", "**/.hg/**",
94  
95          // git
96          "**/.git", "**/.git/**",
97  
98          // BitKeeper
99          "**/BitKeeper", "**/BitKeeper/**", "**/ChangeSet", "**/ChangeSet/**",
100 
101         // darcs
102         "**/_darcs", "**/_darcs/**", "**/.darcsrepo", "**/.darcsrepo/**", "**/-darcs-backup*", "**/.darcs-temp-mail" };
103 
104     /**
105      * The patterns for the files to be included.
106      */
107     protected String[] includes;
108 
109     private MatchPatterns includesPatterns;
110 
111     /**
112      * The patterns for the files to be excluded.
113      */
114     protected String[] excludes;
115 
116     private MatchPatterns excludesPatterns;
117 
118     /**
119      * Whether or not the file system should be treated as a case sensitive one.
120      */
121     protected boolean isCaseSensitive = true;
122 
123     /**
124      * @since 3.3.0
125      */
126     protected Comparator<String> filenameComparator;
127 
128     /**
129      * Sets whether or not the file system should be regarded as case sensitive.
130      *
131      * @param isCaseSensitive whether or not the file system should be regarded as a case sensitive one
132      */
133     public void setCaseSensitive( boolean isCaseSensitive )
134     {
135         this.isCaseSensitive = isCaseSensitive;
136     }
137 
138     /**
139      * <p>Tests whether or not a given path matches the start of a given pattern up to the first "**".</p>
140      *
141      * <p>This is not a general purpose test and should only be used if you can live with false positives. For example,
142      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.</p>
143      *
144      * @param pattern The pattern to match against. Must not be <code>null</code>.
145      * @param str The path to match, as a String. Must not be <code>null</code>.
146      * @return whether or not a given path matches the start of a given pattern up to the first "**".
147      */
148     protected static boolean matchPatternStart( String pattern, String str )
149     {
150         return SelectorUtils.matchPatternStart( pattern, str );
151     }
152 
153     /**
154      * <p>Tests whether or not a given path matches the start of a given pattern up to the first "**".</p>
155      *
156      * <p>This is not a general purpose test and should only be used if you can live with false positives. For example,
157      * <code>pattern=**\a</code> and <code>str=b</code> will yield <code>true</code>.</p>
158      *
159      * @param pattern The pattern to match against. Must not be <code>null</code>.
160      * @param str The path to match, as a String. Must not be <code>null</code>.
161      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
162      * @return whether or not a given path matches the start of a given pattern up to the first "**".
163      */
164     protected static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
165     {
166         return SelectorUtils.matchPatternStart( pattern, str, isCaseSensitive );
167     }
168 
169     /**
170      * Tests whether or not a given path matches a given pattern.
171      *
172      * @param pattern The pattern to match against. Must not be <code>null</code>.
173      * @param str The path to match, as a String. Must not be <code>null</code>.
174      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
175      */
176     protected static boolean matchPath( String pattern, String str )
177     {
178         return SelectorUtils.matchPath( pattern, str );
179     }
180 
181     /**
182      * Tests whether or not a given path matches a given pattern.
183      *
184      * @param pattern The pattern to match against. Must not be <code>null</code>.
185      * @param str The path to match, as a String. Must not be <code>null</code>.
186      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
187      * @return <code>true</code> if the pattern matches against the string, or <code>false</code> otherwise.
188      */
189     protected static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
190     {
191         return SelectorUtils.matchPath( pattern, str, isCaseSensitive );
192     }
193 
194     /**
195      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
196      * '*' means zero or more characters<br>
197      * '?' means one and only one character
198      *
199      * @param pattern The pattern to match against. Must not be <code>null</code>.
200      * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
201      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
202      */
203     public static boolean match( String pattern, String str )
204     {
205         return SelectorUtils.match( pattern, str );
206     }
207 
208     /**
209      * Tests whether or not a string matches against a pattern. The pattern may contain two special characters:<br>
210      * '*' means zero or more characters<br>
211      * '?' means one and only one character
212      *
213      * @param pattern The pattern to match against. Must not be <code>null</code>.
214      * @param str The string which must be matched against the pattern. Must not be <code>null</code>.
215      * @param isCaseSensitive Whether or not matching should be performed case sensitively.
216      * @return <code>true</code> if the string matches against the pattern, or <code>false</code> otherwise.
217      */
218     protected static boolean match( String pattern, String str, boolean isCaseSensitive )
219     {
220         return SelectorUtils.match( pattern, str, isCaseSensitive );
221     }
222 
223     /**
224      * <p>Sets the list of include patterns to use. All '/' and '\' characters are replaced by
225      * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.</p>
226      *
227      * <p>When a pattern ends with a '/' or '\', "**" is appended.</p>
228      *
229      * @param includes A list of include patterns. May be <code>null</code>, indicating that all files should be
230      *            included. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
231      */
232     @Override
233     public void setIncludes( String[] includes )
234     {
235         if ( includes == null )
236         {
237             this.includes = null;
238         }
239         else
240         {
241             final List<String> list = new ArrayList<String>( includes.length );
242             for ( String include : includes )
243             {
244                 if ( include != null )
245                 {
246                     list.add( normalizePattern( include ) );
247                 }
248             }
249             this.includes = list.toArray( new String[0] );
250         }
251     }
252 
253     /**
254      * <p>Sets the list of exclude patterns to use. All '/' and '\' characters are replaced by
255      * <code>File.separatorChar</code>, so the separator used need not match <code>File.separatorChar</code>.</p>
256      *
257      * <p>When a pattern ends with a '/' or '\', "**" is appended.</p>
258      *
259      * @param excludes A list of exclude patterns. May be <code>null</code>, indicating that no files should be
260      *            excluded. If a non-<code>null</code> list is given, all elements must be non-<code>null</code>.
261      */
262     @Override
263     public void setExcludes( String[] excludes )
264     {
265         if ( excludes == null )
266         {
267             this.excludes = null;
268         }
269         else
270         {
271             final List<String> list = new ArrayList<String>( excludes.length );
272             for ( String exclude : excludes )
273             {
274                 if ( exclude != null )
275                 {
276                     list.add( normalizePattern( exclude ) );
277                 }
278             }
279             this.excludes = list.toArray( new String[0] );
280         }
281     }
282 
283     /**
284      * Normalizes the pattern, e.g. converts forward and backward slashes to the platform-specific file separator.
285      *
286      * @param pattern The pattern to normalize, must not be <code>null</code>.
287      * @return The normalized pattern, never <code>null</code>.
288      */
289     private String normalizePattern( String pattern )
290     {
291         pattern = pattern.trim();
292 
293         if ( pattern.startsWith( SelectorUtils.REGEX_HANDLER_PREFIX ) )
294         {
295             if ( File.separatorChar == '\\' )
296             {
297                 pattern = StringUtils.replace( pattern, "/", "\\\\" );
298             }
299             else
300             {
301                 pattern = StringUtils.replace( pattern, "\\\\", "/" );
302             }
303         }
304         else
305         {
306             pattern = pattern.replace( File.separatorChar == '/' ? '\\' : '/', File.separatorChar );
307 
308             if ( pattern.endsWith( File.separator ) )
309             {
310                 pattern += "**";
311             }
312         }
313 
314         return pattern;
315     }
316 
317     /**
318      * Tests whether or not a name matches against at least one include pattern.
319      *
320      * @param name The name to match. Must not be <code>null</code>.
321      * @return <code>true</code> when the name matches against at least one include pattern, or <code>false</code>
322      *         otherwise.
323      */
324     protected boolean isIncluded( String name )
325     {
326         return includesPatterns.matches( name, isCaseSensitive );
327     }
328 
329     protected boolean isIncluded( String name, String[] tokenizedName )
330     {
331         return includesPatterns.matches( name, tokenizedName, isCaseSensitive );
332     }
333 
334     protected boolean isIncluded( String name, char[][] tokenizedName )
335     {
336         return includesPatterns.matches( name, tokenizedName, isCaseSensitive );
337     }
338 
339     /**
340      * Tests whether or not a name matches the start of at least one include pattern.
341      *
342      * @param name The name to match. Must not be <code>null</code>.
343      * @return <code>true</code> when the name matches against the start of at least one include pattern, or
344      *         <code>false</code> otherwise.
345      */
346     protected boolean couldHoldIncluded( String name )
347     {
348         return includesPatterns.matchesPatternStart( name, isCaseSensitive );
349     }
350 
351     /**
352      * Tests whether or not a name matches against at least one exclude pattern.
353      *
354      * @param name The name to match. Must not be <code>null</code>.
355      * @return <code>true</code> when the name matches against at least one exclude pattern, or <code>false</code>
356      *         otherwise.
357      */
358     protected boolean isExcluded( String name )
359     {
360         return excludesPatterns.matches( name, isCaseSensitive );
361     }
362 
363     protected boolean isExcluded( String name, String[] tokenizedName )
364     {
365         return excludesPatterns.matches( name, tokenizedName, isCaseSensitive );
366     }
367 
368     protected boolean isExcluded( String name, char[][] tokenizedName )
369     {
370         return excludesPatterns.matches( name, tokenizedName, isCaseSensitive );
371     }
372 
373     /**
374      * Adds default exclusions to the current exclusions set.
375      */
376     @Override
377     public void addDefaultExcludes()
378     {
379         int excludesLength = excludes == null ? 0 : excludes.length;
380         String[] newExcludes;
381         newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
382         if ( excludesLength > 0 )
383         {
384             System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
385         }
386         for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
387         {
388             newExcludes[i + excludesLength] = DEFAULTEXCLUDES[i].replace( '/', File.separatorChar );
389         }
390         excludes = newExcludes;
391     }
392 
393     protected void setupDefaultFilters()
394     {
395         if ( includes == null )
396         {
397             // No includes supplied, so set it to 'matches all'
398             includes = new String[1];
399             includes[0] = "**";
400         }
401         if ( excludes == null )
402         {
403             excludes = new String[0];
404         }
405     }
406 
407     protected void setupMatchPatterns()
408     {
409         includesPatterns = MatchPatterns.from( includes );
410         excludesPatterns = MatchPatterns.from( excludes );
411     }
412 
413     @Override
414     public void setFilenameComparator( Comparator<String> filenameComparator )
415     {
416         this.filenameComparator = filenameComparator;
417     }
418 }