0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: *
0017: */
0018:
0019: package org.apache.tools.ant.taskdefs;
0020:
0021: import java.io.File;
0022: import java.io.IOException;
0023: import java.util.ArrayList;
0024: import java.util.Enumeration;
0025: import java.util.HashMap;
0026: import java.util.HashSet;
0027: import java.util.Hashtable;
0028: import java.util.Iterator;
0029: import java.util.List;
0030: import java.util.Map;
0031: import java.util.Vector;
0032: import org.apache.tools.ant.Task;
0033: import org.apache.tools.ant.Project;
0034: import org.apache.tools.ant.BuildException;
0035: import org.apache.tools.ant.DirectoryScanner;
0036: import org.apache.tools.ant.types.Mapper;
0037: import org.apache.tools.ant.types.FileSet;
0038: import org.apache.tools.ant.types.FilterSet;
0039: import org.apache.tools.ant.types.FilterChain;
0040: import org.apache.tools.ant.types.FilterSetCollection;
0041: import org.apache.tools.ant.types.Resource;
0042: import org.apache.tools.ant.types.ResourceCollection;
0043: import org.apache.tools.ant.types.ResourceFactory;
0044: import org.apache.tools.ant.types.resources.FileResource;
0045: import org.apache.tools.ant.util.FileUtils;
0046: import org.apache.tools.ant.util.FileNameMapper;
0047: import org.apache.tools.ant.util.IdentityMapper;
0048: import org.apache.tools.ant.util.ResourceUtils;
0049: import org.apache.tools.ant.util.SourceFileScanner;
0050: import org.apache.tools.ant.util.FlatFileNameMapper;
0051:
0052: /**
0053: * Copies a file or directory to a new file
0054: * or directory. Files are only copied if the source file is newer
0055: * than the destination file, or when the destination file does not
0056: * exist. It is possible to explicitly overwrite existing files.</p>
0057: *
0058: * <p>This implementation is based on Arnout Kuiper's initial design
0059: * document, the following mailing list discussions, and the
0060: * copyfile/copydir tasks.</p>
0061: *
0062: *
0063: * @since Ant 1.2
0064: *
0065: * @ant.task category="filesystem"
0066: */
0067: public class Copy extends Task {
0068: static final File NULL_FILE_PLACEHOLDER = new File("/NULL_FILE");
0069: static final String LINE_SEPARATOR = System
0070: .getProperty("line.separator");
0071: // CheckStyle:VisibilityModifier OFF - bc
0072: protected File file = null; // the source file
0073: protected File destFile = null; // the destination file
0074: protected File destDir = null; // the destination directory
0075: protected Vector rcs = new Vector();
0076:
0077: private boolean enableMultipleMappings = false;
0078: protected boolean filtering = false;
0079: protected boolean preserveLastModified = false;
0080: protected boolean forceOverwrite = false;
0081: protected boolean flatten = false;
0082: protected int verbosity = Project.MSG_VERBOSE;
0083: protected boolean includeEmpty = true;
0084: protected boolean failonerror = true;
0085:
0086: protected Hashtable fileCopyMap = new Hashtable();
0087: protected Hashtable dirCopyMap = new Hashtable();
0088: protected Hashtable completeDirMap = new Hashtable();
0089:
0090: protected Mapper mapperElement = null;
0091: protected FileUtils fileUtils;
0092: private Vector filterChains = new Vector();
0093: private Vector filterSets = new Vector();
0094: private String inputEncoding = null;
0095: private String outputEncoding = null;
0096: private long granularity = 0;
0097:
0098: // CheckStyle:VisibilityModifier ON
0099:
0100: /**
0101: * Copy task constructor.
0102: */
0103: public Copy() {
0104: fileUtils = FileUtils.getFileUtils();
0105: granularity = fileUtils.getFileTimestampGranularity();
0106: }
0107:
0108: /**
0109: * Get the FileUtils for this task.
0110: * @return the fileutils object.
0111: */
0112: protected FileUtils getFileUtils() {
0113: return fileUtils;
0114: }
0115:
0116: /**
0117: * Set a single source file to copy.
0118: * @param file the file to copy.
0119: */
0120: public void setFile(File file) {
0121: this .file = file;
0122: }
0123:
0124: /**
0125: * Set the destination file.
0126: * @param destFile the file to copy to.
0127: */
0128: public void setTofile(File destFile) {
0129: this .destFile = destFile;
0130: }
0131:
0132: /**
0133: * Set the destination directory.
0134: * @param destDir the destination directory.
0135: */
0136: public void setTodir(File destDir) {
0137: this .destDir = destDir;
0138: }
0139:
0140: /**
0141: * Add a FilterChain.
0142: * @return a filter chain object.
0143: */
0144: public FilterChain createFilterChain() {
0145: FilterChain filterChain = new FilterChain();
0146: filterChains.addElement(filterChain);
0147: return filterChain;
0148: }
0149:
0150: /**
0151: * Add a filterset.
0152: * @return a filter set object.
0153: */
0154: public FilterSet createFilterSet() {
0155: FilterSet filterSet = new FilterSet();
0156: filterSets.addElement(filterSet);
0157: return filterSet;
0158: }
0159:
0160: /**
0161: * Give the copied files the same last modified time as the original files.
0162: * @param preserve a boolean string.
0163: * @deprecated since 1.5.x.
0164: * setPreserveLastModified(String) has been deprecated and
0165: * replaced with setPreserveLastModified(boolean) to
0166: * consistently let the Introspection mechanism work.
0167: */
0168: public void setPreserveLastModified(String preserve) {
0169: setPreserveLastModified(Project.toBoolean(preserve));
0170: }
0171:
0172: /**
0173: * Give the copied files the same last modified time as the original files.
0174: * @param preserve if true preserve the modified time; default is false.
0175: */
0176: public void setPreserveLastModified(boolean preserve) {
0177: preserveLastModified = preserve;
0178: }
0179:
0180: /**
0181: * Get whether to give the copied files the same last modified time as
0182: * the original files.
0183: * @return the whether destination files will inherit the modification
0184: * times of the corresponding source files.
0185: * @since 1.32, Ant 1.5
0186: */
0187: public boolean getPreserveLastModified() {
0188: return preserveLastModified;
0189: }
0190:
0191: /**
0192: * Get the filtersets being applied to this operation.
0193: *
0194: * @return a vector of FilterSet objects.
0195: */
0196: protected Vector getFilterSets() {
0197: return filterSets;
0198: }
0199:
0200: /**
0201: * Get the filterchains being applied to this operation.
0202: *
0203: * @return a vector of FilterChain objects.
0204: */
0205: protected Vector getFilterChains() {
0206: return filterChains;
0207: }
0208:
0209: /**
0210: * Set filtering mode.
0211: * @param filtering if true enable filtering; default is false.
0212: */
0213: public void setFiltering(boolean filtering) {
0214: this .filtering = filtering;
0215: }
0216:
0217: /**
0218: * Set overwrite mode regarding existing destination file(s).
0219: * @param overwrite if true force overwriting of destination file(s)
0220: * even if the destination file(s) are younger than
0221: * the corresponding source file. Default is false.
0222: */
0223: public void setOverwrite(boolean overwrite) {
0224: this .forceOverwrite = overwrite;
0225: }
0226:
0227: /**
0228: * Set whether files copied from directory trees will be "flattened"
0229: * into a single directory. If there are multiple files with
0230: * the same name in the source directory tree, only the first
0231: * file will be copied into the "flattened" directory, unless
0232: * the forceoverwrite attribute is true.
0233: * @param flatten if true flatten the destination directory. Default
0234: * is false.
0235: */
0236: public void setFlatten(boolean flatten) {
0237: this .flatten = flatten;
0238: }
0239:
0240: /**
0241: * Set verbose mode. Used to force listing of all names of copied files.
0242: * @param verbose whether to output the names of copied files.
0243: * Default is false.
0244: */
0245: public void setVerbose(boolean verbose) {
0246: this .verbosity = verbose ? Project.MSG_INFO
0247: : Project.MSG_VERBOSE;
0248: }
0249:
0250: /**
0251: * Set whether to copy empty directories.
0252: * @param includeEmpty if true copy empty directories. Default is true.
0253: */
0254: public void setIncludeEmptyDirs(boolean includeEmpty) {
0255: this .includeEmpty = includeEmpty;
0256: }
0257:
0258: /**
0259: * Set method of handling mappers that return multiple
0260: * mappings for a given source path.
0261: * @param enableMultipleMappings If true the task will
0262: * copy to all the mappings for a given source path, if
0263: * false, only the first file or directory is
0264: * processed.
0265: * By default, this setting is false to provide backward
0266: * compatibility with earlier releases.
0267: * @since Ant 1.6
0268: */
0269: public void setEnableMultipleMappings(boolean enableMultipleMappings) {
0270: this .enableMultipleMappings = enableMultipleMappings;
0271: }
0272:
0273: /**
0274: * Get whether multiple mapping is enabled.
0275: * @return true if multiple mapping is enabled; false otherwise.
0276: */
0277: public boolean isEnableMultipleMapping() {
0278: return enableMultipleMappings;
0279: }
0280:
0281: /**
0282: * Set whether to fail when errors are encountered. If false, note errors
0283: * to the output but keep going. Default is true.
0284: * @param failonerror true or false.
0285: */
0286: public void setFailOnError(boolean failonerror) {
0287: this .failonerror = failonerror;
0288: }
0289:
0290: /**
0291: * Add a set of files to copy.
0292: * @param set a set of files to copy.
0293: */
0294: public void addFileset(FileSet set) {
0295: add(set);
0296: }
0297:
0298: /**
0299: * Add a collection of files to copy.
0300: * @param res a resource collection to copy.
0301: * @since Ant 1.7
0302: */
0303: public void add(ResourceCollection res) {
0304: rcs.add(res);
0305: }
0306:
0307: /**
0308: * Define the mapper to map source to destination files.
0309: * @return a mapper to be configured.
0310: * @exception BuildException if more than one mapper is defined.
0311: */
0312: public Mapper createMapper() throws BuildException {
0313: if (mapperElement != null) {
0314: throw new BuildException(
0315: "Cannot define more than one mapper", getLocation());
0316: }
0317: mapperElement = new Mapper(getProject());
0318: return mapperElement;
0319: }
0320:
0321: /**
0322: * Add a nested filenamemapper.
0323: * @param fileNameMapper the mapper to add.
0324: * @since Ant 1.6.3
0325: */
0326: public void add(FileNameMapper fileNameMapper) {
0327: createMapper().add(fileNameMapper);
0328: }
0329:
0330: /**
0331: * Set the character encoding.
0332: * @param encoding the character encoding.
0333: * @since 1.32, Ant 1.5
0334: */
0335: public void setEncoding(String encoding) {
0336: this .inputEncoding = encoding;
0337: if (outputEncoding == null) {
0338: outputEncoding = encoding;
0339: }
0340: }
0341:
0342: /**
0343: * Get the character encoding to be used.
0344: * @return the character encoding, <code>null</code> if not set.
0345: *
0346: * @since 1.32, Ant 1.5
0347: */
0348: public String getEncoding() {
0349: return inputEncoding;
0350: }
0351:
0352: /**
0353: * Set the character encoding for output files.
0354: * @param encoding the output character encoding.
0355: * @since Ant 1.6
0356: */
0357: public void setOutputEncoding(String encoding) {
0358: this .outputEncoding = encoding;
0359: }
0360:
0361: /**
0362: * Get the character encoding for output files.
0363: * @return the character encoding for output files,
0364: * <code>null</code> if not set.
0365: *
0366: * @since Ant 1.6
0367: */
0368: public String getOutputEncoding() {
0369: return outputEncoding;
0370: }
0371:
0372: /**
0373: * Set the number of milliseconds leeway to give before deciding a
0374: * target is out of date.
0375: *
0376: * <p>Default is 1 second, or 2 seconds on DOS systems.</p>
0377: * @param granularity the granularity used to decide if a target is out of
0378: * date.
0379: * @since Ant 1.6.2
0380: */
0381: public void setGranularity(long granularity) {
0382: this .granularity = granularity;
0383: }
0384:
0385: /**
0386: * Perform the copy operation.
0387: * @exception BuildException if an error occurs.
0388: */
0389: public void execute() throws BuildException {
0390: File savedFile = file; // may be altered in validateAttributes
0391: File savedDestFile = destFile;
0392: File savedDestDir = destDir;
0393: ResourceCollection savedRc = null;
0394: if (file == null && destFile != null && rcs.size() == 1) {
0395: // will be removed in validateAttributes
0396: savedRc = (ResourceCollection) rcs.elementAt(0);
0397: }
0398: // make sure we don't have an illegal set of options
0399: validateAttributes();
0400:
0401: try {
0402: // deal with the single file
0403: if (file != null) {
0404: if (file.exists()) {
0405: if (destFile == null) {
0406: destFile = new File(destDir, file.getName());
0407: }
0408: if (forceOverwrite
0409: || !destFile.exists()
0410: || (file.lastModified() - granularity > destFile
0411: .lastModified())) {
0412: fileCopyMap.put(file.getAbsolutePath(),
0413: new String[] { destFile
0414: .getAbsolutePath() });
0415: } else {
0416: log(file + " omitted as " + destFile
0417: + " is up to date.",
0418: Project.MSG_VERBOSE);
0419: }
0420: } else {
0421: String message = "Warning: Could not find file "
0422: + file.getAbsolutePath() + " to copy.";
0423: if (!failonerror) {
0424: log(message, Project.MSG_ERR);
0425: } else {
0426: throw new BuildException(message);
0427: }
0428: }
0429: }
0430: // deal with the ResourceCollections
0431:
0432: /* for historical and performance reasons we have to do
0433: things in a rather complex way.
0434:
0435: (1) Move is optimized to move directories if a fileset
0436: has been included completely, therefore FileSets need a
0437: special treatment. This is also required to support
0438: the failOnError semantice (skip filesets with broken
0439: basedir but handle the remaining collections).
0440:
0441: (2) We carry around a few protected methods that work
0442: on basedirs and arrays of names. To optimize stuff, all
0443: resources with the same basedir get collected in
0444: separate lists and then each list is handled in one go.
0445: */
0446:
0447: HashMap filesByBasedir = new HashMap();
0448: HashMap dirsByBasedir = new HashMap();
0449: HashSet baseDirs = new HashSet();
0450: ArrayList nonFileResources = new ArrayList();
0451: for (int i = 0; i < rcs.size(); i++) {
0452: ResourceCollection rc = (ResourceCollection) rcs
0453: .elementAt(i);
0454:
0455: // Step (1) - beware of the ZipFileSet
0456: if (rc instanceof FileSet && rc.isFilesystemOnly()) {
0457: FileSet fs = (FileSet) rc;
0458: DirectoryScanner ds = null;
0459: try {
0460: ds = fs.getDirectoryScanner(getProject());
0461: } catch (BuildException e) {
0462: if (failonerror
0463: || !getMessage(e).endsWith(
0464: " not found.")) {
0465: throw e;
0466: } else {
0467: log("Warning: " + getMessage(e),
0468: Project.MSG_ERR);
0469: continue;
0470: }
0471: }
0472: File fromDir = fs.getDir(getProject());
0473:
0474: String[] srcFiles = ds.getIncludedFiles();
0475: String[] srcDirs = ds.getIncludedDirectories();
0476: if (!flatten && mapperElement == null
0477: && ds.isEverythingIncluded()
0478: && !fs.hasPatterns()) {
0479: completeDirMap.put(fromDir, destDir);
0480: }
0481: add(fromDir, srcFiles, filesByBasedir);
0482: add(fromDir, srcDirs, dirsByBasedir);
0483: baseDirs.add(fromDir);
0484: } else { // not a fileset or contains non-file resources
0485:
0486: if (!rc.isFilesystemOnly()
0487: && !supportsNonFileResources()) {
0488: throw new BuildException(
0489: "Only FileSystem resources are supported.");
0490: }
0491:
0492: Iterator resources = rc.iterator();
0493: while (resources.hasNext()) {
0494: Resource r = (Resource) resources.next();
0495: if (!r.isExists()) {
0496: continue;
0497: }
0498:
0499: File baseDir = NULL_FILE_PLACEHOLDER;
0500: String name = r.getName();
0501: if (r instanceof FileResource) {
0502: FileResource fr = (FileResource) r;
0503: baseDir = getKeyFile(fr.getBaseDir());
0504: if (fr.getBaseDir() == null) {
0505: name = fr.getFile().getAbsolutePath();
0506: }
0507: }
0508:
0509: // copying of dirs is trivial and can be done
0510: // for non-file resources as well as for real
0511: // files.
0512: if (r.isDirectory()
0513: || r instanceof FileResource) {
0514: add(baseDir, name,
0515: r.isDirectory() ? dirsByBasedir
0516: : filesByBasedir);
0517: baseDirs.add(baseDir);
0518: } else { // a not-directory file resource
0519: // needs special treatment
0520: nonFileResources.add(r);
0521: }
0522: }
0523: }
0524: }
0525:
0526: Iterator iter = baseDirs.iterator();
0527: while (iter.hasNext()) {
0528: File f = (File) iter.next();
0529: List files = (List) filesByBasedir.get(f);
0530: List dirs = (List) dirsByBasedir.get(f);
0531:
0532: String[] srcFiles = new String[0];
0533: if (files != null) {
0534: srcFiles = (String[]) files.toArray(srcFiles);
0535: }
0536: String[] srcDirs = new String[0];
0537: if (dirs != null) {
0538: srcDirs = (String[]) dirs.toArray(srcDirs);
0539: }
0540: scan(f == NULL_FILE_PLACEHOLDER ? null : f, destDir,
0541: srcFiles, srcDirs);
0542: }
0543:
0544: // do all the copy operations now...
0545: try {
0546: doFileOperations();
0547: } catch (BuildException e) {
0548: if (!failonerror) {
0549: log("Warning: " + getMessage(e), Project.MSG_ERR);
0550: } else {
0551: throw e;
0552: }
0553: }
0554:
0555: if (nonFileResources.size() > 0) {
0556: Resource[] nonFiles = (Resource[]) nonFileResources
0557: .toArray(new Resource[nonFileResources.size()]);
0558: // restrict to out-of-date resources
0559: Map map = scan(nonFiles, destDir);
0560: try {
0561: doResourceOperations(map);
0562: } catch (BuildException e) {
0563: if (!failonerror) {
0564: log("Warning: " + getMessage(e),
0565: Project.MSG_ERR);
0566: } else {
0567: throw e;
0568: }
0569: }
0570: }
0571: } finally {
0572: // clean up again, so this instance can be used a second
0573: // time
0574: file = savedFile;
0575: destFile = savedDestFile;
0576: destDir = savedDestDir;
0577: if (savedRc != null) {
0578: rcs.insertElementAt(savedRc, 0);
0579: }
0580: fileCopyMap.clear();
0581: dirCopyMap.clear();
0582: completeDirMap.clear();
0583: }
0584: }
0585:
0586: /************************************************************************
0587: ** protected and private methods
0588: ************************************************************************/
0589:
0590: /**
0591: * Ensure we have a consistent and legal set of attributes, and set
0592: * any internal flags necessary based on different combinations
0593: * of attributes.
0594: * @exception BuildException if an error occurs.
0595: */
0596: protected void validateAttributes() throws BuildException {
0597: if (file == null && rcs.size() == 0) {
0598: throw new BuildException(
0599: "Specify at least one source--a file or a resource collection.");
0600: }
0601: if (destFile != null && destDir != null) {
0602: throw new BuildException(
0603: "Only one of tofile and todir may be set.");
0604: }
0605: if (destFile == null && destDir == null) {
0606: throw new BuildException(
0607: "One of tofile or todir must be set.");
0608: }
0609: if (file != null && file.isDirectory()) {
0610: throw new BuildException(
0611: "Use a resource collection to copy directories.");
0612: }
0613: if (destFile != null && rcs.size() > 0) {
0614: if (rcs.size() > 1) {
0615: throw new BuildException(
0616: "Cannot concatenate multiple files into a single file.");
0617: } else {
0618: ResourceCollection rc = (ResourceCollection) rcs
0619: .elementAt(0);
0620: if (!rc.isFilesystemOnly()) {
0621: throw new BuildException(
0622: "Only FileSystem resources are"
0623: + " supported when concatenating"
0624: + " files.");
0625: }
0626: if (rc.size() == 0) {
0627: throw new BuildException(
0628: "Cannot perform operation from directory to file.");
0629: } else if (rc.size() == 1) {
0630: FileResource r = (FileResource) rc.iterator()
0631: .next();
0632: if (file == null) {
0633: file = r.getFile();
0634: rcs.removeElementAt(0);
0635: } else {
0636: throw new BuildException(
0637: "Cannot concatenate multiple files into a single file.");
0638: }
0639: } else {
0640: throw new BuildException(
0641: "Cannot concatenate multiple files into a single file.");
0642: }
0643: }
0644: }
0645: if (destFile != null) {
0646: destDir = destFile.getParentFile();
0647: }
0648: }
0649:
0650: /**
0651: * Compares source files to destination files to see if they should be
0652: * copied.
0653: *
0654: * @param fromDir The source directory.
0655: * @param toDir The destination directory.
0656: * @param files A list of files to copy.
0657: * @param dirs A list of directories to copy.
0658: */
0659: protected void scan(File fromDir, File toDir, String[] files,
0660: String[] dirs) {
0661: FileNameMapper mapper = getMapper();
0662: buildMap(fromDir, toDir, files, mapper, fileCopyMap);
0663:
0664: if (includeEmpty) {
0665: buildMap(fromDir, toDir, dirs, mapper, dirCopyMap);
0666: }
0667: }
0668:
0669: /**
0670: * Compares source resources to destination files to see if they
0671: * should be copied.
0672: *
0673: * @param fromResources The source resources.
0674: * @param toDir The destination directory.
0675: *
0676: * @return a Map with the out-of-date resources as keys and an
0677: * array of target file names as values.
0678: *
0679: * @since Ant 1.7
0680: */
0681: protected Map scan(Resource[] fromResources, File toDir) {
0682: return buildMap(fromResources, toDir, getMapper());
0683: }
0684:
0685: /**
0686: * Add to a map of files/directories to copy.
0687: *
0688: * @param fromDir the source directory.
0689: * @param toDir the destination directory.
0690: * @param names a list of filenames.
0691: * @param mapper a <code>FileNameMapper</code> value.
0692: * @param map a map of source file to array of destination files.
0693: */
0694: protected void buildMap(File fromDir, File toDir, String[] names,
0695: FileNameMapper mapper, Hashtable map) {
0696: String[] toCopy = null;
0697: if (forceOverwrite) {
0698: Vector v = new Vector();
0699: for (int i = 0; i < names.length; i++) {
0700: if (mapper.mapFileName(names[i]) != null) {
0701: v.addElement(names[i]);
0702: }
0703: }
0704: toCopy = new String[v.size()];
0705: v.copyInto(toCopy);
0706: } else {
0707: SourceFileScanner ds = new SourceFileScanner(this );
0708: toCopy = ds.restrict(names, fromDir, toDir, mapper,
0709: granularity);
0710: }
0711: for (int i = 0; i < toCopy.length; i++) {
0712: File src = new File(fromDir, toCopy[i]);
0713: String[] mappedFiles = mapper.mapFileName(toCopy[i]);
0714:
0715: if (!enableMultipleMappings) {
0716: map.put(src.getAbsolutePath(), new String[] { new File(
0717: toDir, mappedFiles[0]).getAbsolutePath() });
0718: } else {
0719: // reuse the array created by the mapper
0720: for (int k = 0; k < mappedFiles.length; k++) {
0721: mappedFiles[k] = new File(toDir, mappedFiles[k])
0722: .getAbsolutePath();
0723: }
0724: map.put(src.getAbsolutePath(), mappedFiles);
0725: }
0726: }
0727: }
0728:
0729: /**
0730: * Create a map of resources to copy.
0731: *
0732: * @param fromResources The source resources.
0733: * @param toDir the destination directory.
0734: * @param mapper a <code>FileNameMapper</code> value.
0735: * @return a map of source resource to array of destination files.
0736: * @since Ant 1.7
0737: */
0738: protected Map buildMap(Resource[] fromResources, final File toDir,
0739: FileNameMapper mapper) {
0740: HashMap map = new HashMap();
0741: Resource[] toCopy = null;
0742: if (forceOverwrite) {
0743: Vector v = new Vector();
0744: for (int i = 0; i < fromResources.length; i++) {
0745: if (mapper.mapFileName(fromResources[i].getName()) != null) {
0746: v.addElement(fromResources[i]);
0747: }
0748: }
0749: toCopy = new Resource[v.size()];
0750: v.copyInto(toCopy);
0751: } else {
0752: toCopy = ResourceUtils.selectOutOfDateSources(this ,
0753: fromResources, mapper, new ResourceFactory() {
0754: public Resource getResource(String name) {
0755: return new FileResource(toDir, name);
0756: }
0757: }, granularity);
0758: }
0759: for (int i = 0; i < toCopy.length; i++) {
0760: String[] mappedFiles = mapper.mapFileName(toCopy[i]
0761: .getName());
0762:
0763: if (!enableMultipleMappings) {
0764: map.put(toCopy[i], new String[] { new File(toDir,
0765: mappedFiles[0]).getAbsolutePath() });
0766: } else {
0767: // reuse the array created by the mapper
0768: for (int k = 0; k < mappedFiles.length; k++) {
0769: mappedFiles[k] = new File(toDir, mappedFiles[k])
0770: .getAbsolutePath();
0771: }
0772: map.put(toCopy[i], mappedFiles);
0773: }
0774: }
0775: return map;
0776: }
0777:
0778: /**
0779: * Actually does the file (and possibly empty directory) copies.
0780: * This is a good method for subclasses to override.
0781: */
0782: protected void doFileOperations() {
0783: if (fileCopyMap.size() > 0) {
0784: log("Copying " + fileCopyMap.size() + " file"
0785: + (fileCopyMap.size() == 1 ? "" : "s") + " to "
0786: + destDir.getAbsolutePath());
0787:
0788: Enumeration e = fileCopyMap.keys();
0789: while (e.hasMoreElements()) {
0790: String fromFile = (String) e.nextElement();
0791: String[] toFiles = (String[]) fileCopyMap.get(fromFile);
0792:
0793: for (int i = 0; i < toFiles.length; i++) {
0794: String toFile = toFiles[i];
0795:
0796: if (fromFile.equals(toFile)) {
0797: log("Skipping self-copy of " + fromFile,
0798: verbosity);
0799: continue;
0800: }
0801: try {
0802: log("Copying " + fromFile + " to " + toFile,
0803: verbosity);
0804:
0805: FilterSetCollection executionFilters = new FilterSetCollection();
0806: if (filtering) {
0807: executionFilters.addFilterSet(getProject()
0808: .getGlobalFilterSet());
0809: }
0810: for (Enumeration filterEnum = filterSets
0811: .elements(); filterEnum
0812: .hasMoreElements();) {
0813: executionFilters
0814: .addFilterSet((FilterSet) filterEnum
0815: .nextElement());
0816: }
0817: fileUtils.copyFile(fromFile, toFile,
0818: executionFilters, filterChains,
0819: forceOverwrite, preserveLastModified,
0820: inputEncoding, outputEncoding,
0821: getProject());
0822: } catch (IOException ioe) {
0823: String msg = "Failed to copy " + fromFile
0824: + " to " + toFile + " due to "
0825: + getDueTo(ioe);
0826: File targetFile = new File(toFile);
0827: if (targetFile.exists() && !targetFile.delete()) {
0828: msg += " and I couldn't delete the corrupt "
0829: + toFile;
0830: }
0831: if (failonerror) {
0832: throw new BuildException(msg, ioe,
0833: getLocation());
0834: }
0835: log(msg, Project.MSG_ERR);
0836: }
0837: }
0838: }
0839: }
0840: if (includeEmpty) {
0841: Enumeration e = dirCopyMap.elements();
0842: int createCount = 0;
0843: while (e.hasMoreElements()) {
0844: String[] dirs = (String[]) e.nextElement();
0845: for (int i = 0; i < dirs.length; i++) {
0846: File d = new File(dirs[i]);
0847: if (!d.exists()) {
0848: if (!d.mkdirs()) {
0849: log("Unable to create directory "
0850: + d.getAbsolutePath(),
0851: Project.MSG_ERR);
0852: } else {
0853: createCount++;
0854: }
0855: }
0856: }
0857: }
0858: if (createCount > 0) {
0859: log("Copied " + dirCopyMap.size() + " empty director"
0860: + (dirCopyMap.size() == 1 ? "y" : "ies")
0861: + " to " + createCount + " empty director"
0862: + (createCount == 1 ? "y" : "ies") + " under "
0863: + destDir.getAbsolutePath());
0864: }
0865: }
0866: }
0867:
0868: /**
0869: * Actually does the resource copies.
0870: * This is a good method for subclasses to override.
0871: * @param map a map of source resource to array of destination files.
0872: * @since Ant 1.7
0873: */
0874: protected void doResourceOperations(Map map) {
0875: if (map.size() > 0) {
0876: log("Copying " + map.size() + " resource"
0877: + (map.size() == 1 ? "" : "s") + " to "
0878: + destDir.getAbsolutePath());
0879:
0880: Iterator iter = map.keySet().iterator();
0881: while (iter.hasNext()) {
0882: Resource fromResource = (Resource) iter.next();
0883: String[] toFiles = (String[]) map.get(fromResource);
0884:
0885: for (int i = 0; i < toFiles.length; i++) {
0886: String toFile = toFiles[i];
0887:
0888: try {
0889: log(
0890: "Copying " + fromResource + " to "
0891: + toFile, verbosity);
0892:
0893: FilterSetCollection executionFilters = new FilterSetCollection();
0894: if (filtering) {
0895: executionFilters.addFilterSet(getProject()
0896: .getGlobalFilterSet());
0897: }
0898: for (Enumeration filterEnum = filterSets
0899: .elements(); filterEnum
0900: .hasMoreElements();) {
0901: executionFilters
0902: .addFilterSet((FilterSet) filterEnum
0903: .nextElement());
0904: }
0905: ResourceUtils.copyResource(fromResource,
0906: new FileResource(destDir, toFile),
0907: executionFilters, filterChains,
0908: forceOverwrite, preserveLastModified,
0909: inputEncoding, outputEncoding,
0910: getProject());
0911: } catch (IOException ioe) {
0912: String msg = "Failed to copy " + fromResource
0913: + " to " + toFile + " due to "
0914: + getDueTo(ioe);
0915: File targetFile = new File(toFile);
0916: if (targetFile.exists() && !targetFile.delete()) {
0917: msg += " and I couldn't delete the corrupt "
0918: + toFile;
0919: }
0920: if (failonerror) {
0921: throw new BuildException(msg, ioe,
0922: getLocation());
0923: }
0924: log(msg, Project.MSG_ERR);
0925: }
0926: }
0927: }
0928: }
0929: }
0930:
0931: /**
0932: * Whether this task can deal with non-file resources.
0933: *
0934: * <p><copy> can while <move> can't since we don't
0935: * know how to remove non-file resources.</p>
0936: *
0937: * <p>This implementation returns true only if this task is
0938: * <copy>. Any subclass of this class that also wants to
0939: * support non-file resources needs to override this method. We
0940: * need to do so for backwards compatibility reasons since we
0941: * can't expect subclasses to support resources.</p>
0942: * @return true if this task supports non file resources.
0943: * @since Ant 1.7
0944: */
0945: protected boolean supportsNonFileResources() {
0946: return getClass().equals(Copy.class);
0947: }
0948:
0949: /**
0950: * Adds the given strings to a list contained in the given map.
0951: * The file is the key into the map.
0952: */
0953: private static void add(File baseDir, String[] names, Map m) {
0954: if (names != null) {
0955: baseDir = getKeyFile(baseDir);
0956: List l = (List) m.get(baseDir);
0957: if (l == null) {
0958: l = new ArrayList(names.length);
0959: m.put(baseDir, l);
0960: }
0961: l.addAll(java.util.Arrays.asList(names));
0962: }
0963: }
0964:
0965: /**
0966: * Adds the given string to a list contained in the given map.
0967: * The file is the key into the map.
0968: */
0969: private static void add(File baseDir, String name, Map m) {
0970: if (name != null) {
0971: add(baseDir, new String[] { name }, m);
0972: }
0973: }
0974:
0975: /**
0976: * Either returns its argument or a plaeholder if the argument is null.
0977: */
0978: private static File getKeyFile(File f) {
0979: return f == null ? NULL_FILE_PLACEHOLDER : f;
0980: }
0981:
0982: /**
0983: * returns the mapper to use based on nested elements or the
0984: * flatten attribute.
0985: */
0986: private FileNameMapper getMapper() {
0987: FileNameMapper mapper = null;
0988: if (mapperElement != null) {
0989: mapper = mapperElement.getImplementation();
0990: } else if (flatten) {
0991: mapper = new FlatFileNameMapper();
0992: } else {
0993: mapper = new IdentityMapper();
0994: }
0995: return mapper;
0996: }
0997:
0998: /**
0999: * Handle getMessage() for exceptions.
1000: * @param ex the exception to handle
1001: * @return ex.getMessage() if ex.getMessage() is not null
1002: * otherwise return ex.toString()
1003: */
1004: private String getMessage(Exception ex) {
1005: return ex.getMessage() == null ? ex.toString() : ex
1006: .getMessage();
1007: }
1008:
1009: /**
1010: * Returns a reason for failure based on
1011: * the exception thrown.
1012: * If the exception is not IOException output the class name,
1013: * output the message
1014: * if the exception is MalformedInput add a little note.
1015: */
1016: private String getDueTo(Exception ex) {
1017: boolean baseIOException = ex.getClass() == IOException.class;
1018: StringBuffer message = new StringBuffer();
1019: if (!baseIOException || ex.getMessage() == null) {
1020: message.append(ex.getClass().getName());
1021: }
1022: if (ex.getMessage() != null) {
1023: if (!baseIOException) {
1024: message.append(" ");
1025: }
1026: message.append(ex.getMessage());
1027: }
1028: if (ex.getClass().getName().indexOf("MalformedInput") != -1) {
1029: message.append(LINE_SEPARATOR);
1030: message
1031: .append("This is normally due to the input file containing invalid");
1032: message.append(LINE_SEPARATOR);
1033: message.append("bytes for the character encoding used : ");
1034: message.append((inputEncoding == null ? fileUtils
1035: .getDefaultEncoding() : inputEncoding));
1036: message.append(LINE_SEPARATOR);
1037: }
1038: return message.toString();
1039: }
1040: }
|