1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant;
  18. import java.io.File;
  19. import java.io.IOException;
  20. import java.util.Arrays;
  21. import java.util.Enumeration;
  22. import java.util.HashMap;
  23. import java.util.HashSet;
  24. import java.util.Hashtable;
  25. import java.util.Map;
  26. import java.util.Set;
  27. import java.util.Vector;
  28. import org.apache.tools.ant.taskdefs.condition.Os;
  29. import org.apache.tools.ant.types.Resource;
  30. import org.apache.tools.ant.types.ResourceFactory;
  31. import org.apache.tools.ant.types.selectors.FileSelector;
  32. import org.apache.tools.ant.types.selectors.SelectorScanner;
  33. import org.apache.tools.ant.types.selectors.SelectorUtils;
  34. import org.apache.tools.ant.util.FileUtils;
  35. /**
  36. * Class for scanning a directory for files/directories which match certain
  37. * criteria.
  38. * <p>
  39. * These criteria consist of selectors and patterns which have been specified.
  40. * With the selectors you can select which files you want to have included.
  41. * Files which are not selected are excluded. With patterns you can include
  42. * or exclude files based on their filename.
  43. * <p>
  44. * The idea is simple. A given directory is recursively scanned for all files
  45. * and directories. Each file/directory is matched against a set of selectors,
  46. * including special support for matching against filenames with include and
  47. * and exclude patterns. Only files/directories which match at least one
  48. * pattern of the include pattern list or other file selector, and don't match
  49. * any pattern of the exclude pattern list or fail to match against a required
  50. * selector will be placed in the list of files/directories found.
  51. * <p>
  52. * When no list of include patterns is supplied, "**" will be used, which
  53. * means that everything will be matched. When no list of exclude patterns is
  54. * supplied, an empty list is used, such that nothing will be excluded. When
  55. * no selectors are supplied, none are applied.
  56. * <p>
  57. * The filename pattern matching is done as follows:
  58. * The name to be matched is split up in path segments. A path segment is the
  59. * name of a directory or file, which is bounded by
  60. * <code>File.separator</code> ('/' under UNIX, '\' under Windows).
  61. * For example, "abc/def/ghi/xyz.java" is split up in the segments "abc",
  62. * "def","ghi" and "xyz.java".
  63. * The same is done for the pattern against which should be matched.
  64. * <p>
  65. * The segments of the name and the pattern are then matched against each
  66. * other. When '**' is used for a path segment in the pattern, it matches
  67. * zero or more path segments of the name.
  68. * <p>
  69. * There is a special case regarding the use of <code>File.separator</code>s
  70. * at the beginning of the pattern and the string to match:<br>
  71. * When a pattern starts with a <code>File.separator</code>, the string
  72. * to match must also start with a <code>File.separator</code>.
  73. * When a pattern does not start with a <code>File.separator</code>, the
  74. * string to match may not start with a <code>File.separator</code>.
  75. * When one of these rules is not obeyed, the string will not
  76. * match.
  77. * <p>
  78. * When a name path segment is matched against a pattern path segment, the
  79. * following special characters can be used:<br>
  80. * '*' matches zero or more characters<br>
  81. * '?' matches one character.
  82. * <p>
  83. * Examples:
  84. * <p>
  85. * "**\*.class" matches all .class files/dirs in a directory tree.
  86. * <p>
  87. * "test\a??.java" matches all files/dirs which start with an 'a', then two
  88. * more characters and then ".java", in a directory called test.
  89. * <p>
  90. * "**" matches everything in a directory tree.
  91. * <p>
  92. * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where
  93. * there is a parent directory called test (e.g. "abc\test\def\ghi\XYZ123").
  94. * <p>
  95. * Case sensitivity may be turned off if necessary. By default, it is
  96. * turned on.
  97. * <p>
  98. * Example of usage:
  99. * <pre>
  100. * String[] includes = {"**\\*.class"};
  101. * String[] excludes = {"modules\\*\\**"};
  102. * ds.setIncludes(includes);
  103. * ds.setExcludes(excludes);
  104. * ds.setBasedir(new File("test"));
  105. * ds.setCaseSensitive(true);
  106. * ds.scan();
  107. *
  108. * System.out.println("FILES:");
  109. * String[] files = ds.getIncludedFiles();
  110. * for (int i = 0; i < files.length; i++) {
  111. * System.out.println(files[i]);
  112. * }
  113. * </pre>
  114. * This will scan a directory called test for .class files, but excludes all
  115. * files in all proper subdirectories of a directory called "modules"
  116. *
  117. */
  118. public class DirectoryScanner
  119. implements FileScanner, SelectorScanner, ResourceFactory {
  120. /** Is OpenVMS the operating system we're running on? */
  121. private static final boolean ON_VMS = Os.isFamily("openvms");
  122. /**
  123. * Patterns which should be excluded by default.
  124. *
  125. * <p>Note that you can now add patterns to the list of default
  126. * excludes. Added patterns will not become part of this array
  127. * that has only been kept around for backwards compatibility
  128. * reasons.</p>
  129. *
  130. * @deprecated use the {@link #getDefaultExcludes
  131. * getDefaultExcludes} method instead.
  132. */
  133. protected static final String[] DEFAULTEXCLUDES = {
  134. // Miscellaneous typical temporary files
  135. "**/*~",
  136. "**/#*#",
  137. "**/.#*",
  138. "**/%*%",
  139. "**/._*",
  140. // CVS
  141. "**/CVS",
  142. "**/CVS/**",
  143. "**/.cvsignore",
  144. // SCCS
  145. "**/SCCS",
  146. "**/SCCS/**",
  147. // Visual SourceSafe
  148. "**/vssver.scc",
  149. // Subversion
  150. "**/.svn",
  151. "**/.svn/**",
  152. // Mac
  153. "**/.DS_Store"
  154. };
  155. /**
  156. * Patterns which should be excluded by default.
  157. *
  158. * @see #addDefaultExcludes()
  159. */
  160. private static Vector defaultExcludes = new Vector();
  161. static {
  162. resetDefaultExcludes();
  163. }
  164. /** The base directory to be scanned. */
  165. protected File basedir;
  166. /** The patterns for the files to be included. */
  167. protected String[] includes;
  168. /** The patterns for the files to be excluded. */
  169. protected String[] excludes;
  170. /** Selectors that will filter which files are in our candidate list. */
  171. protected FileSelector[] selectors = null;
  172. /** The files which matched at least one include and no excludes
  173. * and were selected.
  174. */
  175. protected Vector filesIncluded;
  176. /** The files which did not match any includes or selectors. */
  177. protected Vector filesNotIncluded;
  178. /**
  179. * The files which matched at least one include and at least
  180. * one exclude.
  181. */
  182. protected Vector filesExcluded;
  183. /** The directories which matched at least one include and no excludes
  184. * and were selected.
  185. */
  186. protected Vector dirsIncluded;
  187. /** The directories which were found and did not match any includes. */
  188. protected Vector dirsNotIncluded;
  189. /**
  190. * The directories which matched at least one include and at least one
  191. * exclude.
  192. */
  193. protected Vector dirsExcluded;
  194. /** The files which matched at least one include and no excludes and
  195. * which a selector discarded.
  196. */
  197. protected Vector filesDeselected;
  198. /** The directories which matched at least one include and no excludes
  199. * but which a selector discarded.
  200. */
  201. protected Vector dirsDeselected;
  202. /** Whether or not our results were built by a slow scan. */
  203. protected boolean haveSlowResults = false;
  204. /**
  205. * Whether or not the file system should be treated as a case sensitive
  206. * one.
  207. */
  208. protected boolean isCaseSensitive = true;
  209. /**
  210. * Whether or not symbolic links should be followed.
  211. *
  212. * @since Ant 1.5
  213. */
  214. private boolean followSymlinks = true;
  215. /** Helper. */
  216. private static final FileUtils FILE_UTILS = FileUtils.newFileUtils();
  217. /** Whether or not everything tested so far has been included. */
  218. protected boolean everythingIncluded = true;
  219. /**
  220. * Sole constructor.
  221. */
  222. public DirectoryScanner() {
  223. }
  224. /**
  225. * Tests whether or not a given path matches the start of a given
  226. * pattern up to the first "**".
  227. * <p>
  228. * This is not a general purpose test and should only be used if you
  229. * can live with false positives. For example, <code>pattern=**\a</code>
  230. * and <code>str=b</code> will yield <code>true</code>.
  231. *
  232. * @param pattern The pattern to match against. Must not be
  233. * <code>null</code>.
  234. * @param str The path to match, as a String. Must not be
  235. * <code>null</code>.
  236. *
  237. * @return whether or not a given path matches the start of a given
  238. * pattern up to the first "**".
  239. */
  240. protected static boolean matchPatternStart(String pattern, String str) {
  241. return SelectorUtils.matchPatternStart(pattern, str);
  242. }
  243. /**
  244. * Tests whether or not a given path matches the start of a given
  245. * pattern up to the first "**".
  246. * <p>
  247. * This is not a general purpose test and should only be used if you
  248. * can live with false positives. For example, <code>pattern=**\a</code>
  249. * and <code>str=b</code> will yield <code>true</code>.
  250. *
  251. * @param pattern The pattern to match against. Must not be
  252. * <code>null</code>.
  253. * @param str The path to match, as a String. Must not be
  254. * <code>null</code>.
  255. * @param isCaseSensitive Whether or not matching should be performed
  256. * case sensitively.
  257. *
  258. * @return whether or not a given path matches the start of a given
  259. * pattern up to the first "**".
  260. */
  261. protected static boolean matchPatternStart(String pattern, String str,
  262. boolean isCaseSensitive) {
  263. return SelectorUtils.matchPatternStart(pattern, str, isCaseSensitive);
  264. }
  265. /**
  266. * Tests whether or not a given path matches a given pattern.
  267. *
  268. * @param pattern The pattern to match against. Must not be
  269. * <code>null</code>.
  270. * @param str The path to match, as a String. Must not be
  271. * <code>null</code>.
  272. *
  273. * @return <code>true</code> if the pattern matches against the string,
  274. * or <code>false</code> otherwise.
  275. */
  276. protected static boolean matchPath(String pattern, String str) {
  277. return SelectorUtils.matchPath(pattern, str);
  278. }
  279. /**
  280. * Tests whether or not a given path matches a given pattern.
  281. *
  282. * @param pattern The pattern to match against. Must not be
  283. * <code>null</code>.
  284. * @param str The path to match, as a String. Must not be
  285. * <code>null</code>.
  286. * @param isCaseSensitive Whether or not matching should be performed
  287. * case sensitively.
  288. *
  289. * @return <code>true</code> if the pattern matches against the string,
  290. * or <code>false</code> otherwise.
  291. */
  292. protected static boolean matchPath(String pattern, String str,
  293. boolean isCaseSensitive) {
  294. return SelectorUtils.matchPath(pattern, str, isCaseSensitive);
  295. }
  296. /**
  297. * Tests whether or not a string matches against a pattern.
  298. * The pattern may contain two special characters:<br>
  299. * '*' means zero or more characters<br>
  300. * '?' means one and only one character
  301. *
  302. * @param pattern The pattern to match against.
  303. * Must not be <code>null</code>.
  304. * @param str The string which must be matched against the pattern.
  305. * Must not be <code>null</code>.
  306. *
  307. * @return <code>true</code> if the string matches against the pattern,
  308. * or <code>false</code> otherwise.
  309. */
  310. public static boolean match(String pattern, String str) {
  311. return SelectorUtils.match(pattern, str);
  312. }
  313. /**
  314. * Tests whether or not a string matches against a pattern.
  315. * The pattern may contain two special characters:<br>
  316. * '*' means zero or more characters<br>
  317. * '?' means one and only one character
  318. *
  319. * @param pattern The pattern to match against.
  320. * Must not be <code>null</code>.
  321. * @param str The string which must be matched against the pattern.
  322. * Must not be <code>null</code>.
  323. * @param isCaseSensitive Whether or not matching should be performed
  324. * case sensitively.
  325. *
  326. *
  327. * @return <code>true</code> if the string matches against the pattern,
  328. * or <code>false</code> otherwise.
  329. */
  330. protected static boolean match(String pattern, String str,
  331. boolean isCaseSensitive) {
  332. return SelectorUtils.match(pattern, str, isCaseSensitive);
  333. }
  334. /**
  335. * Get the list of patterns that should be excluded by default.
  336. *
  337. * @return An array of <code>String</code> based on the current
  338. * contents of the <code>defaultExcludes</code>
  339. * <code>Vector</code>.
  340. *
  341. * @since Ant 1.6
  342. */
  343. public static String[] getDefaultExcludes() {
  344. return (String[]) defaultExcludes.toArray(new String[defaultExcludes
  345. .size()]);
  346. }
  347. /**
  348. * Add a pattern to the default excludes unless it is already a
  349. * default exclude.
  350. *
  351. * @param s A string to add as an exclude pattern.
  352. * @return <code>true</code> if the string was added
  353. * <code>false</code> if it already
  354. * existed.
  355. *
  356. * @since Ant 1.6
  357. */
  358. public static boolean addDefaultExclude(String s) {
  359. if (defaultExcludes.indexOf(s) == -1) {
  360. defaultExcludes.add(s);
  361. return true;
  362. }
  363. return false;
  364. }
  365. /**
  366. * Remove a string if it is a default exclude.
  367. *
  368. * @param s The string to attempt to remove.
  369. * @return <code>true</code> if <code>s</code> was a default
  370. * exclude (and thus was removed),
  371. * <code>false</code> if <code>s</code> was not
  372. * in the default excludes list to begin with
  373. *
  374. * @since Ant 1.6
  375. */
  376. public static boolean removeDefaultExclude(String s) {
  377. return defaultExcludes.remove(s);
  378. }
  379. /**
  380. * Go back to the hard wired default exclude patterns
  381. *
  382. * @since Ant 1.6
  383. */
  384. public static void resetDefaultExcludes() {
  385. defaultExcludes = new Vector();
  386. for (int i = 0; i < DEFAULTEXCLUDES.length; i++) {
  387. defaultExcludes.add(DEFAULTEXCLUDES[i]);
  388. }
  389. }
  390. /**
  391. * Sets the base directory to be scanned. This is the directory which is
  392. * scanned recursively. All '/' and '\' characters are replaced by
  393. * <code>File.separatorChar</code>, so the separator used need not match
  394. * <code>File.separatorChar</code>.
  395. *
  396. * @param basedir The base directory to scan.
  397. * Must not be <code>null</code>.
  398. */
  399. public void setBasedir(String basedir) {
  400. setBasedir(new File(basedir.replace('/', File.separatorChar).replace(
  401. '\\', File.separatorChar)));
  402. }
  403. /**
  404. * Sets the base directory to be scanned. This is the directory which is
  405. * scanned recursively.
  406. *
  407. * @param basedir The base directory for scanning.
  408. * Should not be <code>null</code>.
  409. */
  410. public void setBasedir(File basedir) {
  411. this.basedir = basedir;
  412. }
  413. /**
  414. * Returns the base directory to be scanned.
  415. * This is the directory which is scanned recursively.
  416. *
  417. * @return the base directory to be scanned
  418. */
  419. public File getBasedir() {
  420. return basedir;
  421. }
  422. /**
  423. * Find out whether include exclude patterns are matched in a
  424. * case sensitive way
  425. * @return whether or not the scanning is case sensitive
  426. * @since ant 1.6
  427. */
  428. public boolean isCaseSensitive() {
  429. return isCaseSensitive;
  430. }
  431. /**
  432. * Sets whether or not include and exclude patterns are matched
  433. * in a case sensitive way
  434. *
  435. * @param isCaseSensitive whether or not the file system should be
  436. * regarded as a case sensitive one
  437. */
  438. public void setCaseSensitive(boolean isCaseSensitive) {
  439. this.isCaseSensitive = isCaseSensitive;
  440. }
  441. /**
  442. * gets whether or not a DirectoryScanner follows symbolic links
  443. *
  444. * @return flag indicating whether symbolic links should be followed
  445. *
  446. * @since ant 1.6
  447. */
  448. public boolean isFollowSymlinks() {
  449. return followSymlinks;
  450. }
  451. /**
  452. * Sets whether or not symbolic links should be followed.
  453. *
  454. * @param followSymlinks whether or not symbolic links should be followed
  455. */
  456. public void setFollowSymlinks(boolean followSymlinks) {
  457. this.followSymlinks = followSymlinks;
  458. }
  459. /**
  460. * Sets the list of include patterns to use. All '/' and '\' characters
  461. * are replaced by <code>File.separatorChar</code>, so the separator used
  462. * need not match <code>File.separatorChar</code>.
  463. * <p>
  464. * When a pattern ends with a '/' or '\', "**" is appended.
  465. *
  466. * @param includes A list of include patterns.
  467. * May be <code>null</code>, indicating that all files
  468. * should be included. If a non-<code>null</code>
  469. * list is given, all elements must be
  470. * non-<code>null</code>.
  471. */
  472. public void setIncludes(String[] includes) {
  473. if (includes == null) {
  474. this.includes = null;
  475. } else {
  476. this.includes = new String[includes.length];
  477. for (int i = 0; i < includes.length; i++) {
  478. String pattern;
  479. pattern = includes[i].replace('/', File.separatorChar).replace(
  480. '\\', File.separatorChar);
  481. if (pattern.endsWith(File.separator)) {
  482. pattern += "**";
  483. }
  484. this.includes[i] = pattern;
  485. }
  486. }
  487. }
  488. /**
  489. * Sets the list of exclude patterns to use. All '/' and '\' characters
  490. * are replaced by <code>File.separatorChar</code>, so the separator used
  491. * need not match <code>File.separatorChar</code>.
  492. * <p>
  493. * When a pattern ends with a '/' or '\', "**" is appended.
  494. *
  495. * @param excludes A list of exclude patterns.
  496. * May be <code>null</code>, indicating that no files
  497. * should be excluded. If a non-<code>null</code> list is
  498. * given, all elements must be non-<code>null</code>.
  499. */
  500. public void setExcludes(String[] excludes) {
  501. if (excludes == null) {
  502. this.excludes = null;
  503. } else {
  504. this.excludes = new String[excludes.length];
  505. for (int i = 0; i < excludes.length; i++) {
  506. String pattern;
  507. pattern = excludes[i].replace('/', File.separatorChar).replace(
  508. '\\', File.separatorChar);
  509. if (pattern.endsWith(File.separator)) {
  510. pattern += "**";
  511. }
  512. this.excludes[i] = pattern;
  513. }
  514. }
  515. }
  516. /**
  517. * Sets the selectors that will select the filelist.
  518. *
  519. * @param selectors specifies the selectors to be invoked on a scan
  520. */
  521. public void setSelectors(FileSelector[] selectors) {
  522. this.selectors = selectors;
  523. }
  524. /**
  525. * Returns whether or not the scanner has included all the files or
  526. * directories it has come across so far.
  527. *
  528. * @return <code>true</code> if all files and directories which have
  529. * been found so far have been included.
  530. */
  531. public boolean isEverythingIncluded() {
  532. return everythingIncluded;
  533. }
  534. /**
  535. * Scans the base directory for files which match at least one include
  536. * pattern and don't match any exclude patterns. If there are selectors
  537. * then the files must pass muster there, as well.
  538. *
  539. * @exception IllegalStateException if the base directory was set
  540. * incorrectly (i.e. if it is <code>null</code>, doesn't exist,
  541. * or isn't a directory).
  542. */
  543. public void scan() throws IllegalStateException {
  544. if (basedir == null) {
  545. throw new IllegalStateException("No basedir set");
  546. }
  547. if (!basedir.exists()) {
  548. throw new IllegalStateException("basedir " + basedir
  549. + " does not exist");
  550. }
  551. if (!basedir.isDirectory()) {
  552. throw new IllegalStateException("basedir " + basedir
  553. + " is not a directory");
  554. }
  555. if (includes == null) {
  556. // No includes supplied, so set it to 'matches all'
  557. includes = new String[1];
  558. includes[0] = "**";
  559. }
  560. if (excludes == null) {
  561. excludes = new String[0];
  562. }
  563. filesIncluded = new Vector();
  564. filesNotIncluded = new Vector();
  565. filesExcluded = new Vector();
  566. filesDeselected = new Vector();
  567. dirsIncluded = new Vector();
  568. dirsNotIncluded = new Vector();
  569. dirsExcluded = new Vector();
  570. dirsDeselected = new Vector();
  571. if (isIncluded("")) {
  572. if (!isExcluded("")) {
  573. if (isSelected("", basedir)) {
  574. dirsIncluded.addElement("");
  575. } else {
  576. dirsDeselected.addElement("");
  577. }
  578. } else {
  579. dirsExcluded.addElement("");
  580. }
  581. } else {
  582. dirsNotIncluded.addElement("");
  583. }
  584. checkIncludePatterns();
  585. clearCaches();
  586. }
  587. /**
  588. * this routine is actually checking all the include patterns in
  589. * order to avoid scanning everything under base dir
  590. * @since ant1.6
  591. */
  592. private void checkIncludePatterns() {
  593. Hashtable newroots = new Hashtable();
  594. // put in the newroots vector the include patterns without
  595. // wildcard tokens
  596. for (int icounter = 0; icounter < includes.length; icounter++) {
  597. String newpattern =
  598. SelectorUtils.rtrimWildcardTokens(includes[icounter]);
  599. newroots.put(newpattern, includes[icounter]);
  600. }
  601. if (newroots.containsKey("")) {
  602. // we are going to scan everything anyway
  603. scandir(basedir, "", true);
  604. } else {
  605. // only scan directories that can include matched files or
  606. // directories
  607. Enumeration enum2 = newroots.keys();
  608. File canonBase = null;
  609. try {
  610. canonBase = basedir.getCanonicalFile();
  611. } catch (IOException ex) {
  612. throw new BuildException(ex);
  613. }
  614. while (enum2.hasMoreElements()) {
  615. String currentelement = (String) enum2.nextElement();
  616. String originalpattern = (String) newroots.get(currentelement);
  617. File myfile = new File(basedir, currentelement);
  618. if (myfile.exists()) {
  619. // may be on a case insensitive file system. We want
  620. // the results to show what's really on the disk, so
  621. // we need to double check.
  622. try {
  623. File canonFile = myfile.getCanonicalFile();
  624. String path = FILE_UTILS.removeLeadingPath(canonBase,
  625. canonFile);
  626. if (!path.equals(currentelement) || ON_VMS) {
  627. myfile = findFile(basedir, currentelement);
  628. if (myfile != null) {
  629. currentelement =
  630. FILE_UTILS.removeLeadingPath(basedir,
  631. myfile);
  632. }
  633. }
  634. } catch (IOException ex) {
  635. throw new BuildException(ex);
  636. }
  637. }
  638. if ((myfile == null || !myfile.exists()) && !isCaseSensitive) {
  639. File f = findFileCaseInsensitive(basedir, currentelement);
  640. if (f.exists()) {
  641. // adapt currentelement to the case we've
  642. // actually found
  643. currentelement = FILE_UTILS.removeLeadingPath(basedir,
  644. f);
  645. myfile = f;
  646. }
  647. }
  648. if (myfile != null && myfile.exists()) {
  649. if (!followSymlinks
  650. && isSymlink(basedir, currentelement)) {
  651. continue;
  652. }
  653. if (myfile.isDirectory()) {
  654. if (isIncluded(currentelement)
  655. && currentelement.length() > 0) {
  656. accountForIncludedDir(currentelement, myfile, true);
  657. } else {
  658. if (currentelement.length() > 0) {
  659. if (currentelement.charAt(currentelement
  660. .length() - 1)
  661. != File.separatorChar) {
  662. currentelement =
  663. currentelement + File.separatorChar;
  664. }
  665. }
  666. scandir(myfile, currentelement, true);
  667. }
  668. } else {
  669. if (isCaseSensitive
  670. && originalpattern.equals(currentelement)) {
  671. accountForIncludedFile(currentelement, myfile);
  672. } else if (!isCaseSensitive
  673. && originalpattern
  674. .equalsIgnoreCase(currentelement)) {
  675. accountForIncludedFile(currentelement, myfile);
  676. }
  677. }
  678. }
  679. }
  680. }
  681. }
  682. /**
  683. * Top level invocation for a slow scan. A slow scan builds up a full
  684. * list of excluded/included files/directories, whereas a fast scan
  685. * will only have full results for included files, as it ignores
  686. * directories which can't possibly hold any included files/directories.
  687. * <p>
  688. * Returns immediately if a slow scan has already been completed.
  689. */
  690. protected void slowScan() {
  691. if (haveSlowResults) {
  692. return;
  693. }
  694. String[] excl = new String[dirsExcluded.size()];
  695. dirsExcluded.copyInto(excl);
  696. String[] notIncl = new String[dirsNotIncluded.size()];
  697. dirsNotIncluded.copyInto(notIncl);
  698. for (int i = 0; i < excl.length; i++) {
  699. if (!couldHoldIncluded(excl[i])) {
  700. scandir(new File(basedir, excl[i]),
  701. excl[i] + File.separator, false);
  702. }
  703. }
  704. for (int i = 0; i < notIncl.length; i++) {
  705. if (!couldHoldIncluded(notIncl[i])) {
  706. scandir(new File(basedir, notIncl[i]),
  707. notIncl[i] + File.separator, false);
  708. }
  709. }
  710. haveSlowResults = true;
  711. }
  712. /**
  713. * Scans the given directory for files and directories. Found files and
  714. * directories are placed in their respective collections, based on the
  715. * matching of includes, excludes, and the selectors. When a directory
  716. * is found, it is scanned recursively.
  717. *
  718. * @param dir The directory to scan. Must not be <code>null</code>.
  719. * @param vpath The path relative to the base directory (needed to
  720. * prevent problems with an absolute path when using
  721. * dir). Must not be <code>null</code>.
  722. * @param fast Whether or not this call is part of a fast scan.
  723. *
  724. * @see #filesIncluded
  725. * @see #filesNotIncluded
  726. * @see #filesExcluded
  727. * @see #dirsIncluded
  728. * @see #dirsNotIncluded
  729. * @see #dirsExcluded
  730. * @see #slowScan
  731. */
  732. protected void scandir(File dir, String vpath, boolean fast) {
  733. if (dir == null) {
  734. throw new BuildException("dir must not be null.");
  735. } else if (!dir.exists()) {
  736. throw new BuildException(dir + " doesn't exists.");
  737. } else if (!dir.isDirectory()) {
  738. throw new BuildException(dir + " is not a directory.");
  739. }
  740. // avoid double scanning of directories, can only happen in fast mode
  741. if (fast && hasBeenScanned(vpath)) {
  742. return;
  743. }
  744. String[] newfiles = dir.list();
  745. if (newfiles == null) {
  746. /*
  747. * two reasons are mentioned in the API docs for File.list
  748. * (1) dir is not a directory. This is impossible as
  749. * we wouldn't get here in this case.
  750. * (2) an IO error occurred (why doesn't it throw an exception
  751. * then???)
  752. */
  753. throw new BuildException("IO error scanning directory "
  754. + dir.getAbsolutePath());
  755. }
  756. if (!followSymlinks) {
  757. Vector noLinks = new Vector();
  758. for (int i = 0; i < newfiles.length; i++) {
  759. try {
  760. if (FILE_UTILS.isSymbolicLink(dir, newfiles[i])) {
  761. String name = vpath + newfiles[i];
  762. File file = new File(dir, newfiles[i]);
  763. if (file.isDirectory()) {
  764. dirsExcluded.addElement(name);
  765. } else {
  766. filesExcluded.addElement(name);
  767. }
  768. } else {
  769. noLinks.addElement(newfiles[i]);
  770. }
  771. } catch (IOException ioe) {
  772. String msg = "IOException caught while checking "
  773. + "for links, couldn't get canonical path!";
  774. // will be caught and redirected to Ant's logging system
  775. System.err.println(msg);
  776. noLinks.addElement(newfiles[i]);
  777. }
  778. }
  779. newfiles = new String[noLinks.size()];
  780. noLinks.copyInto(newfiles);
  781. }
  782. for (int i = 0; i < newfiles.length; i++) {
  783. String name = vpath + newfiles[i];
  784. File file = new File(dir, newfiles[i]);
  785. if (file.isDirectory()) {
  786. if (isIncluded(name)) {
  787. accountForIncludedDir(name, file, fast);
  788. } else {
  789. everythingIncluded = false;
  790. dirsNotIncluded.addElement(name);
  791. if (fast && couldHoldIncluded(name)) {
  792. scandir(file, name + File.separator, fast);
  793. }
  794. }
  795. if (!fast) {
  796. scandir(file, name + File.separator, fast);
  797. }
  798. } else if (file.isFile()) {
  799. if (isIncluded(name)) {
  800. accountForIncludedFile(name, file);
  801. } else {
  802. everythingIncluded = false;
  803. filesNotIncluded.addElement(name);
  804. }
  805. }
  806. }
  807. }
  808. /**
  809. * process included file
  810. * @param name path of the file relative to the directory of the fileset
  811. * @param file included file
  812. */
  813. private void accountForIncludedFile(String name, File file) {
  814. if (!filesIncluded.contains(name)
  815. && !filesExcluded.contains(name)
  816. && !filesDeselected.contains(name)) {
  817. if (!isExcluded(name)) {
  818. if (isSelected(name, file)) {
  819. filesIncluded.addElement(name);
  820. } else {
  821. everythingIncluded = false;
  822. filesDeselected.addElement(name);
  823. }
  824. } else {
  825. everythingIncluded = false;
  826. filesExcluded.addElement(name);
  827. }
  828. }
  829. }
  830. /**
  831. *
  832. * @param name path of the directory relative to the directory of
  833. * the fileset
  834. * @param file directory as file
  835. * @param fast
  836. */
  837. private void accountForIncludedDir(String name, File file, boolean fast) {
  838. if (!dirsIncluded.contains(name)
  839. && !dirsExcluded.contains(name)
  840. && !dirsDeselected.contains(name)) {
  841. if (!isExcluded(name)) {
  842. if (isSelected(name, file)) {
  843. dirsIncluded.addElement(name);
  844. if (fast) {
  845. scandir(file, name + File.separator, fast);
  846. }
  847. } else {
  848. everythingIncluded = false;
  849. dirsDeselected.addElement(name);
  850. if (fast && couldHoldIncluded(name)) {
  851. scandir(file, name + File.separator, fast);
  852. }
  853. }
  854. } else {
  855. everythingIncluded = false;
  856. dirsExcluded.addElement(name);
  857. if (fast && couldHoldIncluded(name)) {
  858. scandir(file, name + File.separator, fast);
  859. }
  860. }
  861. }
  862. }
  863. /**
  864. * Tests whether or not a name matches against at least one include
  865. * pattern.
  866. *
  867. * @param name The name to match. Must not be <code>null</code>.
  868. * @return <code>true</code> when the name matches against at least one
  869. * include pattern, or <code>false</code> otherwise.
  870. */
  871. protected boolean isIncluded(String name) {
  872. for (int i = 0; i < includes.length; i++) {
  873. if (matchPath(includes[i], name, isCaseSensitive)) {
  874. return true;
  875. }
  876. }
  877. return false;
  878. }
  879. /**
  880. * Tests whether or not a name matches the start of at least one include
  881. * pattern.
  882. *
  883. * @param name The name to match. Must not be <code>null</code>.
  884. * @return <code>true</code> when the name matches against the start of at
  885. * least one include pattern, or <code>false</code> otherwise.
  886. */
  887. protected boolean couldHoldIncluded(String name) {
  888. for (int i = 0; i < includes.length; i++) {
  889. if (matchPatternStart(includes[i], name, isCaseSensitive)) {
  890. if (isMorePowerfulThanExcludes(name, includes[i])) {
  891. return true;
  892. }
  893. }
  894. }
  895. return false;
  896. }
  897. /**
  898. * find out whether one particular include pattern is more powerful
  899. * than all the excludes
  900. * note : the power comparison is based on the length of the include pattern
  901. * and of the exclude patterns without the wildcards
  902. * ideally the comparison should be done based on the depth
  903. * of the match, that is to say how many file separators have been matched
  904. * before the first ** or the end of the pattern
  905. *
  906. * IMPORTANT : this function should return false "with care"
  907. *
  908. * @param name the relative path that one want to test
  909. * @param includepattern one include pattern
  910. * @return true if there is no exclude pattern more powerful than this include pattern
  911. * @since ant1.6
  912. */
  913. private boolean isMorePowerfulThanExcludes(String name, String includepattern) {
  914. String soughtexclude = name + File.separator + "**";
  915. for (int counter = 0; counter < excludes.length; counter++) {
  916. if (excludes[counter].equals(soughtexclude)) {
  917. return false;
  918. }
  919. }
  920. return true;
  921. }
  922. /**
  923. * Tests whether or not a name matches against at least one exclude
  924. * pattern.
  925. *
  926. * @param name The name to match. Must not be <code>null</code>.
  927. * @return <code>true</code> when the name matches against at least one
  928. * exclude pattern, or <code>false</code> otherwise.
  929. */
  930. protected boolean isExcluded(String name) {
  931. for (int i = 0; i < excludes.length; i++) {
  932. if (matchPath(excludes[i], name, isCaseSensitive)) {
  933. return true;
  934. }
  935. }
  936. return false;
  937. }
  938. /**
  939. * Tests whether a name should be selected.
  940. *
  941. * @param name the filename to check for selecting
  942. * @param file the java.io.File object for this filename
  943. * @return <code>false</code> when the selectors says that the file
  944. * should not be selected, <code>true</code> otherwise.
  945. */
  946. protected boolean isSelected(String name, File file) {
  947. if (selectors != null) {
  948. for (int i = 0; i < selectors.length; i++) {
  949. if (!selectors[i].isSelected(basedir, name, file)) {
  950. return false;
  951. }
  952. }
  953. }
  954. return true;
  955. }
  956. /**
  957. * Returns the names of the files which matched at least one of the
  958. * include patterns and none of the exclude patterns.
  959. * The names are relative to the base directory.
  960. *
  961. * @return the names of the files which matched at least one of the
  962. * include patterns and none of the exclude patterns.
  963. */
  964. public String[] getIncludedFiles() {
  965. String[] files = new String[filesIncluded.size()];
  966. filesIncluded.copyInto(files);
  967. Arrays.sort(files);
  968. return files;
  969. }
  970. /**
  971. * Returns the names of the files which matched none of the include
  972. * patterns. The names are relative to the base directory. This involves
  973. * performing a slow scan if one has not already been completed.
  974. *
  975. * @return the names of the files which matched none of the include
  976. * patterns.
  977. *
  978. * @see #slowScan
  979. */
  980. public String[] getNotIncludedFiles() {
  981. slowScan();
  982. String[] files = new String[filesNotIncluded.size()];
  983. filesNotIncluded.copyInto(files);
  984. return files;
  985. }
  986. /**
  987. * Returns the names of the files which matched at least one of the
  988. * include patterns and at least one of the exclude patterns.
  989. * The names are relative to the base directory. This involves
  990. * performing a slow scan if one has not already been completed.
  991. *
  992. * @return the names of the files which matched at least one of the
  993. * include patterns and at least one of the exclude patterns.
  994. *
  995. * @see #slowScan
  996. */
  997. public String[] getExcludedFiles() {
  998. slowScan();
  999. String[] files = new String[filesExcluded.size()];
  1000. filesExcluded.copyInto(files);
  1001. return files;
  1002. }
  1003. /**
  1004. * <p>Returns the names of the files which were selected out and
  1005. * therefore not ultimately included.</p>
  1006. *
  1007. * <p>The names are relative to the base directory. This involves
  1008. * performing a slow scan if one has not already been completed.</p>
  1009. *
  1010. * @return the names of the files which were deselected.
  1011. *
  1012. * @see #slowScan
  1013. */
  1014. public String[] getDeselectedFiles() {
  1015. slowScan();
  1016. String[] files = new String[filesDeselected.size()];
  1017. filesDeselected.copyInto(files);
  1018. return files;
  1019. }
  1020. /**
  1021. * Returns the names of the directories which matched at least one of the
  1022. * include patterns and none of the exclude patterns.
  1023. * The names are relative to the base directory.
  1024. *
  1025. * @return the names of the directories which matched at least one of the
  1026. * include patterns and none of the exclude patterns.
  1027. */
  1028. public String[] getIncludedDirectories() {
  1029. String[] directories = new String[dirsIncluded.size()];
  1030. dirsIncluded.copyInto(directories);
  1031. Arrays.sort(directories);
  1032. return directories;
  1033. }
  1034. /**
  1035. * Returns the names of the directories which matched none of the include
  1036. * patterns. The names are relative to the base directory. This involves
  1037. * performing a slow scan if one has not already been completed.
  1038. *
  1039. * @return the names of the directories which matched none of the include
  1040. * patterns.
  1041. *
  1042. * @see #slowScan
  1043. */
  1044. public String[] getNotIncludedDirectories() {
  1045. slowScan();
  1046. String[] directories = new String[dirsNotIncluded.size()];
  1047. dirsNotIncluded.copyInto(directories);
  1048. return directories;
  1049. }
  1050. /**
  1051. * Returns the names of the directories which matched at least one of the
  1052. * include patterns and at least one of the exclude patterns.
  1053. * The names are relative to the base directory. This involves
  1054. * performing a slow scan if one has not already been completed.
  1055. *
  1056. * @return the names of the directories which matched at least one of the
  1057. * include patterns and at least one of the exclude patterns.
  1058. *
  1059. * @see #slowScan
  1060. */
  1061. public String[] getExcludedDirectories() {
  1062. slowScan();
  1063. String[] directories = new String[dirsExcluded.size()];
  1064. dirsExcluded.copyInto(directories);
  1065. return directories;
  1066. }
  1067. /**
  1068. * <p>Returns the names of the directories which were selected out and
  1069. * therefore not ultimately included.</p>
  1070. *
  1071. * <p>The names are relative to the base directory. This involves
  1072. * performing a slow scan if one has not already been completed.</p>
  1073. *
  1074. * @return the names of the directories which were deselected.
  1075. *
  1076. * @see #slowScan
  1077. */
  1078. public String[] getDeselectedDirectories() {
  1079. slowScan();
  1080. String[] directories = new String[dirsDeselected.size()];
  1081. dirsDeselected.copyInto(directories);
  1082. return directories;
  1083. }
  1084. /**
  1085. * Adds default exclusions to the current exclusions set.
  1086. */
  1087. public void addDefaultExcludes() {
  1088. int excludesLength = excludes == null ? 0 : excludes.length;
  1089. String[] newExcludes;
  1090. newExcludes = new String[excludesLength + defaultExcludes.size()];
  1091. if (excludesLength > 0) {
  1092. System.arraycopy(excludes, 0, newExcludes, 0, excludesLength);
  1093. }
  1094. String[] defaultExcludesTemp = getDefaultExcludes();
  1095. for (int i = 0; i < defaultExcludesTemp.length; i++) {
  1096. newExcludes[i + excludesLength] =
  1097. defaultExcludesTemp[i].replace('/', File.separatorChar)
  1098. .replace('\\', File.separatorChar);
  1099. }
  1100. excludes = newExcludes;
  1101. }
  1102. /**
  1103. * Get the named resource
  1104. * @param name path name of the file relative to the dir attribute.
  1105. *
  1106. * @return the resource with the given name.
  1107. * @since Ant 1.5.2
  1108. */
  1109. public Resource getResource(String name) {
  1110. File f = FILE_UTILS.resolveFile(basedir, name);
  1111. return new Resource(name, f.exists(), f.lastModified(),
  1112. f.isDirectory());
  1113. }
  1114. /**
  1115. * temporary table to speed up the various scanning methods below
  1116. *
  1117. * @since Ant 1.6
  1118. */
  1119. private Map fileListMap = new HashMap();
  1120. /**
  1121. * Returns a cached result of list performed on file, if
  1122. * available. Invokes the method and caches the result otherwise.
  1123. *
  1124. * @since Ant 1.6
  1125. */
  1126. private String[] list(File file) {
  1127. String[] files = (String[]) fileListMap.get(file);
  1128. if (files == null) {
  1129. files = file.list();
  1130. if (files != null) {
  1131. fileListMap.put(file, files);
  1132. }
  1133. }
  1134. return files;
  1135. }
  1136. /**
  1137. * From <code>base</code> traverse the filesystem in a case
  1138. * insensitive manner in order to find a file that matches the
  1139. * given name.
  1140. *
  1141. * @return File object that points to the file in question. if it
  1142. * hasn't been found it will simply be <code>new File(base,
  1143. * path)</code>.
  1144. *
  1145. * @since Ant 1.6
  1146. */
  1147. private File findFileCaseInsensitive(File base, String path) {
  1148. File f = findFileCaseInsensitive(base,
  1149. SelectorUtils.tokenizePath(path));
  1150. return f == null ? new File(base, path) : f;
  1151. }
  1152. /**
  1153. * From <code>base</code> traverse the filesystem in a case
  1154. * insensitive manner in order to find a file that matches the
  1155. * given stack of names.
  1156. *
  1157. * @return File object that points to the file in question or null.
  1158. *
  1159. * @since Ant 1.6
  1160. */
  1161. private File findFileCaseInsensitive(File base, Vector pathElements) {
  1162. if (pathElements.size() == 0) {
  1163. return base;
  1164. } else {
  1165. if (!base.isDirectory()) {
  1166. return null;
  1167. }
  1168. String[] files = list(base);
  1169. if (files == null) {
  1170. throw new BuildException("IO error scanning directory "
  1171. + base.getAbsolutePath());
  1172. }
  1173. String current = (String) pathElements.remove(0);
  1174. for (int i = 0; i < files.length; i++) {
  1175. if (files[i].equals(current)) {
  1176. base = new File(base, files[i]);
  1177. return findFileCaseInsensitive(base, pathElements);
  1178. }
  1179. }
  1180. for (int i = 0; i < files.length; i++) {
  1181. if (files[i].equalsIgnoreCase(current)) {
  1182. base = new File(base, files[i]);
  1183. return findFileCaseInsensitive(base, pathElements);
  1184. }
  1185. }
  1186. }
  1187. return null;
  1188. }
  1189. /**
  1190. * From <code>base</code> traverse the filesystem in order to find
  1191. * a file that matches the given name.
  1192. *
  1193. * @return File object that points to the file in question or null.
  1194. *
  1195. * @since Ant 1.6
  1196. */
  1197. private File findFile(File base, String path) {
  1198. return findFile(base, SelectorUtils.tokenizePath(path));
  1199. }
  1200. /**
  1201. * From <code>base</code> traverse the filesystem in order to find
  1202. * a file that matches the given stack of names.
  1203. *
  1204. * @return File object that points to the file in question or null.
  1205. *
  1206. * @since Ant 1.6
  1207. */
  1208. private File findFile(File base, Vector pathElements) {
  1209. if (pathElements.size() == 0) {
  1210. return base;
  1211. } else {
  1212. if (!base.isDirectory()) {
  1213. return null;
  1214. }
  1215. String[] files = list(base);
  1216. if (files == null) {
  1217. throw new BuildException("IO error scanning directory "
  1218. + base.getAbsolutePath());
  1219. }
  1220. String current = (String) pathElements.remove(0);
  1221. for (int i = 0; i < files.length; i++) {
  1222. if (files[i].equals(current)) {
  1223. base = new File(base, files[i]);
  1224. return findFile(base, pathElements);
  1225. }
  1226. }
  1227. }
  1228. return null;
  1229. }
  1230. /**
  1231. * Do we have to traverse a symlink when trying to reach path from
  1232. * basedir?
  1233. * @since Ant 1.6
  1234. */
  1235. private boolean isSymlink(File base, String path) {
  1236. return isSymlink(base, SelectorUtils.tokenizePath(path));
  1237. }
  1238. /**
  1239. * Do we have to traverse a symlink when trying to reach path from
  1240. * basedir?
  1241. * @since Ant 1.6
  1242. */
  1243. private boolean isSymlink(File base, Vector pathElements) {
  1244. if (pathElements.size() > 0) {
  1245. String current = (String) pathElements.remove(0);
  1246. try {
  1247. if (FILE_UTILS.isSymbolicLink(base, current)) {
  1248. return true;
  1249. } else {
  1250. base = new File(base, current);
  1251. return isSymlink(base, pathElements);
  1252. }
  1253. } catch (IOException ioe) {
  1254. String msg = "IOException caught while checking "
  1255. + "for links, couldn't get canonical path!";
  1256. // will be caught and redirected to Ant's logging system
  1257. System.err.println(msg);
  1258. return false;
  1259. }
  1260. }
  1261. return false;
  1262. }
  1263. /**
  1264. * List of all scanned directories.
  1265. *
  1266. * @since Ant 1.6
  1267. */
  1268. private Set scannedDirs = new HashSet();
  1269. /**
  1270. * Has the directory with the given path relative to the base
  1271. * directory already been scanned?
  1272. *
  1273. * <p>Registers the given directory as scanned as a side effect.</p>
  1274. *
  1275. * @since Ant 1.6
  1276. */
  1277. private boolean hasBeenScanned(String vpath) {
  1278. return !scannedDirs.add(vpath);
  1279. }
  1280. /**
  1281. * Clear internal caches.
  1282. *
  1283. * @since Ant 1.6
  1284. */
  1285. private void clearCaches() {
  1286. fileListMap.clear();
  1287. scannedDirs.clear();
  1288. }
  1289. }