source: release-kits/lirk3/resources/gs3-release-maker/apache-ant-1.6.5/src/main/org/apache/tools/ant/taskdefs/Sync.java@ 14982

Last change on this file since 14982 was 14982, checked in by oranfry, 16 years ago

initial import of LiRK3

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