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: **/*~, **/#*#, **/.#*, **/%*%, **/._*</li>
34 * <li>CVS: **/CVS, **/CVS/**, **/.cvsignore</li>
35 * <li>RCS: **/RCS, **/RCS/**</li>
36 * <li>SCCS: **/SCCS, **/SCCS/**</li>
37 * <li>VSSercer: **/vssver.scc</li>
38 * <li>MKS: **/project.pj</li>
39 * <li>SVN: **/.svn, **/.svn/**</li>
40 * <li>GNU: **/.arch-ids, **/.arch-ids/**</li>
41 * <li>Bazaar: **/.bzr, **/.bzr/**</li>
42 * <li>SurroundSCM: **/.MySCMServerInfo</li>
43 * <li>Mac: **/.DS_Store</li>
44 * <li>Serena Dimension: **/.metadata, **/.metadata/**</li>
45 * <li>Mercurial: **/.hg, **/.hg/**</li>
46 * <li>Git: **/.git, **/.git/**</li>
47 * <li>Bitkeeper: **/BitKeeper, **/BitKeeper/**, **/ChangeSet,
48 * **/ChangeSet/**</li>
49 * <li>Darcs: **/_darcs, **/_darcs/**, **/.darcsrepo,
50 * **/.darcsrepo/****/-darcs-backup*, **/.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 }