You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Sync.java 13 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /*
  2. * Copyright 2003-2005 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. /*
  18. * This code is based on code Copyright (c) 2002, Landmark Graphics
  19. * Corp that has been kindly donated to the Apache Software
  20. * Foundation.
  21. */
  22. package org.apache.tools.ant.taskdefs;
  23. import java.io.File;
  24. import java.util.HashSet;
  25. import java.util.Set;
  26. import org.apache.tools.ant.BuildException;
  27. import org.apache.tools.ant.DirectoryScanner;
  28. import org.apache.tools.ant.Project;
  29. import org.apache.tools.ant.Task;
  30. import org.apache.tools.ant.types.AbstractFileSet;
  31. import org.apache.tools.ant.types.FileSet;
  32. /**
  33. * Synchronize a local target directory from the files defined
  34. * in one or more filesets.
  35. *
  36. * <p>Uses a &lt;copy&gt; task internally, but forbidding the use of
  37. * mappers and filter chains. Files of the destination directory not
  38. * present in any of the source fileset are removed.</p>
  39. *
  40. * @version $Revision$
  41. * @since Ant 1.6
  42. *
  43. * revised by <a href="mailto:daniel.armbrust@mayo.edu">Dan Armbrust</a>
  44. * to remove orphaned directories.
  45. *
  46. * @ant.task category="filesystem"
  47. */
  48. public class Sync extends Task {
  49. // Same as regular <copy> task... see at end-of-file!
  50. private MyCopy myCopy;
  51. // Similar to a fileset, but doesn't allow dir attribute to be set
  52. private SyncTarget syncTarget;
  53. // Override Task#init
  54. /**
  55. * @see Task#init()
  56. */
  57. public void init()
  58. throws BuildException {
  59. // Instantiate it
  60. myCopy = new MyCopy();
  61. configureTask(myCopy);
  62. // Default config of <mycopy> for our purposes.
  63. myCopy.setFiltering(false);
  64. myCopy.setIncludeEmptyDirs(false);
  65. myCopy.setPreserveLastModified(true);
  66. }
  67. private void configureTask(Task helper) {
  68. helper.setProject(getProject());
  69. helper.setTaskName(getTaskName());
  70. helper.setOwningTarget(getOwningTarget());
  71. helper.init();
  72. }
  73. // Override Task#execute
  74. /**
  75. * @see Task#execute()
  76. */
  77. public void execute()
  78. throws BuildException {
  79. // The destination of the files to copy
  80. File toDir = myCopy.getToDir();
  81. // The complete list of files to copy
  82. Set allFiles = myCopy.nonOrphans;
  83. // If the destination directory didn't already exist,
  84. // or was empty, then no previous file removal is necessary!
  85. boolean noRemovalNecessary = !toDir.exists() || toDir.list().length < 1;
  86. // Copy all the necessary out-of-date files
  87. log("PASS#1: Copying files to " + toDir, Project.MSG_DEBUG);
  88. myCopy.execute();
  89. // Do we need to perform further processing?
  90. if (noRemovalNecessary) {
  91. log("NO removing necessary in " + toDir, Project.MSG_DEBUG);
  92. return; // nope ;-)
  93. }
  94. // Get rid of all files not listed in the source filesets.
  95. log("PASS#2: Removing orphan files from " + toDir, Project.MSG_DEBUG);
  96. int[] removedFileCount = removeOrphanFiles(allFiles, toDir);
  97. logRemovedCount(removedFileCount[0], "dangling director", "y", "ies");
  98. logRemovedCount(removedFileCount[1], "dangling file", "", "s");
  99. // Get rid of empty directories on the destination side
  100. if (!myCopy.getIncludeEmptyDirs()) {
  101. log("PASS#3: Removing empty directories from " + toDir,
  102. Project.MSG_DEBUG);
  103. int removedDirCount = removeEmptyDirectories(toDir, false);
  104. logRemovedCount(removedDirCount, "empty director", "y", "ies");
  105. }
  106. }
  107. private void logRemovedCount(int count, String prefix,
  108. String singularSuffix, String pluralSuffix) {
  109. File toDir = myCopy.getToDir();
  110. String what = (prefix == null) ? "" : prefix;
  111. what += (count < 2) ? singularSuffix : pluralSuffix;
  112. if (count > 0) {
  113. log("Removed " + count + " " + what + " from " + toDir,
  114. Project.MSG_INFO);
  115. } else {
  116. log("NO " + what + " to remove from " + toDir,
  117. Project.MSG_VERBOSE);
  118. }
  119. }
  120. /**
  121. * Removes all files and folders not found as keys of a table
  122. * (used as a set!).
  123. *
  124. * <p>If the provided file is a directory, it is recursively
  125. * scanned for orphaned files which will be removed as well.</p>
  126. *
  127. * <p>If the directory is an orphan, it will also be removed.</p>
  128. *
  129. * @param nonOrphans the table of all non-orphan <code>File</code>s.
  130. * @param file the initial file or directory to scan or test.
  131. * @return the number of orphaned files and directories actually removed.
  132. * Position 0 of the array is the number of orphaned directories.
  133. * Position 1 of the array is the number or orphaned files.
  134. */
  135. private int[] removeOrphanFiles(Set nonOrphans, File toDir) {
  136. int[] removedCount = new int[] {0, 0};
  137. String[] excls =
  138. (String[]) nonOrphans.toArray(new String[nonOrphans.size() + 1]);
  139. // want to keep toDir itself
  140. excls[nonOrphans.size()] = "";
  141. DirectoryScanner ds = null;
  142. if (syncTarget != null) {
  143. syncTarget.setTargetDir(toDir);
  144. ds = syncTarget.getDirectoryScanner(getProject());
  145. } else {
  146. ds = new DirectoryScanner();
  147. ds.setBasedir(toDir);
  148. }
  149. ds.addExcludes(excls);
  150. ds.scan();
  151. String[] files = ds.getIncludedFiles();
  152. for (int i = 0; i < files.length; i++) {
  153. File f = new File(toDir, files[i]);
  154. log("Removing orphan file: " + f, Project.MSG_DEBUG);
  155. f.delete();
  156. ++removedCount[1];
  157. }
  158. String[] dirs = ds.getIncludedDirectories();
  159. // ds returns the directories as it has visited them.
  160. // iterating through the array backwards means we are deleting
  161. // leaves before their parent nodes - thus making sure (well,
  162. // more likely) that the directories are empty when we try to
  163. // delete them.
  164. for (int i = dirs.length - 1; i >= 0; --i) {
  165. File f = new File(toDir, dirs[i]);
  166. log("Removing orphan directory: " + f, Project.MSG_DEBUG);
  167. f.delete();
  168. ++removedCount[0];
  169. }
  170. return removedCount;
  171. }
  172. /**
  173. * Removes all empty directories from a directory.
  174. *
  175. * <p><em>Note that a directory that contains only empty
  176. * directories, directly or not, will be removed!</em></p>
  177. *
  178. * <p>Recurses depth-first to find the leaf directories
  179. * which are empty and removes them, then unwinds the
  180. * recursion stack, removing directories which have
  181. * become empty themselves, etc...</p>
  182. *
  183. * @param dir the root directory to scan for empty directories.
  184. * @param removeIfEmpty whether to remove the root directory
  185. * itself if it becomes empty.
  186. * @return the number of empty directories actually removed.
  187. */
  188. private int removeEmptyDirectories(File dir, boolean removeIfEmpty) {
  189. int removedCount = 0;
  190. if (dir.isDirectory()) {
  191. File[] children = dir.listFiles();
  192. for (int i = 0; i < children.length; ++i) {
  193. File file = children[i];
  194. // Test here again to avoid method call for non-directories!
  195. if (file.isDirectory()) {
  196. removedCount += removeEmptyDirectories(file, true);
  197. }
  198. }
  199. if (children.length > 0) {
  200. // This directory may have become empty...
  201. // We need to re-query its children list!
  202. children = dir.listFiles();
  203. }
  204. if (children.length < 1 && removeIfEmpty) {
  205. log("Removing empty directory: " + dir, Project.MSG_DEBUG);
  206. dir.delete();
  207. ++removedCount;
  208. }
  209. }
  210. return removedCount;
  211. }
  212. //
  213. // Various copy attributes/subelements of <copy> passed thru to <mycopy>
  214. //
  215. /**
  216. * Sets the destination directory.
  217. * @param destDir the destination directory
  218. */
  219. public void setTodir(File destDir) {
  220. myCopy.setTodir(destDir);
  221. }
  222. /**
  223. * Used to force listing of all names of copied files.
  224. * @param verbose if true force listing of all names of copied files.
  225. */
  226. public void setVerbose(boolean verbose) {
  227. myCopy.setVerbose(verbose);
  228. }
  229. /**
  230. * Overwrite any existing destination file(s).
  231. * @param overwrite if true overwrite any existing destination file(s).
  232. */
  233. public void setOverwrite(boolean overwrite) {
  234. myCopy.setOverwrite(overwrite);
  235. }
  236. /**
  237. * Used to copy empty directories.
  238. * @param includeEmpty If true copy empty directories.
  239. */
  240. public void setIncludeEmptyDirs(boolean includeEmpty) {
  241. myCopy.setIncludeEmptyDirs(includeEmpty);
  242. }
  243. /**
  244. * If false, note errors to the output but keep going.
  245. * @param failonerror true or false
  246. */
  247. public void setFailOnError(boolean failonerror) {
  248. myCopy.setFailOnError(failonerror);
  249. }
  250. /**
  251. * Adds a set of files to copy.
  252. * @param set a fileset
  253. */
  254. public void addFileset(FileSet set) {
  255. myCopy.addFileset(set);
  256. }
  257. /**
  258. * The number of milliseconds leeway to give before deciding a
  259. * target is out of date.
  260. *
  261. * <p>Default is 0 milliseconds, or 2 seconds on DOS systems.</p>
  262. * @param granularity a <code>long</code> value
  263. * @since Ant 1.6.2
  264. */
  265. public void setGranularity(long granularity) {
  266. myCopy.setGranularity(granularity);
  267. }
  268. /**
  269. * A container for patterns and selectors that can be used to
  270. * specify files that should be kept in the target even if they
  271. * are not present in any source directory.
  272. *
  273. * <p>You must not invoke this method more than once.</p>
  274. *
  275. * @since Ant 1.7
  276. */
  277. public void addDeleteFromTarget(SyncTarget s) {
  278. if (syncTarget != null) {
  279. throw new BuildException("you must not specify multiple "
  280. + "deletefromtaget elements.");
  281. }
  282. syncTarget = s;
  283. }
  284. /**
  285. * Subclass Copy in order to access it's file/dir maps.
  286. */
  287. public static class MyCopy extends Copy {
  288. // List of files that must be copied, irrelevant from the
  289. // fact that they are newer or not than the destination.
  290. private Set nonOrphans = new HashSet();
  291. /** Constructor for MyCopy. */
  292. public MyCopy() {
  293. }
  294. /**
  295. * @see Copy#scan(File, File, String[], String[])
  296. */
  297. protected void scan(File fromDir, File toDir, String[] files,
  298. String[] dirs) {
  299. assertTrue("No mapper", mapperElement == null);
  300. super.scan(fromDir, toDir, files, dirs);
  301. for (int i = 0; i < files.length; ++i) {
  302. nonOrphans.add(files[i]);
  303. }
  304. for (int i = 0; i < dirs.length; ++i) {
  305. nonOrphans.add(dirs[i]);
  306. }
  307. }
  308. /**
  309. * Get the destination directory.
  310. * @return the destination directory
  311. */
  312. public File getToDir() {
  313. return destDir;
  314. }
  315. /**
  316. * Get the includeEmptyDirs attribute.
  317. * @return true if emptyDirs are to be included
  318. */
  319. public boolean getIncludeEmptyDirs() {
  320. return includeEmpty;
  321. }
  322. }
  323. /**
  324. * Inner class used to hold exclude patterns and selectors to save
  325. * stuff that happens to live in the target directory but should
  326. * not get removed.
  327. *
  328. * @since Ant 1.7
  329. */
  330. public static class SyncTarget extends AbstractFileSet {
  331. public SyncTarget() {
  332. super();
  333. setDefaultexcludes(false);
  334. }
  335. public void setDir(File dir) throws BuildException {
  336. throw new BuildException("synctarget doesn't support the dir "
  337. + "attribute");
  338. }
  339. private void setTargetDir(File dir) throws BuildException {
  340. super.setDir(dir);
  341. }
  342. }
  343. /**
  344. * Pseudo-assert method.
  345. */
  346. private static void assertTrue(String message, boolean condition) {
  347. if (!condition) {
  348. throw new BuildException("Assertion Error: " + message);
  349. }
  350. }
  351. }