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 }