001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.Enumeration;
024: import org.apache.tools.ant.Project;
025: import org.apache.tools.ant.BuildException;
026: import org.apache.tools.ant.DirectoryScanner;
027: import org.apache.tools.ant.types.FileSet;
028: import org.apache.tools.ant.types.FilterSet;
029: import org.apache.tools.ant.types.FilterSetCollection;
030:
031: /**
032: * Moves a file or directory to a new file or directory.
033: * By default, the
034: * destination file is overwritten if it already exists.
035: * When <i>overwrite</i> is
036: * turned off, then files are only moved if the source file is
037: * newer than the destination file, or when the destination file does
038: * not exist.
039: *
040: * <p>Source files and directories are only deleted when the file or
041: * directory has been copied to the destination successfully. Filtering
042: * also works.</p>
043: *
044: * <p>This implementation is based on Arnout Kuiper's initial design
045: * document, the following mailing list discussions, and the
046: * copyfile/copydir tasks.</p>
047: *
048: *
049: * @since Ant 1.2
050: *
051: * @ant.task category="filesystem"
052: */
053: public class Move extends Copy {
054:
055: /**
056: * Constructor of object.
057: * This sets the forceOverwrite attribute of the Copy parent class
058: * to true.
059: *
060: */
061: public Move() {
062: super ();
063: setOverwrite(true);
064: }
065:
066: /** {@inheritDoc}. */
067: protected void validateAttributes() throws BuildException {
068: if (file != null && file.isDirectory()) {
069: if ((destFile != null && destDir != null)
070: || (destFile == null && destDir == null)) {
071: throw new BuildException(
072: "One and only one of tofile and todir "
073: + "must be set.");
074: }
075: destFile = (destFile == null) ? new File(destDir, file
076: .getName()) : destFile;
077: destDir = (destDir == null) ? destFile.getParentFile()
078: : destDir;
079:
080: completeDirMap.put(file, destFile);
081: file = null;
082: } else {
083: super .validateAttributes();
084: }
085: }
086:
087: //************************************************************************
088: // protected and private methods
089: //************************************************************************
090:
091: /**
092: * Override copy's doFileOperations to move the
093: * files instead of copying them.
094: */
095: protected void doFileOperations() {
096: //Attempt complete directory renames, if any, first.
097: if (completeDirMap.size() > 0) {
098: Enumeration e = completeDirMap.keys();
099: while (e.hasMoreElements()) {
100: File fromDir = (File) e.nextElement();
101: File toDir = (File) completeDirMap.get(fromDir);
102: boolean renamed = false;
103: try {
104: log("Attempting to rename dir: " + fromDir + " to "
105: + toDir, verbosity);
106: renamed = renameFile(fromDir, toDir, filtering,
107: forceOverwrite);
108: } catch (IOException ioe) {
109: String msg = "Failed to rename dir " + fromDir
110: + " to " + toDir + " due to "
111: + ioe.getMessage();
112: throw new BuildException(msg, ioe, getLocation());
113: }
114: if (!renamed) {
115: FileSet fs = new FileSet();
116: fs.setProject(getProject());
117: fs.setDir(fromDir);
118: addFileset(fs);
119: DirectoryScanner ds = fs
120: .getDirectoryScanner(getProject());
121: String[] files = ds.getIncludedFiles();
122: String[] dirs = ds.getIncludedDirectories();
123: scan(fromDir, toDir, files, dirs);
124: }
125: }
126: }
127: int moveCount = fileCopyMap.size();
128: if (moveCount > 0) { // files to move
129: log("Moving " + moveCount + " file"
130: + ((moveCount == 1) ? "" : "s") + " to "
131: + destDir.getAbsolutePath());
132:
133: Enumeration e = fileCopyMap.keys();
134: while (e.hasMoreElements()) {
135: String fromFile = (String) e.nextElement();
136:
137: File f = new File(fromFile);
138: boolean selfMove = false;
139: if (f.exists()) { //Is this file still available to be moved?
140: String[] toFiles = (String[]) fileCopyMap
141: .get(fromFile);
142: for (int i = 0; i < toFiles.length; i++) {
143: String toFile = (String) toFiles[i];
144:
145: if (fromFile.equals(toFile)) {
146: log("Skipping self-move of " + fromFile,
147: verbosity);
148: selfMove = true;
149:
150: // if this is the last time through the loop then
151: // move will not occur, but that's what we want
152: continue;
153: }
154: File d = new File(toFile);
155: if ((i + 1) == toFiles.length && !selfMove) {
156: // Only try to move if this is the last mapped file
157: // and one of the mappings isn't to itself
158: moveFile(f, d, filtering, forceOverwrite);
159: } else {
160: copyFile(f, d, filtering, forceOverwrite);
161: }
162: }
163: }
164: }
165: }
166:
167: if (includeEmpty) {
168: Enumeration e = dirCopyMap.keys();
169: int createCount = 0;
170: while (e.hasMoreElements()) {
171: String fromDirName = (String) e.nextElement();
172: String[] toDirNames = (String[]) dirCopyMap
173: .get(fromDirName);
174: boolean selfMove = false;
175: for (int i = 0; i < toDirNames.length; i++) {
176:
177: if (fromDirName.equals(toDirNames[i])) {
178: log("Skipping self-move of " + fromDirName,
179: verbosity);
180: selfMove = true;
181: continue;
182: }
183:
184: File d = new File(toDirNames[i]);
185: if (!d.exists()) {
186: if (!d.mkdirs()) {
187: log("Unable to create directory "
188: + d.getAbsolutePath(),
189: Project.MSG_ERR);
190: } else {
191: createCount++;
192: }
193: }
194: }
195:
196: File fromDir = new File(fromDirName);
197: if (!selfMove && okToDelete(fromDir)) {
198: deleteDir(fromDir);
199: }
200:
201: }
202:
203: if (createCount > 0) {
204: log("Moved " + dirCopyMap.size() + " empty director"
205: + (dirCopyMap.size() == 1 ? "y" : "ies")
206: + " to " + createCount + " empty director"
207: + (createCount == 1 ? "y" : "ies") + " under "
208: + destDir.getAbsolutePath());
209: }
210: }
211: }
212:
213: /**
214: * Try to move the file via a rename, but if this fails or filtering
215: * is enabled, copy the file then delete the sourceFile.
216: */
217: private void moveFile(File fromFile, File toFile,
218: boolean filtering, boolean overwrite) {
219: boolean moved = false;
220: try {
221: log("Attempting to rename: " + fromFile + " to " + toFile,
222: verbosity);
223: moved = renameFile(fromFile, toFile, filtering,
224: forceOverwrite);
225: } catch (IOException ioe) {
226: String msg = "Failed to rename " + fromFile + " to "
227: + toFile + " due to " + ioe.getMessage();
228: throw new BuildException(msg, ioe, getLocation());
229: }
230:
231: if (!moved) {
232: copyFile(fromFile, toFile, filtering, overwrite);
233: if (!fromFile.delete()) {
234: throw new BuildException("Unable to delete " + "file "
235: + fromFile.getAbsolutePath());
236: }
237: }
238: }
239:
240: /**
241: * Copy fromFile to toFile.
242: * @param fromFile
243: * @param toFile
244: * @param filtering
245: * @param overwrite
246: */
247: private void copyFile(File fromFile, File toFile,
248: boolean filtering, boolean overwrite) {
249: try {
250: log("Copying " + fromFile + " to " + toFile, verbosity);
251:
252: FilterSetCollection executionFilters = new FilterSetCollection();
253: if (filtering) {
254: executionFilters.addFilterSet(getProject()
255: .getGlobalFilterSet());
256: }
257: for (Enumeration filterEnum = getFilterSets().elements(); filterEnum
258: .hasMoreElements();) {
259: executionFilters.addFilterSet((FilterSet) filterEnum
260: .nextElement());
261: }
262:
263: getFileUtils().copyFile(fromFile, toFile, executionFilters,
264: getFilterChains(), forceOverwrite,
265: getPreserveLastModified(), getEncoding(),
266: getOutputEncoding(), getProject());
267:
268: } catch (IOException ioe) {
269: String msg = "Failed to copy " + fromFile + " to " + toFile
270: + " due to " + ioe.getMessage();
271: throw new BuildException(msg, ioe, getLocation());
272: }
273: }
274:
275: /**
276: * Its only ok to delete a directory tree if there are
277: * no files in it.
278: * @param d the directory to check
279: * @return true if a deletion can go ahead
280: */
281: protected boolean okToDelete(File d) {
282: String[] list = d.list();
283: if (list == null) {
284: return false;
285: } // maybe io error?
286:
287: for (int i = 0; i < list.length; i++) {
288: String s = list[i];
289: File f = new File(d, s);
290: if (f.isDirectory()) {
291: if (!okToDelete(f)) {
292: return false;
293: }
294: } else {
295: return false; // found a file
296: }
297: }
298:
299: return true;
300: }
301:
302: /**
303: * Go and delete the directory tree.
304: * @param d the directory to delete
305: */
306: protected void deleteDir(File d) {
307: deleteDir(d, false);
308: }
309:
310: /**
311: * Go and delete the directory tree.
312: * @param d the directory to delete
313: * @param deleteFiles whether to delete files
314: */
315: protected void deleteDir(File d, boolean deleteFiles) {
316: String[] list = d.list();
317: if (list == null) {
318: return;
319: } // on an io error list() can return null
320:
321: for (int i = 0; i < list.length; i++) {
322: String s = list[i];
323: File f = new File(d, s);
324: if (f.isDirectory()) {
325: deleteDir(f);
326: } else if (deleteFiles && !(f.delete())) {
327: throw new BuildException("Unable to delete file "
328: + f.getAbsolutePath());
329: } else {
330: throw new BuildException("UNEXPECTED ERROR - The file "
331: + f.getAbsolutePath() + " should not exist!");
332: }
333: }
334: log("Deleting directory " + d.getAbsolutePath(), verbosity);
335: if (!d.delete()) {
336: throw new BuildException("Unable to delete directory "
337: + d.getAbsolutePath());
338: }
339: }
340:
341: /**
342: * Attempts to rename a file from a source to a destination.
343: * If overwrite is set to true, this method overwrites existing file
344: * even if the destination file is newer. Otherwise, the source file is
345: * renamed only if the destination file is older than it.
346: * Method then checks if token filtering is used. If it is, this method
347: * returns false assuming it is the responsibility to the copyFile method.
348: *
349: * @param sourceFile the file to rename
350: * @param destFile the destination file
351: * @param filtering if true, filtering is in operation, file will
352: * be copied/deleted instead of renamed
353: * @param overwrite if true force overwrite even if destination file
354: * is newer than source file
355: * @return true if the file was renamed
356: * @exception IOException if an error occurs
357: * @exception BuildException if an error occurs
358: */
359: protected boolean renameFile(File sourceFile, File destFile,
360: boolean filtering, boolean overwrite) throws IOException,
361: BuildException {
362:
363: boolean renamed = false;
364: if ((getFilterSets().size() + getFilterChains().size() == 0)
365: && !(filtering || destFile.isDirectory())) {
366: // ensure that parent dir of dest file exists!
367: File parent = destFile.getParentFile();
368: if (parent != null && !parent.exists()) {
369: parent.mkdirs();
370: }
371: if (destFile.isFile() && !destFile.equals(sourceFile)
372: && !destFile.delete()) {
373: throw new BuildException("Unable to remove existing "
374: + "file " + destFile);
375: }
376: renamed = sourceFile.renameTo(destFile);
377: }
378: return renamed;
379: }
380: }
|