FileDocCategorySizeDatePackage
Copy.javaAPI DocApache Ant 1.7038282Wed Dec 13 06:16:20 GMT 2006org.apache.tools.ant.taskdefs

Copy

public class Copy extends org.apache.tools.ant.Task
Copies a file or directory to a new file or directory. Files are only copied if the source file is newer than the destination file, or when the destination file does not exist. It is possible to explicitly overwrite existing files.

This implementation is based on Arnout Kuiper's initial design document, the following mailing list discussions, and the copyfile/copydir tasks.

since
Ant 1.2
ant.task
category="filesystem"

Fields Summary
static final File
NULL_FILE_PLACEHOLDER
static final String
LINE_SEPARATOR
protected File
file
protected File
destFile
protected File
destDir
protected Vector
rcs
private boolean
enableMultipleMappings
protected boolean
filtering
protected boolean
preserveLastModified
protected boolean
forceOverwrite
protected boolean
flatten
protected int
verbosity
protected boolean
includeEmpty
protected boolean
failonerror
protected Hashtable
fileCopyMap
protected Hashtable
dirCopyMap
protected Hashtable
completeDirMap
protected org.apache.tools.ant.types.Mapper
mapperElement
protected org.apache.tools.ant.util.FileUtils
fileUtils
private Vector
filterChains
private Vector
filterSets
private String
inputEncoding
private String
outputEncoding
private long
granularity
Constructors Summary
public Copy()
Copy task constructor.

    // CheckStyle:VisibilityModifier ON

            
      
        fileUtils = FileUtils.getFileUtils();
        granularity = fileUtils.getFileTimestampGranularity();
    
Methods Summary
public voidadd(org.apache.tools.ant.types.ResourceCollection res)
Add a collection of files to copy.

param
res a resource collection to copy.
since
Ant 1.7

        rcs.add(res);
    
public voidadd(org.apache.tools.ant.util.FileNameMapper fileNameMapper)
Add a nested filenamemapper.

param
fileNameMapper the mapper to add.
since
Ant 1.6.3

        createMapper().add(fileNameMapper);
    
private static voidadd(java.io.File baseDir, java.lang.String[] names, java.util.Map m)
Adds the given strings to a list contained in the given map. The file is the key into the map.

        if (names != null) {
            baseDir = getKeyFile(baseDir);
            List l = (List) m.get(baseDir);
            if (l == null) {
                l = new ArrayList(names.length);
                m.put(baseDir, l);
            }
            l.addAll(java.util.Arrays.asList(names));
        }
    
private static voidadd(java.io.File baseDir, java.lang.String name, java.util.Map m)
Adds the given string to a list contained in the given map. The file is the key into the map.

        if (name != null) {
            add(baseDir, new String[] {name}, m);
        }
    
public voidaddFileset(org.apache.tools.ant.types.FileSet set)
Add a set of files to copy.

param
set a set of files to copy.

        add(set);
    
protected voidbuildMap(java.io.File fromDir, java.io.File toDir, java.lang.String[] names, org.apache.tools.ant.util.FileNameMapper mapper, java.util.Hashtable map)
Add to a map of files/directories to copy.

param
fromDir the source directory.
param
toDir the destination directory.
param
names a list of filenames.
param
mapper a FileNameMapper value.
param
map a map of source file to array of destination files.

        String[] toCopy = null;
        if (forceOverwrite) {
            Vector v = new Vector();
            for (int i = 0; i < names.length; i++) {
                if (mapper.mapFileName(names[i]) != null) {
                    v.addElement(names[i]);
                }
            }
            toCopy = new String[v.size()];
            v.copyInto(toCopy);
        } else {
            SourceFileScanner ds = new SourceFileScanner(this);
            toCopy = ds.restrict(names, fromDir, toDir, mapper, granularity);
        }
        for (int i = 0; i < toCopy.length; i++) {
            File src = new File(fromDir, toCopy[i]);
            String[] mappedFiles = mapper.mapFileName(toCopy[i]);

            if (!enableMultipleMappings) {
                map.put(src.getAbsolutePath(),
                        new String[] {new File(toDir, mappedFiles[0]).getAbsolutePath()});
            } else {
                // reuse the array created by the mapper
                for (int k = 0; k < mappedFiles.length; k++) {
                    mappedFiles[k] = new File(toDir, mappedFiles[k]).getAbsolutePath();
                }
                map.put(src.getAbsolutePath(), mappedFiles);
            }
        }
    
protected java.util.MapbuildMap(org.apache.tools.ant.types.Resource[] fromResources, java.io.File toDir, org.apache.tools.ant.util.FileNameMapper mapper)
Create a map of resources to copy.

param
fromResources The source resources.
param
toDir the destination directory.
param
mapper a FileNameMapper value.
return
a map of source resource to array of destination files.
since
Ant 1.7

        HashMap map = new HashMap();
        Resource[] toCopy = null;
        if (forceOverwrite) {
            Vector v = new Vector();
            for (int i = 0; i < fromResources.length; i++) {
                if (mapper.mapFileName(fromResources[i].getName()) != null) {
                    v.addElement(fromResources[i]);
                }
            }
            toCopy = new Resource[v.size()];
            v.copyInto(toCopy);
        } else {
            toCopy =
                ResourceUtils.selectOutOfDateSources(this, fromResources,
                                                     mapper,
                                                     new ResourceFactory() {
                           public Resource getResource(String name) {
                               return new FileResource(toDir, name);
                           }
                                                     },
                                                     granularity);
        }
        for (int i = 0; i < toCopy.length; i++) {
            String[] mappedFiles = mapper.mapFileName(toCopy[i].getName());

            if (!enableMultipleMappings) {
                map.put(toCopy[i],
                        new String[] {new File(toDir, mappedFiles[0]).getAbsolutePath()});
            } else {
                // reuse the array created by the mapper
                for (int k = 0; k < mappedFiles.length; k++) {
                    mappedFiles[k] = new File(toDir, mappedFiles[k]).getAbsolutePath();
                }
                map.put(toCopy[i], mappedFiles);
            }
        }
        return map;
    
public org.apache.tools.ant.types.FilterChaincreateFilterChain()
Add a FilterChain.

return
a filter chain object.

        FilterChain filterChain = new FilterChain();
        filterChains.addElement(filterChain);
        return filterChain;
    
public org.apache.tools.ant.types.FilterSetcreateFilterSet()
Add a filterset.

return
a filter set object.

        FilterSet filterSet = new FilterSet();
        filterSets.addElement(filterSet);
        return filterSet;
    
public org.apache.tools.ant.types.MappercreateMapper()
Define the mapper to map source to destination files.

return
a mapper to be configured.
exception
BuildException if more than one mapper is defined.

        if (mapperElement != null) {
            throw new BuildException("Cannot define more than one mapper",
                                     getLocation());
        }
        mapperElement = new Mapper(getProject());
        return mapperElement;
    
protected voiddoFileOperations()
Actually does the file (and possibly empty directory) copies. This is a good method for subclasses to override.

        if (fileCopyMap.size() > 0) {
            log("Copying " + fileCopyMap.size()
                + " file" + (fileCopyMap.size() == 1 ? "" : "s")
                + " to " + destDir.getAbsolutePath());

            Enumeration e = fileCopyMap.keys();
            while (e.hasMoreElements()) {
                String fromFile = (String) e.nextElement();
                String[] toFiles = (String[]) fileCopyMap.get(fromFile);

                for (int i = 0; i < toFiles.length; i++) {
                    String toFile = toFiles[i];

                    if (fromFile.equals(toFile)) {
                        log("Skipping self-copy of " + fromFile, verbosity);
                        continue;
                    }
                    try {
                        log("Copying " + fromFile + " to " + toFile, verbosity);

                        FilterSetCollection executionFilters =
                            new FilterSetCollection();
                        if (filtering) {
                            executionFilters
                                .addFilterSet(getProject().getGlobalFilterSet());
                        }
                        for (Enumeration filterEnum = filterSets.elements();
                            filterEnum.hasMoreElements();) {
                            executionFilters
                                .addFilterSet((FilterSet) filterEnum.nextElement());
                        }
                        fileUtils.copyFile(fromFile, toFile, executionFilters,
                                           filterChains, forceOverwrite,
                                           preserveLastModified, inputEncoding,
                                           outputEncoding, getProject());
                    } catch (IOException ioe) {
                        String msg = "Failed to copy " + fromFile + " to " + toFile
                            + " due to " + getDueTo(ioe);
                        File targetFile = new File(toFile);
                        if (targetFile.exists() && !targetFile.delete()) {
                            msg += " and I couldn't delete the corrupt " + toFile;
                        }
                        if (failonerror) {
                            throw new BuildException(msg, ioe, getLocation());
                        }
                        log(msg, Project.MSG_ERR);
                    }
                }
            }
        }
        if (includeEmpty) {
            Enumeration e = dirCopyMap.elements();
            int createCount = 0;
            while (e.hasMoreElements()) {
                String[] dirs = (String[]) e.nextElement();
                for (int i = 0; i < dirs.length; i++) {
                    File d = new File(dirs[i]);
                    if (!d.exists()) {
                        if (!d.mkdirs()) {
                            log("Unable to create directory "
                                + d.getAbsolutePath(), Project.MSG_ERR);
                        } else {
                            createCount++;
                        }
                    }
                }
            }
            if (createCount > 0) {
                log("Copied " + dirCopyMap.size()
                    + " empty director"
                    + (dirCopyMap.size() == 1 ? "y" : "ies")
                    + " to " + createCount
                    + " empty director"
                    + (createCount == 1 ? "y" : "ies") + " under "
                    + destDir.getAbsolutePath());
            }
        }
    
protected voiddoResourceOperations(java.util.Map map)
Actually does the resource copies. This is a good method for subclasses to override.

param
map a map of source resource to array of destination files.
since
Ant 1.7

        if (map.size() > 0) {
            log("Copying " + map.size()
                + " resource" + (map.size() == 1 ? "" : "s")
                + " to " + destDir.getAbsolutePath());

            Iterator iter = map.keySet().iterator();
            while (iter.hasNext()) {
                Resource fromResource = (Resource) iter.next();
                String[] toFiles = (String[]) map.get(fromResource);

                for (int i = 0; i < toFiles.length; i++) {
                    String toFile = toFiles[i];

                    try {
                        log("Copying " + fromResource + " to " + toFile,
                            verbosity);

                        FilterSetCollection executionFilters =
                            new FilterSetCollection();
                        if (filtering) {
                            executionFilters
                                .addFilterSet(getProject().getGlobalFilterSet());
                        }
                        for (Enumeration filterEnum = filterSets.elements();
                            filterEnum.hasMoreElements();) {
                            executionFilters
                                .addFilterSet((FilterSet) filterEnum.nextElement());
                        }
                        ResourceUtils.copyResource(fromResource,
                                                   new FileResource(destDir,
                                                                    toFile),
                                                   executionFilters,
                                                   filterChains,
                                                   forceOverwrite,
                                                   preserveLastModified,
                                                   inputEncoding,
                                                   outputEncoding,
                                                   getProject());
                    } catch (IOException ioe) {
                        String msg = "Failed to copy " + fromResource
                            + " to " + toFile
                            + " due to " + getDueTo(ioe);
                        File targetFile = new File(toFile);
                        if (targetFile.exists() && !targetFile.delete()) {
                            msg += " and I couldn't delete the corrupt " + toFile;
                        }
                        if (failonerror) {
                            throw new BuildException(msg, ioe, getLocation());
                        }
                        log(msg, Project.MSG_ERR);
                    }
                }
            }
        }
    
public voidexecute()
Perform the copy operation.

exception
BuildException if an error occurs.

        File savedFile = file; // may be altered in validateAttributes
        File savedDestFile = destFile;
        File savedDestDir = destDir;
        ResourceCollection savedRc = null;
        if (file == null && destFile != null && rcs.size() == 1) {
            // will be removed in validateAttributes
            savedRc = (ResourceCollection) rcs.elementAt(0);
        }
        // make sure we don't have an illegal set of options
        validateAttributes();

        try {
            // deal with the single file
            if (file != null) {
                if (file.exists()) {
                    if (destFile == null) {
                        destFile = new File(destDir, file.getName());
                    }
                    if (forceOverwrite || !destFile.exists()
                        || (file.lastModified() - granularity
                                > destFile.lastModified())) {
                        fileCopyMap.put(file.getAbsolutePath(),
                                        new String[] {destFile.getAbsolutePath()});
                    } else {
                        log(file + " omitted as " + destFile
                            + " is up to date.", Project.MSG_VERBOSE);
                    }
                } else {
                    String message = "Warning: Could not find file "
                        + file.getAbsolutePath() + " to copy.";
                    if (!failonerror) {
                        log(message, Project.MSG_ERR);
                    } else {
                        throw new BuildException(message);
                    }
                }
            }
            // deal with the ResourceCollections

            /* for historical and performance reasons we have to do
               things in a rather complex way.

               (1) Move is optimized to move directories if a fileset
               has been included completely, therefore FileSets need a
               special treatment.  This is also required to support
               the failOnError semantice (skip filesets with broken
               basedir but handle the remaining collections).

               (2) We carry around a few protected methods that work
               on basedirs and arrays of names.  To optimize stuff, all
               resources with the same basedir get collected in
               separate lists and then each list is handled in one go.
            */

            HashMap filesByBasedir = new HashMap();
            HashMap dirsByBasedir = new HashMap();
            HashSet baseDirs = new HashSet();
            ArrayList nonFileResources = new ArrayList();
            for (int i = 0; i < rcs.size(); i++) {
                ResourceCollection rc = (ResourceCollection) rcs.elementAt(i);

                // Step (1) - beware of the ZipFileSet
                if (rc instanceof FileSet && rc.isFilesystemOnly()) {
                    FileSet fs = (FileSet) rc;
                    DirectoryScanner ds = null;
                    try {
                        ds = fs.getDirectoryScanner(getProject());
                    } catch (BuildException e) {
                        if (failonerror
                            || !getMessage(e).endsWith(" not found.")) {
                            throw e;
                        } else {
                            log("Warning: " + getMessage(e), Project.MSG_ERR);
                            continue;
                        }
                    }
                    File fromDir = fs.getDir(getProject());

                    String[] srcFiles = ds.getIncludedFiles();
                    String[] srcDirs = ds.getIncludedDirectories();
                    if (!flatten && mapperElement == null
                        && ds.isEverythingIncluded() && !fs.hasPatterns()) {
                        completeDirMap.put(fromDir, destDir);
                    }
                    add(fromDir, srcFiles, filesByBasedir);
                    add(fromDir, srcDirs, dirsByBasedir);
                    baseDirs.add(fromDir);
                } else { // not a fileset or contains non-file resources

                    if (!rc.isFilesystemOnly() && !supportsNonFileResources()) {
                        throw new BuildException(
                                   "Only FileSystem resources are supported.");
                    }

                    Iterator resources = rc.iterator();
                    while (resources.hasNext()) {
                        Resource r = (Resource) resources.next();
                        if (!r.isExists()) {
                            continue;
                        }

                        File baseDir = NULL_FILE_PLACEHOLDER;
                        String name = r.getName();
                        if (r instanceof FileResource) {
                            FileResource fr = (FileResource) r;
                            baseDir = getKeyFile(fr.getBaseDir());
                            if (fr.getBaseDir() == null) {
                                name = fr.getFile().getAbsolutePath();
                            }
                        }

                        // copying of dirs is trivial and can be done
                        // for non-file resources as well as for real
                        // files.
                        if (r.isDirectory() || r instanceof FileResource) {
                            add(baseDir, name,
                                r.isDirectory() ? dirsByBasedir
                                                : filesByBasedir);
                            baseDirs.add(baseDir);
                        } else { // a not-directory file resource
                            // needs special treatment
                            nonFileResources.add(r);
                        }
                    }
                }
            }

            Iterator iter = baseDirs.iterator();
            while (iter.hasNext()) {
                File f = (File) iter.next();
                List files = (List) filesByBasedir.get(f);
                List dirs = (List) dirsByBasedir.get(f);

                String[] srcFiles = new String[0];
                if (files != null) {
                    srcFiles = (String[]) files.toArray(srcFiles);
                }
                String[] srcDirs = new String[0];
                if (dirs != null) {
                    srcDirs = (String[]) dirs.toArray(srcDirs);
                }
                scan(f == NULL_FILE_PLACEHOLDER ? null : f, destDir, srcFiles,
                     srcDirs);
            }

            // do all the copy operations now...
            try {
                doFileOperations();
            } catch (BuildException e) {
                if (!failonerror) {
                    log("Warning: " + getMessage(e), Project.MSG_ERR);
                } else {
                    throw e;
                }
            }

            if (nonFileResources.size() > 0) {
                Resource[] nonFiles =
                    (Resource[]) nonFileResources.toArray(new Resource[nonFileResources.size()]);
                // restrict to out-of-date resources
                Map map = scan(nonFiles, destDir);
                try {
                    doResourceOperations(map);
                } catch (BuildException e) {
                    if (!failonerror) {
                        log("Warning: " + getMessage(e), Project.MSG_ERR);
                    } else {
                        throw e;
                    }
                }
            }
        } finally {
            // clean up again, so this instance can be used a second
            // time
            file = savedFile;
            destFile = savedDestFile;
            destDir = savedDestDir;
            if (savedRc != null) {
                rcs.insertElementAt(savedRc, 0);
            }
            fileCopyMap.clear();
            dirCopyMap.clear();
            completeDirMap.clear();
        }
    
private java.lang.StringgetDueTo(java.lang.Exception ex)
Returns a reason for failure based on the exception thrown. If the exception is not IOException output the class name, output the message if the exception is MalformedInput add a little note.

        boolean baseIOException = ex.getClass() == IOException.class;
        StringBuffer message = new StringBuffer();
        if (!baseIOException || ex.getMessage() == null) {
            message.append(ex.getClass().getName());
        }
        if (ex.getMessage() != null) {
            if (!baseIOException) {
                message.append(" ");
            }
            message.append(ex.getMessage());
        }
        if (ex.getClass().getName().indexOf("MalformedInput") != -1) {
            message.append(LINE_SEPARATOR);
            message.append(
                "This is normally due to the input file containing invalid");
             message.append(LINE_SEPARATOR);
            message.append("bytes for the character encoding used : ");
            message.append(
                (inputEncoding == null
                 ? fileUtils.getDefaultEncoding() : inputEncoding));
            message.append(LINE_SEPARATOR);
        }
        return message.toString();
    
public java.lang.StringgetEncoding()
Get the character encoding to be used.

return
the character encoding, null if not set.
since
1.32, Ant 1.5

        return inputEncoding;
    
protected org.apache.tools.ant.util.FileUtilsgetFileUtils()
Get the FileUtils for this task.

return
the fileutils object.

        return fileUtils;
    
protected java.util.VectorgetFilterChains()
Get the filterchains being applied to this operation.

return
a vector of FilterChain objects.

        return filterChains;
    
protected java.util.VectorgetFilterSets()
Get the filtersets being applied to this operation.

return
a vector of FilterSet objects.

        return filterSets;
    
private static java.io.FilegetKeyFile(java.io.File f)
Either returns its argument or a plaeholder if the argument is null.

        return f == null ? NULL_FILE_PLACEHOLDER : f;
    
private org.apache.tools.ant.util.FileNameMappergetMapper()
returns the mapper to use based on nested elements or the flatten attribute.

        FileNameMapper mapper = null;
        if (mapperElement != null) {
            mapper = mapperElement.getImplementation();
        } else if (flatten) {
            mapper = new FlatFileNameMapper();
        } else {
            mapper = new IdentityMapper();
        }
        return mapper;
    
private java.lang.StringgetMessage(java.lang.Exception ex)
Handle getMessage() for exceptions.

param
ex the exception to handle
return
ex.getMessage() if ex.getMessage() is not null otherwise return ex.toString()

        return ex.getMessage() == null ? ex.toString() : ex.getMessage();
    
public java.lang.StringgetOutputEncoding()
Get the character encoding for output files.

return
the character encoding for output files, null if not set.
since
Ant 1.6

        return outputEncoding;
    
public booleangetPreserveLastModified()
Get whether to give the copied files the same last modified time as the original files.

return
the whether destination files will inherit the modification times of the corresponding source files.
since
1.32, Ant 1.5

        return preserveLastModified;
    
public booleanisEnableMultipleMapping()
Get whether multiple mapping is enabled.

return
true if multiple mapping is enabled; false otherwise.

        return enableMultipleMappings;
    
protected voidscan(java.io.File fromDir, java.io.File toDir, java.lang.String[] files, java.lang.String[] dirs)
Compares source files to destination files to see if they should be copied.

param
fromDir The source directory.
param
toDir The destination directory.
param
files A list of files to copy.
param
dirs A list of directories to copy.

        FileNameMapper mapper = getMapper();
        buildMap(fromDir, toDir, files, mapper, fileCopyMap);

        if (includeEmpty) {
            buildMap(fromDir, toDir, dirs, mapper, dirCopyMap);
        }
    
protected java.util.Mapscan(org.apache.tools.ant.types.Resource[] fromResources, java.io.File toDir)
Compares source resources to destination files to see if they should be copied.

param
fromResources The source resources.
param
toDir The destination directory.
return
a Map with the out-of-date resources as keys and an array of target file names as values.
since
Ant 1.7

        return buildMap(fromResources, toDir, getMapper());
    
public voidsetEnableMultipleMappings(boolean enableMultipleMappings)
Set method of handling mappers that return multiple mappings for a given source path.

param
enableMultipleMappings If true the task will copy to all the mappings for a given source path, if false, only the first file or directory is processed. By default, this setting is false to provide backward compatibility with earlier releases.
since
Ant 1.6

        this.enableMultipleMappings = enableMultipleMappings;
    
public voidsetEncoding(java.lang.String encoding)
Set the character encoding.

param
encoding the character encoding.
since
1.32, Ant 1.5

        this.inputEncoding = encoding;
        if (outputEncoding == null) {
            outputEncoding = encoding;
        }
    
public voidsetFailOnError(boolean failonerror)
Set whether to fail when errors are encountered. If false, note errors to the output but keep going. Default is true.

param
failonerror true or false.

        this.failonerror = failonerror;
    
public voidsetFile(java.io.File file)
Set a single source file to copy.

param
file the file to copy.

        this.file = file;
    
public voidsetFiltering(boolean filtering)
Set filtering mode.

param
filtering if true enable filtering; default is false.

        this.filtering = filtering;
    
public voidsetFlatten(boolean flatten)
Set whether files copied from directory trees will be "flattened" into a single directory. If there are multiple files with the same name in the source directory tree, only the first file will be copied into the "flattened" directory, unless the forceoverwrite attribute is true.

param
flatten if true flatten the destination directory. Default is false.

        this.flatten = flatten;
    
public voidsetGranularity(long granularity)
Set the number of milliseconds leeway to give before deciding a target is out of date.

Default is 1 second, or 2 seconds on DOS systems.

param
granularity the granularity used to decide if a target is out of date.
since
Ant 1.6.2

        this.granularity = granularity;
    
public voidsetIncludeEmptyDirs(boolean includeEmpty)
Set whether to copy empty directories.

param
includeEmpty if true copy empty directories. Default is true.

        this.includeEmpty = includeEmpty;
    
public voidsetOutputEncoding(java.lang.String encoding)
Set the character encoding for output files.

param
encoding the output character encoding.
since
Ant 1.6

        this.outputEncoding = encoding;
    
public voidsetOverwrite(boolean overwrite)
Set overwrite mode regarding existing destination file(s).

param
overwrite if true force overwriting of destination file(s) even if the destination file(s) are younger than the corresponding source file. Default is false.

        this.forceOverwrite = overwrite;
    
public voidsetPreserveLastModified(java.lang.String preserve)
Give the copied files the same last modified time as the original files.

param
preserve a boolean string.
deprecated
since 1.5.x. setPreserveLastModified(String) has been deprecated and replaced with setPreserveLastModified(boolean) to consistently let the Introspection mechanism work.

        setPreserveLastModified(Project.toBoolean(preserve));
    
public voidsetPreserveLastModified(boolean preserve)
Give the copied files the same last modified time as the original files.

param
preserve if true preserve the modified time; default is false.

        preserveLastModified = preserve;
    
public voidsetTodir(java.io.File destDir)
Set the destination directory.

param
destDir the destination directory.

        this.destDir = destDir;
    
public voidsetTofile(java.io.File destFile)
Set the destination file.

param
destFile the file to copy to.

        this.destFile = destFile;
    
public voidsetVerbose(boolean verbose)
Set verbose mode. Used to force listing of all names of copied files.

param
verbose whether to output the names of copied files. Default is false.

        this.verbosity = verbose ? Project.MSG_INFO : Project.MSG_VERBOSE;
    
protected booleansupportsNonFileResources()
Whether this task can deal with non-file resources.

<copy> can while <move> can't since we don't know how to remove non-file resources.

This implementation returns true only if this task is <copy>. Any subclass of this class that also wants to support non-file resources needs to override this method. We need to do so for backwards compatibility reasons since we can't expect subclasses to support resources.

return
true if this task supports non file resources.
since
Ant 1.7

        return getClass().equals(Copy.class);
    
protected voidvalidateAttributes()
Ensure we have a consistent and legal set of attributes, and set any internal flags necessary based on different combinations of attributes.

exception
BuildException if an error occurs.

        if (file == null && rcs.size() == 0) {
            throw new BuildException(
                "Specify at least one source--a file or a resource collection.");
        }
        if (destFile != null && destDir != null) {
            throw new BuildException(
                "Only one of tofile and todir may be set.");
        }
        if (destFile == null && destDir == null) {
            throw new BuildException("One of tofile or todir must be set.");
        }
        if (file != null && file.isDirectory()) {
            throw new BuildException("Use a resource collection to copy directories.");
        }
        if (destFile != null && rcs.size() > 0) {
            if (rcs.size() > 1) {
                throw new BuildException(
                    "Cannot concatenate multiple files into a single file.");
            } else {
                ResourceCollection rc = (ResourceCollection) rcs.elementAt(0);
                if (!rc.isFilesystemOnly()) {
                    throw new BuildException("Only FileSystem resources are"
                                             + " supported when concatenating"
                                             + " files.");
                }
                if (rc.size() == 0) {
                    throw new BuildException(
                        "Cannot perform operation from directory to file.");
                } else if (rc.size() == 1) {
                    FileResource r = (FileResource) rc.iterator().next();
                    if (file == null) {
                        file = r.getFile();
                        rcs.removeElementAt(0);
                    } else {
                        throw new BuildException(
                            "Cannot concatenate multiple files into a single file.");
                    }
                } else {
                    throw new BuildException(
                        "Cannot concatenate multiple files into a single file.");
                }
            }
        }
        if (destFile != null) {
            destDir = destFile.getParentFile();
        }