| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * https://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| */ |
| |
| package org.apache.tools.ant.taskdefs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Hashtable; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.Vector; |
| |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.DirectoryScanner; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.tools.ant.types.FileSet; |
| import org.apache.tools.ant.types.FilterChain; |
| import org.apache.tools.ant.types.FilterSet; |
| import org.apache.tools.ant.types.FilterSetCollection; |
| import org.apache.tools.ant.types.Mapper; |
| import org.apache.tools.ant.types.Resource; |
| import org.apache.tools.ant.types.ResourceCollection; |
| import org.apache.tools.ant.types.resources.FileProvider; |
| import org.apache.tools.ant.types.resources.FileResource; |
| import org.apache.tools.ant.util.FileNameMapper; |
| import org.apache.tools.ant.util.FileUtils; |
| import org.apache.tools.ant.util.FlatFileNameMapper; |
| import org.apache.tools.ant.util.IdentityMapper; |
| import org.apache.tools.ant.util.LinkedHashtable; |
| import org.apache.tools.ant.util.ResourceUtils; |
| import org.apache.tools.ant.util.SourceFileScanner; |
| import org.apache.tools.ant.util.StringUtils; |
| |
| /** |
| * <p>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.</p> |
| * |
| * <p>This implementation is based on Arnout Kuiper's initial design |
| * document, the following mailing list discussions, and the |
| * copyfile/copydir tasks.</p> |
| * |
| * |
| * @since Ant 1.2 |
| * |
| * @ant.task category="filesystem" |
| */ |
| public class Copy extends Task { |
| private static final String MSG_WHEN_COPYING_EMPTY_RC_TO_FILE = |
| "Cannot perform operation from directory to file."; |
| |
| @Deprecated |
| static final String LINE_SEPARATOR = StringUtils.LINE_SEP; |
| static final File NULL_FILE_PLACEHOLDER = new File("/NULL_FILE"); |
| // CheckStyle:VisibilityModifier OFF - bc |
| protected File file = null; // the source file |
| protected File destFile = null; // the destination file |
| protected File destDir = null; // the destination directory |
| protected Vector<ResourceCollection> rcs = new Vector<>(); |
| // here to provide API backwards compatibility |
| protected Vector<ResourceCollection> filesets = rcs; |
| |
| private boolean enableMultipleMappings = false; |
| protected boolean filtering = false; |
| protected boolean preserveLastModified = false; |
| protected boolean forceOverwrite = false; |
| protected boolean flatten = false; |
| protected int verbosity = Project.MSG_VERBOSE; |
| protected boolean includeEmpty = true; |
| protected boolean failonerror = true; |
| |
| protected Hashtable<String, String[]> fileCopyMap = new LinkedHashtable<>(); |
| protected Hashtable<String, String[]> dirCopyMap = new LinkedHashtable<>(); |
| protected Hashtable<File, File> completeDirMap = new LinkedHashtable<>(); |
| |
| protected Mapper mapperElement = null; |
| protected FileUtils fileUtils; |
| //CheckStyle:VisibilityModifier ON |
| private final Vector<FilterChain> filterChains = new Vector<>(); |
| private final Vector<FilterSet> filterSets = new Vector<>(); |
| private String inputEncoding = null; |
| private String outputEncoding = null; |
| private long granularity = 0; |
| private boolean force = false; |
| private boolean quiet = false; |
| |
| // used to store the single non-file resource to copy when the |
| // tofile attribute has been used |
| private Resource singleResource = null; |
| |
| /** |
| * Copy task constructor. |
| */ |
| public Copy() { |
| fileUtils = FileUtils.getFileUtils(); |
| granularity = fileUtils.getFileTimestampGranularity(); |
| } |
| |
| /** |
| * Get the FileUtils for this task. |
| * @return the fileutils object. |
| */ |
| protected FileUtils getFileUtils() { |
| return fileUtils; |
| } |
| |
| /** |
| * Set a single source file to copy. |
| * @param file the file to copy. |
| */ |
| public void setFile(final File file) { |
| this.file = file; |
| } |
| |
| /** |
| * Set the destination file. |
| * @param destFile the file to copy to. |
| */ |
| public void setTofile(final File destFile) { |
| this.destFile = destFile; |
| } |
| |
| /** |
| * Set the destination directory. |
| * @param destDir the destination directory. |
| */ |
| public void setTodir(final File destDir) { |
| this.destDir = destDir; |
| } |
| |
| /** |
| * Add a FilterChain. |
| * @return a filter chain object. |
| */ |
| public FilterChain createFilterChain() { |
| final FilterChain filterChain = new FilterChain(); |
| filterChains.addElement(filterChain); |
| return filterChain; |
| } |
| |
| /** |
| * Add a filterset. |
| * @return a filter set object. |
| */ |
| public FilterSet createFilterSet() { |
| final FilterSet filterSet = new FilterSet(); |
| filterSets.addElement(filterSet); |
| return filterSet; |
| } |
| |
| /** |
| * 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. |
| */ |
| @Deprecated |
| public void setPreserveLastModified(final String preserve) { |
| setPreserveLastModified(Project.toBoolean(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. |
| */ |
| public void setPreserveLastModified(final boolean preserve) { |
| preserveLastModified = preserve; |
| } |
| |
| /** |
| * 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 |
| */ |
| public boolean getPreserveLastModified() { |
| return preserveLastModified; |
| } |
| |
| /** |
| * Get the filtersets being applied to this operation. |
| * |
| * @return a vector of FilterSet objects. |
| */ |
| protected Vector<FilterSet> getFilterSets() { |
| return filterSets; |
| } |
| |
| /** |
| * Get the filterchains being applied to this operation. |
| * |
| * @return a vector of FilterChain objects. |
| */ |
| protected Vector<FilterChain> getFilterChains() { |
| return filterChains; |
| } |
| |
| /** |
| * Set filtering mode. |
| * @param filtering if true enable filtering; default is false. |
| */ |
| public void setFiltering(final boolean filtering) { |
| this.filtering = filtering; |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setOverwrite(final boolean overwrite) { |
| this.forceOverwrite = overwrite; |
| } |
| |
| /** |
| * Whether read-only destinations will be overwritten. |
| * |
| * <p>Defaults to false</p> |
| * |
| * @param f boolean |
| * @since Ant 1.8.2 |
| */ |
| public void setForce(final boolean f) { |
| force = f; |
| } |
| |
| /** |
| * Whether read-only destinations will be overwritten. |
| * |
| * @return boolean |
| * @since Ant 1.8.2 |
| */ |
| public boolean getForce() { |
| return force; |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setFlatten(final boolean flatten) { |
| this.flatten = flatten; |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setVerbose(final boolean verbose) { |
| this.verbosity = verbose ? Project.MSG_INFO : Project.MSG_VERBOSE; |
| } |
| |
| /** |
| * Set whether to copy empty directories. |
| * @param includeEmpty if true copy empty directories. Default is true. |
| */ |
| public void setIncludeEmptyDirs(final boolean includeEmpty) { |
| this.includeEmpty = includeEmpty; |
| } |
| |
| /** |
| * Set quiet mode. Used to hide messages when a file or directory to be |
| * copied does not exist. |
| * |
| * @param quiet |
| * whether or not to display error messages when a file or |
| * directory does not exist. Default is false. |
| */ |
| public void setQuiet(final boolean quiet) { |
| this.quiet = quiet; |
| } |
| |
| /** |
| * 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 |
| */ |
| public void setEnableMultipleMappings(final boolean enableMultipleMappings) { |
| this.enableMultipleMappings = enableMultipleMappings; |
| } |
| |
| /** |
| * Get whether multiple mapping is enabled. |
| * @return true if multiple mapping is enabled; false otherwise. |
| */ |
| public boolean isEnableMultipleMapping() { |
| return enableMultipleMappings; |
| } |
| |
| /** |
| * 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. |
| */ |
| public void setFailOnError(final boolean failonerror) { |
| this.failonerror = failonerror; |
| } |
| |
| /** |
| * Add a set of files to copy. |
| * @param set a set of files to copy. |
| */ |
| public void addFileset(final FileSet set) { |
| add(set); |
| } |
| |
| /** |
| * Add a collection of files to copy. |
| * @param res a resource collection to copy. |
| * @since Ant 1.7 |
| */ |
| public void add(final ResourceCollection res) { |
| rcs.add(res); |
| } |
| |
| /** |
| * Define the mapper to map source to destination files. |
| * @return a mapper to be configured. |
| * @exception BuildException if more than one mapper is defined. |
| */ |
| public Mapper createMapper() throws BuildException { |
| if (mapperElement != null) { |
| throw new BuildException("Cannot define more than one mapper", |
| getLocation()); |
| } |
| mapperElement = new Mapper(getProject()); |
| return mapperElement; |
| } |
| |
| /** |
| * Add a nested filenamemapper. |
| * @param fileNameMapper the mapper to add. |
| * @since Ant 1.6.3 |
| */ |
| public void add(final FileNameMapper fileNameMapper) { |
| createMapper().add(fileNameMapper); |
| } |
| |
| /** |
| * Set the character encoding. |
| * @param encoding the character encoding. |
| * @since 1.32, Ant 1.5 |
| */ |
| public void setEncoding(final String encoding) { |
| this.inputEncoding = encoding; |
| if (outputEncoding == null) { |
| outputEncoding = encoding; |
| } |
| } |
| |
| /** |
| * Get the character encoding to be used. |
| * @return the character encoding, <code>null</code> if not set. |
| * |
| * @since 1.32, Ant 1.5 |
| */ |
| public String getEncoding() { |
| return inputEncoding; |
| } |
| |
| /** |
| * Set the character encoding for output files. |
| * @param encoding the output character encoding. |
| * @since Ant 1.6 |
| */ |
| public void setOutputEncoding(final String encoding) { |
| this.outputEncoding = encoding; |
| } |
| |
| /** |
| * Get the character encoding for output files. |
| * @return the character encoding for output files, |
| * <code>null</code> if not set. |
| * |
| * @since Ant 1.6 |
| */ |
| public String getOutputEncoding() { |
| return outputEncoding; |
| } |
| |
| /** |
| * Set the number of milliseconds leeway to give before deciding a |
| * target is out of date. |
| * |
| * <p>Default is 1 second, or 2 seconds on DOS systems.</p> |
| * @param granularity the granularity used to decide if a target is out of |
| * date. |
| * @since Ant 1.6.2 |
| */ |
| public void setGranularity(final long granularity) { |
| this.granularity = granularity; |
| } |
| |
| /** |
| * Perform the copy operation. |
| * @exception BuildException if an error occurs. |
| */ |
| @Override |
| public void execute() throws BuildException { |
| final File savedFile = file; // may be altered in validateAttributes |
| final File savedDestFile = destFile; |
| final File savedDestDir = destDir; |
| ResourceCollection savedRc = null; |
| if (file == null && destFile != null && rcs.size() == 1) { |
| // will be removed in validateAttributes |
| savedRc = rcs.elementAt(0); |
| } |
| |
| try { |
| // make sure we don't have an illegal set of options |
| try { |
| validateAttributes(); |
| } catch (final BuildException e) { |
| if (failonerror |
| || !getMessage(e) |
| .equals(MSG_WHEN_COPYING_EMPTY_RC_TO_FILE)) { |
| throw e; |
| } else { |
| log("Warning: " + getMessage(e), Project.MSG_ERR); |
| return; |
| } |
| } |
| |
| // deal with the single file |
| copySingleFile(); |
| |
| // 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 semantic (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. |
| */ |
| |
| final Map<File, List<String>> filesByBasedir = new HashMap<>(); |
| final Map<File, List<String>> dirsByBasedir = new HashMap<>(); |
| final Set<File> baseDirs = new HashSet<>(); |
| final List<Resource> nonFileResources = new ArrayList<>(); |
| |
| for (ResourceCollection rc : rcs) { |
| |
| // Step (1) - beware of the ZipFileSet |
| if (rc instanceof FileSet && rc.isFilesystemOnly()) { |
| final FileSet fs = (FileSet) rc; |
| DirectoryScanner ds; |
| try { |
| ds = fs.getDirectoryScanner(getProject()); |
| } catch (final BuildException e) { |
| if (failonerror |
| || !getMessage(e).endsWith(DirectoryScanner |
| .DOES_NOT_EXIST_POSTFIX)) { |
| throw e; |
| } |
| if (!quiet) { |
| log("Warning: " + getMessage(e), Project.MSG_ERR); |
| } |
| continue; |
| } |
| final File fromDir = fs.getDir(getProject()); |
| |
| if (!flatten && mapperElement == null |
| && ds.isEverythingIncluded() && !fs.hasPatterns()) { |
| completeDirMap.put(fromDir, destDir); |
| } |
| add(fromDir, ds.getIncludedFiles(), filesByBasedir); |
| add(fromDir, ds.getIncludedDirectories(), 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."); |
| } |
| |
| for (final Resource r : rc) { |
| if (!r.isExists()) { |
| final String message = "Warning: Could not find resource " |
| + r.toLongString() + " to copy."; |
| if (!failonerror) { |
| if (!quiet) { |
| log(message, Project.MSG_ERR); |
| } |
| } else { |
| throw new BuildException(message); |
| } |
| continue; |
| } |
| |
| File baseDir = NULL_FILE_PLACEHOLDER; |
| String name = r.getName(); |
| final FileProvider fp = r.as(FileProvider.class); |
| if (fp != null) { |
| final FileResource fr = ResourceUtils.asFileResource(fp); |
| 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() || fp != null) { |
| add(baseDir, name, |
| r.isDirectory() ? dirsByBasedir |
| : filesByBasedir); |
| baseDirs.add(baseDir); |
| } else { // a not-directory file resource |
| // needs special treatment |
| nonFileResources.add(r); |
| } |
| } |
| } |
| } |
| |
| iterateOverBaseDirs(baseDirs, dirsByBasedir, filesByBasedir); |
| |
| // do all the copy operations now... |
| try { |
| doFileOperations(); |
| } catch (final BuildException e) { |
| if (!failonerror) { |
| if (!quiet) { |
| log("Warning: " + getMessage(e), Project.MSG_ERR); |
| } |
| } else { |
| throw e; |
| } |
| } |
| |
| if (!nonFileResources.isEmpty() || singleResource != null) { |
| final Resource[] nonFiles = |
| nonFileResources.toArray(new Resource[nonFileResources.size()]); |
| // restrict to out-of-date resources |
| final Map<Resource, String[]> map = scan(nonFiles, destDir); |
| if (singleResource != null) { |
| map.put(singleResource, |
| new String[] {destFile.getAbsolutePath()}); |
| } |
| try { |
| doResourceOperations(map); |
| } catch (final BuildException e) { |
| if (!failonerror) { |
| if (!quiet) { |
| log("Warning: " + getMessage(e), Project.MSG_ERR); |
| } |
| } else { |
| throw e; |
| } |
| } |
| } |
| } finally { |
| // clean up again, so this instance can be used a second |
| // time |
| singleResource = null; |
| file = savedFile; |
| destFile = savedDestFile; |
| destDir = savedDestDir; |
| if (savedRc != null) { |
| rcs.insertElementAt(savedRc, 0); |
| } |
| fileCopyMap.clear(); |
| dirCopyMap.clear(); |
| completeDirMap.clear(); |
| } |
| } |
| |
| /************************************************************************ |
| ** protected and private methods |
| ************************************************************************/ |
| |
| private void copySingleFile() { |
| // 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 { |
| final String message = "Warning: Could not find file " |
| + file.getAbsolutePath() + " to copy."; |
| if (!failonerror) { |
| if (!quiet) { |
| log(message, Project.MSG_ERR); |
| } |
| } else { |
| throw new BuildException(message); |
| } |
| } |
| } |
| } |
| |
| private void iterateOverBaseDirs(final Set<File> baseDirs, |
| final Map<File, List<String>> dirsByBasedir, |
| final Map<File, List<String>> filesByBasedir) { |
| |
| for (final File f : baseDirs) { |
| final List<String> files = filesByBasedir.get(f); |
| final List<String> dirs = dirsByBasedir.get(f); |
| |
| String[] srcFiles = new String[0]; |
| if (files != null) { |
| srcFiles = files.toArray(srcFiles); |
| } |
| String[] srcDirs = new String[0]; |
| if (dirs != null) { |
| srcDirs = dirs.toArray(srcDirs); |
| } |
| scan(f == NULL_FILE_PLACEHOLDER ? null : f, destDir, srcFiles, |
| srcDirs); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| protected void validateAttributes() throws BuildException { |
| if (file == null && rcs.isEmpty()) { |
| 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.isEmpty()) { |
| if (rcs.size() > 1) { |
| throw new BuildException( |
| "Cannot concatenate multiple files into a single file."); |
| } |
| final ResourceCollection rc = rcs.elementAt(0); |
| if (!rc.isFilesystemOnly() && !supportsNonFileResources()) { |
| throw new BuildException( |
| "Only FileSystem resources are supported."); |
| } |
| if (rc.isEmpty()) { |
| throw new BuildException(MSG_WHEN_COPYING_EMPTY_RC_TO_FILE); |
| } |
| if (rc.size() == 1) { |
| final Resource res = rc.iterator().next(); |
| final FileProvider r = res.as(FileProvider.class); |
| if (file == null) { |
| if (r != null) { |
| file = r.getFile(); |
| } else { |
| singleResource = res; |
| } |
| 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(); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| protected void scan(final File fromDir, final File toDir, final String[] files, |
| final String[] dirs) { |
| final FileNameMapper mapper = getMapper(); |
| buildMap(fromDir, toDir, files, mapper, fileCopyMap); |
| |
| if (includeEmpty) { |
| buildMap(fromDir, toDir, dirs, mapper, dirCopyMap); |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| protected Map<Resource, String[]> scan(final Resource[] fromResources, final File toDir) { |
| return buildMap(fromResources, toDir, getMapper()); |
| } |
| |
| /** |
| * 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 <code>FileNameMapper</code> value. |
| * @param map a map of source file to array of destination files. |
| */ |
| protected void buildMap(final File fromDir, final File toDir, final String[] names, |
| final FileNameMapper mapper, final Hashtable<String, String[]> map) { |
| String[] toCopy = null; |
| if (forceOverwrite) { |
| final List<String> v = new ArrayList<>(); |
| for (String name : names) { |
| if (mapper.mapFileName(name) != null) { |
| v.add(name); |
| } |
| } |
| toCopy = v.toArray(new String[v.size()]); |
| } else { |
| final SourceFileScanner ds = new SourceFileScanner(this); |
| toCopy = ds.restrict(names, fromDir, toDir, mapper, granularity); |
| } |
| for (String name : toCopy) { |
| final File src = new File(fromDir, name); |
| final String[] mappedFiles = mapper.mapFileName(name); |
| if (mappedFiles == null || mappedFiles.length == 0) { |
| continue; |
| } |
| |
| 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); |
| } |
| } |
| } |
| |
| /** |
| * Create a map of resources to copy. |
| * |
| * @param fromResources The source resources. |
| * @param toDir the destination directory. |
| * @param mapper a <code>FileNameMapper</code> value. |
| * @return a map of source resource to array of destination files. |
| * @since Ant 1.7 |
| */ |
| protected Map<Resource, String[]> buildMap(final Resource[] fromResources, final File toDir, |
| final FileNameMapper mapper) { |
| final Map<Resource, String[]> map = new HashMap<>(); |
| Resource[] toCopy; |
| if (forceOverwrite) { |
| final List<Resource> v = new ArrayList<>(); |
| for (Resource rc : fromResources) { |
| if (mapper.mapFileName(rc.getName()) != null) { |
| v.add(rc); |
| } |
| } |
| toCopy = v.toArray(new Resource[v.size()]); |
| } else { |
| toCopy = ResourceUtils.selectOutOfDateSources(this, fromResources, mapper, |
| name -> new FileResource(toDir, name), granularity); |
| } |
| for (Resource rc : toCopy) { |
| final String[] mappedFiles = mapper.mapFileName(rc.getName()); |
| if (mappedFiles == null || mappedFiles.length == 0) { |
| throw new BuildException("Can't copy a resource without a" |
| + " name if the mapper doesn't" |
| + " provide one."); |
| } |
| if (!enableMultipleMappings) { |
| map.put(rc, 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(rc, mappedFiles); |
| } |
| } |
| return map; |
| } |
| |
| /** |
| * Actually does the file (and possibly empty directory) copies. |
| * This is a good method for subclasses to override. |
| */ |
| protected void doFileOperations() { |
| if (!fileCopyMap.isEmpty()) { |
| log("Copying " + fileCopyMap.size() |
| + " file" + (fileCopyMap.size() == 1 ? "" : "s") |
| + " to " + destDir.getAbsolutePath()); |
| |
| for (final Map.Entry<String, String[]> e : fileCopyMap.entrySet()) { |
| final String fromFile = e.getKey(); |
| |
| for (final String toFile : e.getValue()) { |
| if (fromFile.equals(toFile)) { |
| log("Skipping self-copy of " + fromFile, verbosity); |
| continue; |
| } |
| try { |
| log("Copying " + fromFile + " to " + toFile, verbosity); |
| |
| final FilterSetCollection executionFilters = |
| new FilterSetCollection(); |
| if (filtering) { |
| executionFilters |
| .addFilterSet(getProject().getGlobalFilterSet()); |
| } |
| for (final FilterSet filterSet : filterSets) { |
| executionFilters.addFilterSet(filterSet); |
| } |
| fileUtils.copyFile(new File(fromFile), new File(toFile), |
| executionFilters, |
| filterChains, forceOverwrite, |
| preserveLastModified, |
| /* append: */ false, inputEncoding, |
| outputEncoding, getProject(), |
| getForce()); |
| } catch (final IOException ioe) { |
| String msg = "Failed to copy " + fromFile + " to " + toFile |
| + " due to " + getDueTo(ioe); |
| final File targetFile = new File(toFile); |
| if (!(ioe instanceof |
| ResourceUtils.ReadOnlyTargetFileException) |
| && 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) { |
| int createCount = 0; |
| for (final String[] dirs : dirCopyMap.values()) { |
| for (String dir : dirs) { |
| final File d = new File(dir); |
| if (!d.exists()) { |
| if (!d.mkdirs() && !d.isDirectory()) { |
| 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()); |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| protected void doResourceOperations(final Map<Resource, String[]> map) { |
| if (!map.isEmpty()) { |
| log("Copying " + map.size() |
| + " resource" + (map.size() == 1 ? "" : "s") |
| + " to " + destDir.getAbsolutePath()); |
| |
| for (final Map.Entry<Resource, String[]> e : map.entrySet()) { |
| final Resource fromResource = e.getKey(); |
| for (final String toFile : e.getValue()) { |
| try { |
| log("Copying " + fromResource + " to " + toFile, |
| verbosity); |
| |
| final FilterSetCollection executionFilters = new FilterSetCollection(); |
| if (filtering) { |
| executionFilters |
| .addFilterSet(getProject().getGlobalFilterSet()); |
| } |
| for (final FilterSet filterSet : filterSets) { |
| executionFilters.addFilterSet(filterSet); |
| } |
| ResourceUtils.copyResource(fromResource, |
| new FileResource(destDir, |
| toFile), |
| executionFilters, |
| filterChains, |
| forceOverwrite, |
| preserveLastModified, |
| /* append: */ false, |
| inputEncoding, |
| outputEncoding, |
| getProject(), |
| getForce()); |
| } catch (final IOException ioe) { |
| String msg = "Failed to copy " + fromResource |
| + " to " + toFile |
| + " due to " + getDueTo(ioe); |
| final File targetFile = new File(toFile); |
| if (!(ioe instanceof |
| ResourceUtils.ReadOnlyTargetFileException) |
| && 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); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Whether this task can deal with non-file resources. |
| * |
| * <p><copy> can while <move> can't since we don't |
| * know how to remove non-file resources.</p> |
| * |
| * <p>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.</p> |
| * @return true if this task supports non file resources. |
| * @since Ant 1.7 |
| */ |
| protected boolean supportsNonFileResources() { |
| return getClass().equals(Copy.class); |
| } |
| |
| /** |
| * Adds the given strings to a list contained in the given map. |
| * The file is the key into the map. |
| */ |
| private static void add(File baseDir, final String[] names, final Map<File, List<String>> m) { |
| if (names != null) { |
| baseDir = getKeyFile(baseDir); |
| List<String> l = m.computeIfAbsent(baseDir, k -> new ArrayList<>(names.length)); |
| l.addAll(Arrays.asList(names)); |
| } |
| } |
| |
| /** |
| * Adds the given string to a list contained in the given map. |
| * The file is the key into the map. |
| */ |
| private static void add(final File baseDir, final String name, final Map<File, List<String>> m) { |
| if (name != null) { |
| add(baseDir, new String[] {name}, m); |
| } |
| } |
| |
| /** |
| * Either returns its argument or a placeholder if the argument is null. |
| */ |
| private static File getKeyFile(final File f) { |
| return f == null ? NULL_FILE_PLACEHOLDER : f; |
| } |
| |
| /** |
| * returns the mapper to use based on nested elements or the |
| * flatten attribute. |
| */ |
| private FileNameMapper getMapper() { |
| FileNameMapper mapper = null; |
| if (mapperElement != null) { |
| mapper = mapperElement.getImplementation(); |
| } else if (flatten) { |
| mapper = new FlatFileNameMapper(); |
| } else { |
| mapper = new IdentityMapper(); |
| } |
| return mapper; |
| } |
| |
| /** |
| * Handle getMessage() for exceptions. |
| * @param ex the exception to handle |
| * @return ex.getMessage() if ex.getMessage() is not null |
| * otherwise return ex.toString() |
| */ |
| private String getMessage(final Exception ex) { |
| return ex.getMessage() == null ? ex.toString() : ex.getMessage(); |
| } |
| |
| /** |
| * 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. |
| */ |
| private String getDueTo(final Exception ex) { |
| final boolean baseIOException = ex.getClass() == IOException.class; |
| final StringBuilder message = new StringBuilder(); |
| 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().contains("MalformedInput")) { |
| message.append(String.format( |
| "%nThis is normally due to the input file containing invalid" |
| + "%nbytes for the character encoding used : %s%n", |
| inputEncoding == null ? fileUtils.getDefaultEncoding() : inputEncoding)); |
| } |
| return message.toString(); |
| } |
| } |