KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > tools > ant > taskdefs > Sync


1 /*
2  * Licensed to the Apache Software Foundation (ASF) under one or more
3  * contributor license agreements. See the NOTICE file distributed with
4  * this work for additional information regarding copyright ownership.
5  * The ASF licenses this file to You under the Apache License, Version 2.0
6  * (the "License"); you may not use this file except in compliance with
7  * the License. You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */

18
19 package org.apache.tools.ant.taskdefs;
20
21 import java.io.File JavaDoc;
22
23 import java.util.HashSet JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Set JavaDoc;
27
28 import org.apache.tools.ant.BuildException;
29 import org.apache.tools.ant.DirectoryScanner;
30 import org.apache.tools.ant.Project;
31 import org.apache.tools.ant.Task;
32 import org.apache.tools.ant.types.AbstractFileSet;
33 import org.apache.tools.ant.types.FileSet;
34 import org.apache.tools.ant.types.PatternSet;
35 import org.apache.tools.ant.types.Resource;
36 import org.apache.tools.ant.types.ResourceCollection;
37 import org.apache.tools.ant.types.selectors.FileSelector;
38 import org.apache.tools.ant.types.selectors.NoneSelector;
39
40 /**
41  * Synchronize a local target directory from the files defined
42  * in one or more filesets.
43  *
44  * <p>Uses a &lt;copy&gt; task internally, but forbidding the use of
45  * mappers and filter chains. Files of the destination directory not
46  * present in any of the source fileset are removed.</p>
47  *
48  * @since Ant 1.6
49  *
50  * revised by <a HREF="mailto:daniel.armbrust@mayo.edu">Dan Armbrust</a>
51  * to remove orphaned directories.
52  *
53  * @ant.task category="filesystem"
54  */

55 public class Sync extends Task {
56
57     // Same as regular <copy> task... see at end-of-file!
58
private MyCopy myCopy;
59
60     // Similar to a fileset, but doesn't allow dir attribute to be set
61
private SyncTarget syncTarget;
62
63     // Override Task#init
64
/**
65      * Initialize the sync task.
66      * @throws BuildException if there is a problem.
67      * @see Task#init()
68      */

69     public void init()
70         throws BuildException {
71         // Instantiate it
72
myCopy = new MyCopy();
73         configureTask(myCopy);
74
75         // Default config of <mycopy> for our purposes.
76
myCopy.setFiltering(false);
77         myCopy.setIncludeEmptyDirs(false);
78         myCopy.setPreserveLastModified(true);
79     }
80
81     private void configureTask(Task helper) {
82         helper.setProject(getProject());
83         helper.setTaskName(getTaskName());
84         helper.setOwningTarget(getOwningTarget());
85         helper.init();
86     }
87
88     // Override Task#execute
89
/**
90      * Execute the sync task.
91      * @throws BuildException if there is an error.
92      * @see Task#execute()
93      */

94     public void execute()
95         throws BuildException {
96         // The destination of the files to copy
97
File JavaDoc toDir = myCopy.getToDir();
98
99         // The complete list of files to copy
100
Set JavaDoc allFiles = myCopy.nonOrphans;
101
102         // If the destination directory didn't already exist,
103
// or was empty, then no previous file removal is necessary!
104
boolean noRemovalNecessary = !toDir.exists() || toDir.list().length < 1;
105
106         // Copy all the necessary out-of-date files
107
log("PASS#1: Copying files to " + toDir, Project.MSG_DEBUG);
108         myCopy.execute();
109
110         // Do we need to perform further processing?
111
if (noRemovalNecessary) {
112             log("NO removing necessary in " + toDir, Project.MSG_DEBUG);
113             return; // nope ;-)
114
}
115
116         // Get rid of all files not listed in the source filesets.
117
log("PASS#2: Removing orphan files from " + toDir, Project.MSG_DEBUG);
118         int[] removedFileCount = removeOrphanFiles(allFiles, toDir);
119         logRemovedCount(removedFileCount[0], "dangling director", "y", "ies");
120         logRemovedCount(removedFileCount[1], "dangling file", "", "s");
121
122         // Get rid of empty directories on the destination side
123
if (!myCopy.getIncludeEmptyDirs()) {
124             log("PASS#3: Removing empty directories from " + toDir,
125                 Project.MSG_DEBUG);
126             int removedDirCount = removeEmptyDirectories(toDir, false);
127             logRemovedCount(removedDirCount, "empty director", "y", "ies");
128         }
129     }
130
131     private void logRemovedCount(int count, String JavaDoc prefix,
132                                  String JavaDoc singularSuffix, String JavaDoc pluralSuffix) {
133         File JavaDoc toDir = myCopy.getToDir();
134
135         String JavaDoc what = (prefix == null) ? "" : prefix;
136         what += (count < 2) ? singularSuffix : pluralSuffix;
137
138         if (count > 0) {
139             log("Removed " + count + " " + what + " from " + toDir,
140                 Project.MSG_INFO);
141         } else {
142             log("NO " + what + " to remove from " + toDir,
143                 Project.MSG_VERBOSE);
144         }
145     }
146
147     /**
148      * Removes all files and folders not found as keys of a table
149      * (used as a set!).
150      *
151      * <p>If the provided file is a directory, it is recursively
152      * scanned for orphaned files which will be removed as well.</p>
153      *
154      * <p>If the directory is an orphan, it will also be removed.</p>
155      *
156      * @param nonOrphans the table of all non-orphan <code>File</code>s.
157      * @param file the initial file or directory to scan or test.
158      * @return the number of orphaned files and directories actually removed.
159      * Position 0 of the array is the number of orphaned directories.
160      * Position 1 of the array is the number or orphaned files.
161      */

162     private int[] removeOrphanFiles(Set JavaDoc nonOrphans, File JavaDoc toDir) {
163         int[] removedCount = new int[] {0, 0};
164         String JavaDoc[] excls =
165             (String JavaDoc[]) nonOrphans.toArray(new String JavaDoc[nonOrphans.size() + 1]);
166         // want to keep toDir itself
167
excls[nonOrphans.size()] = "";
168
169         DirectoryScanner ds = null;
170         if (syncTarget != null) {
171             FileSet fs = new FileSet();
172             fs.setDir(toDir);
173             fs.setCaseSensitive(syncTarget.isCaseSensitive());
174             fs.setFollowSymlinks(syncTarget.isFollowSymlinks());
175
176             // preserveInTarget would find all files we want to keep,
177
// but we need to find all that we want to delete - so the
178
// meaning of all patterns and selectors must be inverted
179
PatternSet ps = syncTarget.mergePatterns(getProject());
180             fs.appendExcludes(ps.getIncludePatterns(getProject()));
181             fs.appendIncludes(ps.getExcludePatterns(getProject()));
182             fs.setDefaultexcludes(!syncTarget.getDefaultexcludes());
183
184             // selectors are implicitly ANDed in DirectoryScanner. To
185
// revert their logic we wrap them into a <none> selector
186
// instead.
187
FileSelector[] s = syncTarget.getSelectors(getProject());
188             if (s.length > 0) {
189                 NoneSelector ns = new NoneSelector();
190                 for (int i = 0; i < s.length; i++) {
191                     ns.appendSelector(s[i]);
192                 }
193                 fs.appendSelector(ns);
194             }
195             ds = fs.getDirectoryScanner(getProject());
196         } else {
197             ds = new DirectoryScanner();
198             ds.setBasedir(toDir);
199         }
200         ds.addExcludes(excls);
201
202         ds.scan();
203         String JavaDoc[] files = ds.getIncludedFiles();
204         for (int i = 0; i < files.length; i++) {
205             File JavaDoc f = new File JavaDoc(toDir, files[i]);
206             log("Removing orphan file: " + f, Project.MSG_DEBUG);
207             f.delete();
208             ++removedCount[1];
209         }
210         String JavaDoc[] dirs = ds.getIncludedDirectories();
211         // ds returns the directories in lexicographic order.
212
// iterating through the array backwards means we are deleting
213
// leaves before their parent nodes - thus making sure (well,
214
// more likely) that the directories are empty when we try to
215
// delete them.
216
for (int i = dirs.length - 1; i >= 0; --i) {
217             File JavaDoc f = new File JavaDoc(toDir, dirs[i]);
218             if (f.list().length < 1) {
219             log("Removing orphan directory: " + f, Project.MSG_DEBUG);
220             f.delete();
221             ++removedCount[0];
222             }
223         }
224         return removedCount;
225     }
226
227     /**
228      * Removes all empty directories from a directory.
229      *
230      * <p><em>Note that a directory that contains only empty
231      * directories, directly or not, will be removed!</em></p>
232      *
233      * <p>Recurses depth-first to find the leaf directories
234      * which are empty and removes them, then unwinds the
235      * recursion stack, removing directories which have
236      * become empty themselves, etc...</p>
237      *
238      * @param dir the root directory to scan for empty directories.
239      * @param removeIfEmpty whether to remove the root directory
240      * itself if it becomes empty.
241      * @return the number of empty directories actually removed.
242      */

243     private int removeEmptyDirectories(File JavaDoc dir, boolean removeIfEmpty) {
244         int removedCount = 0;
245         if (dir.isDirectory()) {
246             File JavaDoc[] children = dir.listFiles();
247             for (int i = 0; i < children.length; ++i) {
248                 File JavaDoc file = children[i];
249                 // Test here again to avoid method call for non-directories!
250
if (file.isDirectory()) {
251                     removedCount += removeEmptyDirectories(file, true);
252                 }
253             }
254             if (children.length > 0) {
255                 // This directory may have become empty...
256
// We need to re-query its children list!
257
children = dir.listFiles();
258             }
259             if (children.length < 1 && removeIfEmpty) {
260                 log("Removing empty directory: " + dir, Project.MSG_DEBUG);
261                 dir.delete();
262                 ++removedCount;
263             }
264         }
265         return removedCount;
266     }
267
268
269     //
270
// Various copy attributes/subelements of <copy> passed thru to <mycopy>
271
//
272

273     /**
274      * Sets the destination directory.
275      * @param destDir the destination directory
276      */

277     public void setTodir(File JavaDoc destDir) {
278         myCopy.setTodir(destDir);
279     }
280
281     /**
282      * Used to force listing of all names of copied files.
283      * @param verbose if true force listing of all names of copied files.
284      */

285     public void setVerbose(boolean verbose) {
286         myCopy.setVerbose(verbose);
287     }
288
289     /**
290      * Overwrite any existing destination file(s).
291      * @param overwrite if true overwrite any existing destination file(s).
292      */

293     public void setOverwrite(boolean overwrite) {
294         myCopy.setOverwrite(overwrite);
295     }
296
297     /**
298      * Used to copy empty directories.
299      * @param includeEmpty If true copy empty directories.
300      */

301     public void setIncludeEmptyDirs(boolean includeEmpty) {
302         myCopy.setIncludeEmptyDirs(includeEmpty);
303     }
304
305     /**
306      * If false, note errors to the output but keep going.
307      * @param failonerror true or false
308      */

309     public void setFailOnError(boolean failonerror) {
310         myCopy.setFailOnError(failonerror);
311     }
312
313     /**
314      * Adds a set of files to copy.
315      * @param set a fileset
316      */

317     public void addFileset(FileSet set) {
318         add(set);
319     }
320
321     /**
322      * Adds a collection of filesystem resources to copy.
323      * @param rc a resource collection
324      * @since Ant 1.7
325      */

326     public void add(ResourceCollection rc) {
327         myCopy.add(rc);
328     }
329
330     /**
331      * The number of milliseconds leeway to give before deciding a
332      * target is out of date.
333      *
334      * <p>Default is 0 milliseconds, or 2 seconds on DOS systems.</p>
335      * @param granularity a <code>long</code> value
336      * @since Ant 1.6.2
337      */

338     public void setGranularity(long granularity) {
339         myCopy.setGranularity(granularity);
340     }
341
342     /**
343      * A container for patterns and selectors that can be used to
344      * specify files that should be kept in the target even if they
345      * are not present in any source directory.
346      *
347      * <p>You must not invoke this method more than once.</p>
348      * @param s a preserveintarget nested element
349      * @since Ant 1.7
350      */

351     public void addPreserveInTarget(SyncTarget s) {
352         if (syncTarget != null) {
353             throw new BuildException("you must not specify multiple "
354                                      + "preserveintarget elements.");
355         }
356         syncTarget = s;
357     }
358
359     /**
360      * Subclass Copy in order to access it's file/dir maps.
361      */

362     public static class MyCopy extends Copy {
363
364         // List of files that must be copied, irrelevant from the
365
// fact that they are newer or not than the destination.
366
private Set JavaDoc nonOrphans = new HashSet JavaDoc();
367
368         /** Constructor for MyCopy. */
369         public MyCopy() {
370         }
371
372         /**
373          * @see Copy#scan(File, File, String[], String[])
374          */

375         /** {@inheritDoc} */
376         protected void scan(File JavaDoc fromDir, File JavaDoc toDir, String JavaDoc[] files,
377                             String JavaDoc[] dirs) {
378             assertTrue("No mapper", mapperElement == null);
379
380             super.scan(fromDir, toDir, files, dirs);
381
382             for (int i = 0; i < files.length; ++i) {
383                 nonOrphans.add(files[i]);
384             }
385             for (int i = 0; i < dirs.length; ++i) {
386                 nonOrphans.add(dirs[i]);
387             }
388         }
389
390         /**
391          * @see Copy#scan(Resource[], File)
392          */

393         /** {@inheritDoc} */
394         protected Map JavaDoc scan(Resource[] resources, File JavaDoc toDir) {
395             assertTrue("No mapper", mapperElement == null);
396
397             Map JavaDoc m = super.scan(resources, toDir);
398
399             Iterator JavaDoc iter = m.keySet().iterator();
400             while (iter.hasNext()) {
401                 nonOrphans.add(((Resource) iter.next()).getName());
402             }
403             return m;
404         }
405
406         /**
407          * Get the destination directory.
408          * @return the destination directory
409          */

410         public File JavaDoc getToDir() {
411             return destDir;
412         }
413
414         /**
415          * Get the includeEmptyDirs attribute.
416          * @return true if emptyDirs are to be included
417          */

418         public boolean getIncludeEmptyDirs() {
419             return includeEmpty;
420         }
421
422         /**
423          * Yes, we can.
424          * @return true always.
425          * @since Ant 1.7
426          */

427         protected boolean supportsNonFileResources() {
428             return true;
429         }
430     }
431
432     /**
433      * Inner class used to hold exclude patterns and selectors to save
434      * stuff that happens to live in the target directory but should
435      * not get removed.
436      *
437      * @since Ant 1.7
438      */

439     public static class SyncTarget extends AbstractFileSet {
440
441         /**
442          * Constructor for SyncTarget.
443          * This just changes the default value of "defaultexcludes" from
444          * true to false.
445          */

446         public SyncTarget() {
447             super();
448         }
449
450         /**
451          * Override AbstractFileSet#setDir(File) to disallow
452          * setting the directory.
453          * @param dir ignored
454          * @throws BuildException always
455          */

456         public void setDir(File JavaDoc dir) throws BuildException {
457             throw new BuildException("preserveintarget doesn't support the dir "
458                                      + "attribute");
459         }
460
461     }
462
463     /**
464      * Pseudo-assert method.
465      */

466     private static void assertTrue(String JavaDoc message, boolean condition) {
467         if (!condition) {
468             throw new BuildException("Assertion Error: " + message);
469         }
470     }
471
472 }
473
Popular Tags