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