1 | /*
|
---|
2 | * Copyright 2000-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 | package org.apache.tools.ant.taskdefs;
|
---|
19 |
|
---|
20 | import java.io.File;
|
---|
21 | import java.io.IOException;
|
---|
22 | import java.util.Enumeration;
|
---|
23 | import org.apache.tools.ant.Project;
|
---|
24 | import org.apache.tools.ant.BuildException;
|
---|
25 | import org.apache.tools.ant.DirectoryScanner;
|
---|
26 | import org.apache.tools.ant.types.FileSet;
|
---|
27 | import org.apache.tools.ant.types.FilterSet;
|
---|
28 | import org.apache.tools.ant.types.FilterSetCollection;
|
---|
29 |
|
---|
30 | /**
|
---|
31 | * Moves a file or directory to a new file or directory.
|
---|
32 | * By default, the
|
---|
33 | * destination file is overwritten if it already exists.
|
---|
34 | * When <i>overwrite</i> is
|
---|
35 | * turned off, then files are only moved if the source file is
|
---|
36 | * newer than the destination file, or when the destination file does
|
---|
37 | * not exist.
|
---|
38 | *
|
---|
39 | * <p>Source files and directories are only deleted when the file or
|
---|
40 | * directory has been copied to the destination successfully. Filtering
|
---|
41 | * also works.</p>
|
---|
42 | *
|
---|
43 | * <p>This implementation is based on Arnout Kuiper's initial design
|
---|
44 | * document, the following mailing list discussions, and the
|
---|
45 | * copyfile/copydir tasks.</p>
|
---|
46 | *
|
---|
47 | *
|
---|
48 | * @since Ant 1.2
|
---|
49 | *
|
---|
50 | * @ant.task category="filesystem"
|
---|
51 | */
|
---|
52 | public class Move extends Copy {
|
---|
53 |
|
---|
54 | /**
|
---|
55 | * Constructor of object.
|
---|
56 | * This sets the forceOverwrite attribute of the Copy parent class
|
---|
57 | * to true.
|
---|
58 | *
|
---|
59 | */
|
---|
60 | public Move() {
|
---|
61 | super();
|
---|
62 | setOverwrite(true);
|
---|
63 | }
|
---|
64 |
|
---|
65 | // inherit doc
|
---|
66 | protected void validateAttributes() throws BuildException {
|
---|
67 | if (file != null && file.isDirectory()) {
|
---|
68 | if ((destFile != null && destDir != null)
|
---|
69 | || (destFile == null && destDir == null)) {
|
---|
70 | throw new BuildException("One and only one of tofile and todir "
|
---|
71 | + "must be set.");
|
---|
72 | }
|
---|
73 | destFile = (destFile == null)
|
---|
74 | ? new File(destDir, file.getName()) : destFile;
|
---|
75 | destDir = (destDir == null)
|
---|
76 | ? destFile.getParentFile() : destDir;
|
---|
77 |
|
---|
78 | completeDirMap.put(file, destFile);
|
---|
79 | file = null;
|
---|
80 | } else {
|
---|
81 | super.validateAttributes();
|
---|
82 | }
|
---|
83 | }
|
---|
84 |
|
---|
85 | //************************************************************************
|
---|
86 | // protected and private methods
|
---|
87 | //************************************************************************
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * Override copy's doFileOperations to move the
|
---|
91 | * files instead of copying them.
|
---|
92 | */
|
---|
93 | protected void doFileOperations() {
|
---|
94 | //Attempt complete directory renames, if any, first.
|
---|
95 | if (completeDirMap.size() > 0) {
|
---|
96 | Enumeration e = completeDirMap.keys();
|
---|
97 | while (e.hasMoreElements()) {
|
---|
98 | File fromDir = (File) e.nextElement();
|
---|
99 | File toDir = (File) completeDirMap.get(fromDir);
|
---|
100 | boolean renamed = false;
|
---|
101 | try {
|
---|
102 | log("Attempting to rename dir: " + fromDir
|
---|
103 | + " to " + toDir, verbosity);
|
---|
104 | renamed =
|
---|
105 | renameFile(fromDir, toDir, filtering, forceOverwrite);
|
---|
106 | } catch (IOException ioe) {
|
---|
107 | String msg = "Failed to rename dir " + fromDir
|
---|
108 | + " to " + toDir
|
---|
109 | + " due to " + ioe.getMessage();
|
---|
110 | throw new BuildException(msg, ioe, getLocation());
|
---|
111 | }
|
---|
112 | if (!renamed) {
|
---|
113 | FileSet fs = new FileSet();
|
---|
114 | fs.setProject(getProject());
|
---|
115 | fs.setDir(fromDir);
|
---|
116 | addFileset(fs);
|
---|
117 | DirectoryScanner ds = fs.getDirectoryScanner(getProject());
|
---|
118 | String[] files = ds.getIncludedFiles();
|
---|
119 | String[] dirs = ds.getIncludedDirectories();
|
---|
120 | scan(fromDir, toDir, files, dirs);
|
---|
121 | }
|
---|
122 | }
|
---|
123 | }
|
---|
124 | int moveCount = fileCopyMap.size();
|
---|
125 | if (moveCount > 0) { // files to move
|
---|
126 | log("Moving " + moveCount + " file"
|
---|
127 | + ((moveCount == 1) ? "" : "s")
|
---|
128 | + " to " + destDir.getAbsolutePath());
|
---|
129 |
|
---|
130 | Enumeration e = fileCopyMap.keys();
|
---|
131 | while (e.hasMoreElements()) {
|
---|
132 | String fromFile = (String) e.nextElement();
|
---|
133 |
|
---|
134 | File f = new File(fromFile);
|
---|
135 | boolean selfMove = false;
|
---|
136 | if (f.exists()) { //Is this file still available to be moved?
|
---|
137 | String[] toFiles = (String[]) fileCopyMap.get(fromFile);
|
---|
138 | for (int i = 0; i < toFiles.length; i++) {
|
---|
139 | String toFile = (String) toFiles[i];
|
---|
140 |
|
---|
141 | if (fromFile.equals(toFile)) {
|
---|
142 | log("Skipping self-move of " + fromFile, verbosity);
|
---|
143 | selfMove = true;
|
---|
144 |
|
---|
145 | // if this is the last time through the loop then
|
---|
146 | // move will not occur, but that's what we want
|
---|
147 | continue;
|
---|
148 | }
|
---|
149 | File d = new File(toFile);
|
---|
150 | if ((i + 1) == toFiles.length && !selfMove) {
|
---|
151 | // Only try to move if this is the last mapped file
|
---|
152 | // and one of the mappings isn't to itself
|
---|
153 | moveFile(f, d, filtering, forceOverwrite);
|
---|
154 | } else {
|
---|
155 | copyFile(f, d, filtering, forceOverwrite);
|
---|
156 | }
|
---|
157 | }
|
---|
158 | }
|
---|
159 | }
|
---|
160 | }
|
---|
161 |
|
---|
162 | if (includeEmpty) {
|
---|
163 | Enumeration e = dirCopyMap.keys();
|
---|
164 | int createCount = 0;
|
---|
165 | while (e.hasMoreElements()) {
|
---|
166 | String fromDirName = (String) e.nextElement();
|
---|
167 | String[] toDirNames = (String[]) dirCopyMap.get(fromDirName);
|
---|
168 | boolean selfMove = false;
|
---|
169 | for (int i = 0; i < toDirNames.length; i++) {
|
---|
170 |
|
---|
171 | if (fromDirName.equals(toDirNames[i])) {
|
---|
172 | log("Skipping self-move of " + fromDirName, verbosity);
|
---|
173 | selfMove = true;
|
---|
174 | continue;
|
---|
175 | }
|
---|
176 |
|
---|
177 | File d = new File(toDirNames[i]);
|
---|
178 | if (!d.exists()) {
|
---|
179 | if (!d.mkdirs()) {
|
---|
180 | log("Unable to create directory "
|
---|
181 | + d.getAbsolutePath(), Project.MSG_ERR);
|
---|
182 | } else {
|
---|
183 | createCount++;
|
---|
184 | }
|
---|
185 | }
|
---|
186 | }
|
---|
187 |
|
---|
188 | File fromDir = new File(fromDirName);
|
---|
189 | if (!selfMove && okToDelete(fromDir)) {
|
---|
190 | deleteDir(fromDir);
|
---|
191 | }
|
---|
192 |
|
---|
193 | }
|
---|
194 |
|
---|
195 | if (createCount > 0) {
|
---|
196 | log("Moved " + dirCopyMap.size()
|
---|
197 | + " empty director"
|
---|
198 | + (dirCopyMap.size() == 1 ? "y" : "ies")
|
---|
199 | + " to " + createCount
|
---|
200 | + " empty director"
|
---|
201 | + (createCount == 1 ? "y" : "ies") + " under "
|
---|
202 | + destDir.getAbsolutePath());
|
---|
203 | }
|
---|
204 | }
|
---|
205 | }
|
---|
206 |
|
---|
207 | /**
|
---|
208 | * Try to move the file via a rename, but if this fails or filtering
|
---|
209 | * is enabled, copy the file then delete the sourceFile.
|
---|
210 | */
|
---|
211 | private void moveFile(File fromFile, File toFile,
|
---|
212 | boolean filtering, boolean overwrite) {
|
---|
213 | boolean moved = false;
|
---|
214 | try {
|
---|
215 | log("Attempting to rename: " + fromFile
|
---|
216 | + " to " + toFile, verbosity);
|
---|
217 | moved = renameFile(fromFile, toFile, filtering, forceOverwrite);
|
---|
218 | } catch (IOException ioe) {
|
---|
219 | String msg = "Failed to rename " + fromFile
|
---|
220 | + " to " + toFile
|
---|
221 | + " due to " + ioe.getMessage();
|
---|
222 | throw new BuildException(msg, ioe, getLocation());
|
---|
223 | }
|
---|
224 |
|
---|
225 | if (!moved) {
|
---|
226 | copyFile(fromFile, toFile, filtering, overwrite);
|
---|
227 | if (!fromFile.delete()) {
|
---|
228 | throw new BuildException("Unable to delete "
|
---|
229 | + "file "
|
---|
230 | + fromFile.getAbsolutePath());
|
---|
231 | }
|
---|
232 | }
|
---|
233 | }
|
---|
234 |
|
---|
235 | /**
|
---|
236 | * Copy fromFile to toFile.
|
---|
237 | * @param fromFile
|
---|
238 | * @param toFile
|
---|
239 | * @param filtering
|
---|
240 | * @param overwrite
|
---|
241 | */
|
---|
242 | private void copyFile(File fromFile, File toFile,
|
---|
243 | boolean filtering, boolean overwrite) {
|
---|
244 | try {
|
---|
245 | log("Copying " + fromFile + " to " + toFile,
|
---|
246 | verbosity);
|
---|
247 |
|
---|
248 | FilterSetCollection executionFilters =
|
---|
249 | new FilterSetCollection();
|
---|
250 | if (filtering) {
|
---|
251 | executionFilters
|
---|
252 | .addFilterSet(getProject().getGlobalFilterSet());
|
---|
253 | }
|
---|
254 | for (Enumeration filterEnum =
|
---|
255 | getFilterSets().elements();
|
---|
256 | filterEnum.hasMoreElements();) {
|
---|
257 | executionFilters
|
---|
258 | .addFilterSet((FilterSet) filterEnum
|
---|
259 | .nextElement());
|
---|
260 | }
|
---|
261 |
|
---|
262 | getFileUtils().copyFile(fromFile, toFile, executionFilters,
|
---|
263 | getFilterChains(),
|
---|
264 | forceOverwrite,
|
---|
265 | getPreserveLastModified(),
|
---|
266 | getEncoding(),
|
---|
267 | getOutputEncoding(),
|
---|
268 | getProject());
|
---|
269 |
|
---|
270 | } catch (IOException ioe) {
|
---|
271 | String msg = "Failed to copy " + fromFile
|
---|
272 | + " to " + toFile
|
---|
273 | + " due to " + ioe.getMessage();
|
---|
274 | throw new BuildException(msg, ioe, getLocation());
|
---|
275 | }
|
---|
276 | }
|
---|
277 |
|
---|
278 |
|
---|
279 | /**
|
---|
280 | * Its only ok to delete a directory tree if there are
|
---|
281 | * no files in it.
|
---|
282 | * @param d the directory to check
|
---|
283 | * @return true if a deletion can go ahead
|
---|
284 | */
|
---|
285 | protected boolean okToDelete(File d) {
|
---|
286 | String[] list = d.list();
|
---|
287 | if (list == null) {
|
---|
288 | return false;
|
---|
289 | } // maybe io error?
|
---|
290 |
|
---|
291 | for (int i = 0; i < list.length; i++) {
|
---|
292 | String s = list[i];
|
---|
293 | File f = new File(d, s);
|
---|
294 | if (f.isDirectory()) {
|
---|
295 | if (!okToDelete(f)) {
|
---|
296 | return false;
|
---|
297 | }
|
---|
298 | } else {
|
---|
299 | return false; // found a file
|
---|
300 | }
|
---|
301 | }
|
---|
302 |
|
---|
303 | return true;
|
---|
304 | }
|
---|
305 |
|
---|
306 | /**
|
---|
307 | * Go and delete the directory tree.
|
---|
308 | * @param d the directory to delete
|
---|
309 | */
|
---|
310 | protected void deleteDir(File d) {
|
---|
311 | deleteDir(d, false);
|
---|
312 | }
|
---|
313 |
|
---|
314 | /**
|
---|
315 | * Go and delete the directory tree.
|
---|
316 | * @param d the directory to delete
|
---|
317 | * @param deleteFiles whether to delete files
|
---|
318 | */
|
---|
319 | protected void deleteDir(File d, boolean deleteFiles) {
|
---|
320 | String[] list = d.list();
|
---|
321 | if (list == null) {
|
---|
322 | return;
|
---|
323 | } // on an io error list() can return null
|
---|
324 |
|
---|
325 | for (int i = 0; i < list.length; i++) {
|
---|
326 | String s = list[i];
|
---|
327 | File f = new File(d, s);
|
---|
328 | if (f.isDirectory()) {
|
---|
329 | deleteDir(f);
|
---|
330 | } else if (deleteFiles && !(f.delete())) {
|
---|
331 | throw new BuildException("Unable to delete file "
|
---|
332 | + f.getAbsolutePath());
|
---|
333 | } else {
|
---|
334 | throw new BuildException("UNEXPECTED ERROR - The file "
|
---|
335 | + f.getAbsolutePath()
|
---|
336 | + " should not exist!");
|
---|
337 | }
|
---|
338 | }
|
---|
339 | log("Deleting directory " + d.getAbsolutePath(), verbosity);
|
---|
340 | if (!d.delete()) {
|
---|
341 | throw new BuildException("Unable to delete directory "
|
---|
342 | + d.getAbsolutePath());
|
---|
343 | }
|
---|
344 | }
|
---|
345 |
|
---|
346 | /**
|
---|
347 | * Attempts to rename a file from a source to a destination.
|
---|
348 | * If overwrite is set to true, this method overwrites existing file
|
---|
349 | * even if the destination file is newer. Otherwise, the source file is
|
---|
350 | * renamed only if the destination file is older than it.
|
---|
351 | * Method then checks if token filtering is used. If it is, this method
|
---|
352 | * returns false assuming it is the responsibility to the copyFile method.
|
---|
353 | *
|
---|
354 | * @param sourceFile the file to rename
|
---|
355 | * @param destFile the destination file
|
---|
356 | * @param filtering if true, filtering is in operation, file will
|
---|
357 | * be copied/deleted instead of renamed
|
---|
358 | * @param overwrite if true force overwrite even if destination file
|
---|
359 | * is newer than source file
|
---|
360 | * @return true if the file was renamed
|
---|
361 | * @exception IOException if an error occurs
|
---|
362 | * @exception BuildException if an error occurs
|
---|
363 | */
|
---|
364 | protected boolean renameFile(File sourceFile, File destFile,
|
---|
365 | boolean filtering, boolean overwrite)
|
---|
366 | throws IOException, BuildException {
|
---|
367 |
|
---|
368 | boolean renamed = false;
|
---|
369 | if ((getFilterSets().size() + getFilterChains().size() == 0)
|
---|
370 | && !(filtering || destFile.isDirectory())) {
|
---|
371 | // ensure that parent dir of dest file exists!
|
---|
372 | File parent = destFile.getParentFile();
|
---|
373 | if (parent != null && !parent.exists()) {
|
---|
374 | parent.mkdirs();
|
---|
375 | }
|
---|
376 | if (destFile.isFile() && !destFile.delete()) {
|
---|
377 | throw new BuildException("Unable to remove existing "
|
---|
378 | + "file " + destFile);
|
---|
379 | }
|
---|
380 | renamed = sourceFile.renameTo(destFile);
|
---|
381 | }
|
---|
382 | return renamed;
|
---|
383 | }
|
---|
384 | }
|
---|