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 java.util.Hashtable;
  22. import java.util.Vector;
  23. import org.apache.tools.ant.BuildException;
  24. import org.apache.tools.ant.DirectoryScanner;
  25. import org.apache.tools.ant.Project;
  26. import org.apache.tools.ant.Task;
  27. import org.apache.tools.ant.types.FileSet;
  28. import org.apache.tools.ant.types.FilterChain;
  29. import org.apache.tools.ant.types.FilterSet;
  30. import org.apache.tools.ant.types.FilterSetCollection;
  31. import org.apache.tools.ant.types.Mapper;
  32. import org.apache.tools.ant.util.FileNameMapper;
  33. import org.apache.tools.ant.util.FileUtils;
  34. import org.apache.tools.ant.util.FlatFileNameMapper;
  35. import org.apache.tools.ant.util.IdentityMapper;
  36. import org.apache.tools.ant.util.SourceFileScanner;
  37. /**
  38. * Copies a file or directory to a new file
  39. * or directory. Files are only copied if the source file is newer
  40. * than the destination file, or when the destination file does not
  41. * exist. It is possible to explicitly overwrite existing files.</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. * @version $Revision: 1.66.2.5 $
  48. *
  49. * @since Ant 1.2
  50. *
  51. * @ant.task category="filesystem"
  52. */
  53. public class Copy extends Task {
  54. protected File file = null; // the source file
  55. protected File destFile = null; // the destination file
  56. protected File destDir = null; // the destination directory
  57. protected Vector filesets = new Vector();
  58. private boolean enableMultipleMappings = false;
  59. protected boolean filtering = false;
  60. protected boolean preserveLastModified = false;
  61. protected boolean forceOverwrite = false;
  62. protected boolean flatten = false;
  63. protected int verbosity = Project.MSG_VERBOSE;
  64. protected boolean includeEmpty = true;
  65. private boolean failonerror = true;
  66. protected Hashtable fileCopyMap = new Hashtable();
  67. protected Hashtable dirCopyMap = new Hashtable();
  68. protected Hashtable completeDirMap = new Hashtable();
  69. protected Mapper mapperElement = null;
  70. private Vector filterChains = new Vector();
  71. private Vector filterSets = new Vector();
  72. private FileUtils fileUtils;
  73. private String inputEncoding = null;
  74. private String outputEncoding = null;
  75. private long granularity = 0;
  76. /**
  77. * Copy task constructor.
  78. */
  79. public Copy() {
  80. fileUtils = FileUtils.newFileUtils();
  81. granularity = fileUtils.getFileTimestampGranularity();
  82. }
  83. /**
  84. * @return the fileutils object
  85. */
  86. protected FileUtils getFileUtils() {
  87. return fileUtils;
  88. }
  89. /**
  90. * Sets a single source file to copy.
  91. * @param file the file to copy
  92. */
  93. public void setFile(File file) {
  94. this.file = file;
  95. }
  96. /**
  97. * Sets the destination file.
  98. * @param destFile the file to copy to
  99. */
  100. public void setTofile(File destFile) {
  101. this.destFile = destFile;
  102. }
  103. /**
  104. * Sets the destination directory.
  105. * @param destDir the destination directory
  106. */
  107. public void setTodir(File destDir) {
  108. this.destDir = destDir;
  109. }
  110. /**
  111. * Adds a FilterChain.
  112. * @return a filter chain object
  113. */
  114. public FilterChain createFilterChain() {
  115. FilterChain filterChain = new FilterChain();
  116. filterChains.addElement(filterChain);
  117. return filterChain;
  118. }
  119. /**
  120. * Adds a filterset.
  121. * @return a filter set object
  122. */
  123. public FilterSet createFilterSet() {
  124. FilterSet filterSet = new FilterSet();
  125. filterSets.addElement(filterSet);
  126. return filterSet;
  127. }
  128. /**
  129. * Give the copied files the same last modified time as the original files.
  130. * @param preserve a boolean string
  131. * @deprecated setPreserveLastModified(String) has been deprecated and
  132. * replaced with setPreserveLastModified(boolean) to
  133. * consistently let the Introspection mechanism work.
  134. */
  135. public void setPreserveLastModified(String preserve) {
  136. setPreserveLastModified(Project.toBoolean(preserve));
  137. }
  138. /**
  139. * Give the copied files the same last modified time as the original files.
  140. * @param preserve if true perverse the modified time, default is false
  141. */
  142. public void setPreserveLastModified(boolean preserve) {
  143. preserveLastModified = preserve;
  144. }
  145. /**
  146. * Whether to give the copied files the same last modified time as
  147. * the original files.
  148. * @return the preserveLastModified attribute
  149. * @since 1.32, Ant 1.5
  150. */
  151. public boolean getPreserveLastModified() {
  152. return preserveLastModified;
  153. }
  154. /**
  155. * Get the filtersets being applied to this operation.
  156. *
  157. * @return a vector of FilterSet objects
  158. */
  159. protected Vector getFilterSets() {
  160. return filterSets;
  161. }
  162. /**
  163. * Get the filterchains being applied to this operation.
  164. *
  165. * @return a vector of FilterChain objects
  166. */
  167. protected Vector getFilterChains() {
  168. return filterChains;
  169. }
  170. /**
  171. * If true, enables filtering.
  172. * @param filtering if true enable filtering, default is false
  173. */
  174. public void setFiltering(boolean filtering) {
  175. this.filtering = filtering;
  176. }
  177. /**
  178. * Overwrite any existing destination file(s).
  179. * @param overwrite if true force overwriting of destination file(s)
  180. * even if the destination file(s) are younger than
  181. * the corresponding source file. Default is false.
  182. */
  183. public void setOverwrite(boolean overwrite) {
  184. this.forceOverwrite = overwrite;
  185. }
  186. /**
  187. * When copying directory trees, the files can be "flattened"
  188. * into a single directory. If there are multiple files with
  189. * the same name in the source directory tree, only the first
  190. * file will be copied into the "flattened" directory, unless
  191. * the forceoverwrite attribute is true.
  192. * @param flatten if true flatten the destination directory. Default
  193. * is false.
  194. */
  195. public void setFlatten(boolean flatten) {
  196. this.flatten = flatten;
  197. }
  198. /**
  199. * Used to force listing of all names of copied files.
  200. * @param verbose output the names of copied files. Default is false.
  201. */
  202. public void setVerbose(boolean verbose) {
  203. if (verbose) {
  204. this.verbosity = Project.MSG_INFO;
  205. } else {
  206. this.verbosity = Project.MSG_VERBOSE;
  207. }
  208. }
  209. /**
  210. * Used to copy empty directories.
  211. * @param includeEmpty if true copy empty directories. Default is true.
  212. */
  213. public void setIncludeEmptyDirs(boolean includeEmpty) {
  214. this.includeEmpty = includeEmpty;
  215. }
  216. /**
  217. * Attribute to handle mappers that return multiple
  218. * mappings for a given source path.
  219. * @param enableMultipleMappings If true the task will
  220. * copy to all the mappings for a given source path, if
  221. * false, only the first file or directory is
  222. * processed.
  223. * By default, this setting is false to provide backward
  224. * compatibility with earlier releases.
  225. * @since 1.6
  226. */
  227. public void setEnableMultipleMappings(boolean enableMultipleMappings) {
  228. this.enableMultipleMappings = enableMultipleMappings;
  229. }
  230. /**
  231. * @return the value of the enableMultipleMapping attribute
  232. */
  233. public boolean isEnableMultipleMapping() {
  234. return enableMultipleMappings;
  235. }
  236. /**
  237. * If false, note errors to the output but keep going.
  238. * @param failonerror true or false
  239. */
  240. public void setFailOnError(boolean failonerror) {
  241. this.failonerror = failonerror;
  242. }
  243. /**
  244. * Adds a set of files to copy.
  245. * @param set a set of files to copy
  246. */
  247. public void addFileset(FileSet set) {
  248. filesets.addElement(set);
  249. }
  250. /**
  251. * Defines the mapper to map source to destination files.
  252. * @return a mapper to be configured
  253. * @exception BuildException if more than one mapper is defined
  254. */
  255. public Mapper createMapper() throws BuildException {
  256. if (mapperElement != null) {
  257. throw new BuildException("Cannot define more than one mapper",
  258. getLocation());
  259. }
  260. mapperElement = new Mapper(getProject());
  261. return mapperElement;
  262. }
  263. /**
  264. * Sets the character encoding
  265. * @param encoding the character encoding
  266. * @since 1.32, Ant 1.5
  267. */
  268. public void setEncoding(String encoding) {
  269. this.inputEncoding = encoding;
  270. if (outputEncoding == null) {
  271. outputEncoding = encoding;
  272. }
  273. }
  274. /**
  275. * @return the character encoding, <code>null</code> if not set.
  276. *
  277. * @since 1.32, Ant 1.5
  278. */
  279. public String getEncoding() {
  280. return inputEncoding;
  281. }
  282. /**
  283. * Sets the character encoding for output files.
  284. * @param encoding the character encoding
  285. * @since Ant 1.6
  286. */
  287. public void setOutputEncoding(String encoding) {
  288. this.outputEncoding = encoding;
  289. }
  290. /**
  291. * @return the character encoding for output files,
  292. * <code>null</code> if not set.
  293. *
  294. * @since Ant 1.6
  295. */
  296. public String getOutputEncoding() {
  297. return outputEncoding;
  298. }
  299. /**
  300. * The number of milliseconds leeway to give before deciding a
  301. * target is out of date.
  302. *
  303. * <p>Default is 0 milliseconds, or 2 seconds on DOS systems.</p>
  304. *
  305. * @since Ant 1.6.2
  306. */
  307. public void setGranularity(long granularity) {
  308. this.granularity = granularity;
  309. }
  310. /**
  311. * Performs the copy operation.
  312. * @exception BuildException if an error occurs
  313. */
  314. public void execute() throws BuildException {
  315. File savedFile = file; // may be altered in validateAttributes
  316. File savedDestFile = destFile;
  317. File savedDestDir = destDir;
  318. FileSet savedFileSet = null;
  319. if (file == null && destFile != null && filesets.size() == 1) {
  320. // will be removed in validateAttributes
  321. savedFileSet = (FileSet) filesets.elementAt(0);
  322. }
  323. // make sure we don't have an illegal set of options
  324. validateAttributes();
  325. try {
  326. // deal with the single file
  327. if (file != null) {
  328. if (file.exists()) {
  329. if (destFile == null) {
  330. destFile = new File(destDir, file.getName());
  331. }
  332. if (forceOverwrite || !destFile.exists()
  333. || (file.lastModified() - granularity
  334. > destFile.lastModified())) {
  335. fileCopyMap.put(file.getAbsolutePath(),
  336. new String[] {destFile.getAbsolutePath()});
  337. } else {
  338. log(file + " omitted as " + destFile
  339. + " is up to date.", Project.MSG_VERBOSE);
  340. }
  341. } else {
  342. String message = "Warning: Could not find file "
  343. + file.getAbsolutePath() + " to copy.";
  344. if (!failonerror) {
  345. log(message);
  346. } else {
  347. throw new BuildException(message);
  348. }
  349. }
  350. }
  351. // deal with the filesets
  352. for (int i = 0; i < filesets.size(); i++) {
  353. FileSet fs = (FileSet) filesets.elementAt(i);
  354. DirectoryScanner ds = null;
  355. try {
  356. ds = fs.getDirectoryScanner(getProject());
  357. } catch (BuildException e) {
  358. if (failonerror
  359. || !e.getMessage().endsWith(" not found.")) {
  360. throw e;
  361. } else {
  362. log("Warning: " + e.getMessage());
  363. continue;
  364. }
  365. }
  366. File fromDir = fs.getDir(getProject());
  367. String[] srcFiles = ds.getIncludedFiles();
  368. String[] srcDirs = ds.getIncludedDirectories();
  369. boolean isEverythingIncluded = ds.isEverythingIncluded()
  370. && (!fs.hasSelectors() && !fs.hasPatterns());
  371. if (isEverythingIncluded
  372. && !flatten && mapperElement == null) {
  373. completeDirMap.put(fromDir, destDir);
  374. }
  375. scan(fromDir, destDir, srcFiles, srcDirs);
  376. }
  377. // do all the copy operations now...
  378. try {
  379. doFileOperations();
  380. } catch (BuildException e) {
  381. if (!failonerror) {
  382. log("Warning: " + e.getMessage(), Project.MSG_ERR);
  383. } else {
  384. throw e;
  385. }
  386. }
  387. } finally {
  388. // clean up again, so this instance can be used a second
  389. // time
  390. file = savedFile;
  391. destFile = savedDestFile;
  392. destDir = savedDestDir;
  393. if (savedFileSet != null) {
  394. filesets.insertElementAt(savedFileSet, 0);
  395. }
  396. fileCopyMap.clear();
  397. dirCopyMap.clear();
  398. completeDirMap.clear();
  399. }
  400. }
  401. /************************************************************************
  402. ** protected and private methods
  403. ************************************************************************/
  404. /**
  405. * Ensure we have a consistent and legal set of attributes, and set
  406. * any internal flags necessary based on different combinations
  407. * of attributes.
  408. * @exception BuildException if an error occurs
  409. */
  410. protected void validateAttributes() throws BuildException {
  411. if (file == null && filesets.size() == 0) {
  412. throw new BuildException("Specify at least one source "
  413. + "- a file or a fileset.");
  414. }
  415. if (destFile != null && destDir != null) {
  416. throw new BuildException("Only one of tofile and todir "
  417. + "may be set.");
  418. }
  419. if (destFile == null && destDir == null) {
  420. throw new BuildException("One of tofile or todir must be set.");
  421. }
  422. if (file != null && file.exists() && file.isDirectory()) {
  423. throw new BuildException("Use a fileset to copy directories.");
  424. }
  425. if (destFile != null && filesets.size() > 0) {
  426. if (filesets.size() > 1) {
  427. throw new BuildException(
  428. "Cannot concatenate multiple files into a single file.");
  429. } else {
  430. FileSet fs = (FileSet) filesets.elementAt(0);
  431. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  432. String[] srcFiles = ds.getIncludedFiles();
  433. if (srcFiles.length == 0) {
  434. throw new BuildException(
  435. "Cannot perform operation from directory to file.");
  436. } else if (srcFiles.length == 1) {
  437. if (file == null) {
  438. file = new File(ds.getBasedir(), srcFiles[0]);
  439. filesets.removeElementAt(0);
  440. } else {
  441. throw new BuildException("Cannot concatenate multiple "
  442. + "files into a single file.");
  443. }
  444. } else {
  445. throw new BuildException("Cannot concatenate multiple "
  446. + "files into a single file.");
  447. }
  448. }
  449. }
  450. if (destFile != null) {
  451. destDir = fileUtils.getParentFile(destFile);
  452. }
  453. }
  454. /**
  455. * Compares source files to destination files to see if they should be
  456. * copied.
  457. *
  458. * @param fromDir The source directory
  459. * @param toDir The destination directory
  460. * @param files A list of files to copy
  461. * @param dirs A list of directories to copy
  462. */
  463. protected void scan(File fromDir, File toDir, String[] files,
  464. String[] dirs) {
  465. FileNameMapper mapper = null;
  466. if (mapperElement != null) {
  467. mapper = mapperElement.getImplementation();
  468. } else if (flatten) {
  469. mapper = new FlatFileNameMapper();
  470. } else {
  471. mapper = new IdentityMapper();
  472. }
  473. buildMap(fromDir, toDir, files, mapper, fileCopyMap);
  474. if (includeEmpty) {
  475. buildMap(fromDir, toDir, dirs, mapper, dirCopyMap);
  476. }
  477. }
  478. /**
  479. * Add to a map of files/directories to copy
  480. *
  481. * @param fromDir the source directory
  482. * @param toDir the destination directory
  483. * @param names a list of filenames
  484. * @param mapper a <code>FileNameMapper</code> value
  485. * @param map a map of source file to array of destination files
  486. */
  487. protected void buildMap(File fromDir, File toDir, String[] names,
  488. FileNameMapper mapper, Hashtable map) {
  489. String[] toCopy = null;
  490. if (forceOverwrite) {
  491. Vector v = new Vector();
  492. for (int i = 0; i < names.length; i++) {
  493. if (mapper.mapFileName(names[i]) != null) {
  494. v.addElement(names[i]);
  495. }
  496. }
  497. toCopy = new String[v.size()];
  498. v.copyInto(toCopy);
  499. } else {
  500. SourceFileScanner ds = new SourceFileScanner(this);
  501. toCopy = ds.restrict(names, fromDir, toDir, mapper, granularity);
  502. }
  503. for (int i = 0; i < toCopy.length; i++) {
  504. File src = new File(fromDir, toCopy[i]);
  505. String[] mappedFiles = mapper.mapFileName(toCopy[i]);
  506. if (!enableMultipleMappings) {
  507. map.put(src.getAbsolutePath(),
  508. new String[] {new File(toDir, mappedFiles[0]).getAbsolutePath()});
  509. } else {
  510. // reuse the array created by the mapper
  511. for (int k = 0; k < mappedFiles.length; k++) {
  512. mappedFiles[k] = new File(toDir, mappedFiles[k]).getAbsolutePath();
  513. }
  514. map.put(src.getAbsolutePath(), mappedFiles);
  515. }
  516. }
  517. }
  518. /**
  519. * Actually does the file (and possibly empty directory) copies.
  520. * This is a good method for subclasses to override.
  521. */
  522. protected void doFileOperations() {
  523. if (fileCopyMap.size() > 0) {
  524. log("Copying " + fileCopyMap.size()
  525. + " file" + (fileCopyMap.size() == 1 ? "" : "s")
  526. + " to " + destDir.getAbsolutePath());
  527. Enumeration e = fileCopyMap.keys();
  528. while (e.hasMoreElements()) {
  529. String fromFile = (String) e.nextElement();
  530. String[] toFiles = (String[]) fileCopyMap.get(fromFile);
  531. for (int i = 0; i < toFiles.length; i++) {
  532. String toFile = toFiles[i];
  533. if (fromFile.equals(toFile)) {
  534. log("Skipping self-copy of " + fromFile, verbosity);
  535. continue;
  536. }
  537. try {
  538. log("Copying " + fromFile + " to " + toFile, verbosity);
  539. FilterSetCollection executionFilters =
  540. new FilterSetCollection();
  541. if (filtering) {
  542. executionFilters
  543. .addFilterSet(getProject().getGlobalFilterSet());
  544. }
  545. for (Enumeration filterEnum = filterSets.elements();
  546. filterEnum.hasMoreElements();) {
  547. executionFilters
  548. .addFilterSet((FilterSet) filterEnum.nextElement());
  549. }
  550. fileUtils.copyFile(fromFile, toFile, executionFilters,
  551. filterChains, forceOverwrite,
  552. preserveLastModified, inputEncoding,
  553. outputEncoding, getProject());
  554. } catch (IOException ioe) {
  555. String msg = "Failed to copy " + fromFile + " to " + toFile
  556. + " due to " + ioe.getMessage();
  557. File targetFile = new File(toFile);
  558. if (targetFile.exists() && !targetFile.delete()) {
  559. msg += " and I couldn't delete the corrupt " + toFile;
  560. }
  561. throw new BuildException(msg, ioe, getLocation());
  562. }
  563. }
  564. }
  565. }
  566. if (includeEmpty) {
  567. Enumeration e = dirCopyMap.elements();
  568. int createCount = 0;
  569. while (e.hasMoreElements()) {
  570. String[] dirs = (String[]) e.nextElement();
  571. for (int i = 0; i < dirs.length; i++) {
  572. File d = new File(dirs[i]);
  573. if (!d.exists()) {
  574. if (!d.mkdirs()) {
  575. log("Unable to create directory "
  576. + d.getAbsolutePath(), Project.MSG_ERR);
  577. } else {
  578. createCount++;
  579. }
  580. }
  581. }
  582. }
  583. if (createCount > 0) {
  584. log("Copied " + dirCopyMap.size()
  585. + " empty director"
  586. + (dirCopyMap.size() == 1 ? "y" : "ies")
  587. + " to " + createCount
  588. + " empty director"
  589. + (createCount == 1 ? "y" : "ies") + " under "
  590. + destDir.getAbsolutePath());
  591. }
  592. }
  593. }
  594. }