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