/* * 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 * * http://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.Hashtable; import java.util.Iterator; 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.types.Commandline; import org.apache.tools.ant.types.AbstractFileSet; import org.apache.tools.ant.types.DirSet; import org.apache.tools.ant.types.EnumeratedAttribute; import org.apache.tools.ant.types.FileList; import org.apache.tools.ant.types.FileSet; 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.FileResource; import org.apache.tools.ant.types.resources.Union; import org.apache.tools.ant.util.FileNameMapper; import org.apache.tools.ant.util.SourceFileScanner; /** * Executes a given command, supplying a set of files as arguments. * * @since Ant 1.2 * * @ant.task category="control" name="apply" */ public class ExecuteOn extends ExecTask { // CheckStyle:VisibilityModifier OFF - bc // filesets has been protected so we need to keep that even after // switching to resource collections. In fact, they will still // get a different treatment form the other resource collections // even in execute since we have some subtle special features like // switching type to "dir" when we encounter a DirSet that would // be more difficult to achieve otherwise. protected Vector filesets = new Vector(); // contains AbstractFileSet // (both DirSet and FileSet) private Union resources = null; private boolean relative = false; private boolean parallel = false; private boolean forwardSlash = false; protected String type = FileDirBoth.FILE; protected Commandline.Marker srcFilePos = null; private boolean skipEmpty = false; protected Commandline.Marker targetFilePos = null; protected Mapper mapperElement = null; protected FileNameMapper mapper = null; protected File destDir = null; private int maxParallel = -1; private boolean addSourceFile = true; private boolean verbose = false; private boolean ignoreMissing = true; private boolean force = false; /** * Has <srcfile> been specified before <targetfile> */ protected boolean srcIsFirst = true; // CheckStyle:VisibilityModifier ON /** * Add a set of files upon which to operate. * @param set the FileSet to add. */ public void addFileset(FileSet set) { filesets.addElement(set); } /** * Add a set of directories upon which to operate. * * @param set the DirSet to add. * * @since Ant 1.6 */ public void addDirset(DirSet set) { filesets.addElement(set); } /** * Add a list of source files upon which to operate. * @param list the FileList to add. */ public void addFilelist(FileList list) { add(list); } /** * Add a collection of resources upon which to operate. * @param rc resource collection to add. * @since Ant 1.7 */ public void add(ResourceCollection rc) { if (resources == null) { resources = new Union(); } resources.add(rc); } /** * Set whether the filenames should be passed on the command line as * absolute or relative pathnames. Paths are relative to the base * directory of the corresponding fileset for source files or the * dest attribute for target files. * @param relative whether to pass relative pathnames. */ public void setRelative(boolean relative) { this.relative = relative; } /** * Set whether to execute in parallel mode. * If true, run the command only once, appending all files as arguments. * If false, command will be executed once for every file. Defaults to false. * @param parallel whether to run in parallel. */ public void setParallel(boolean parallel) { this.parallel = parallel; } /** * Set whether the command works only on files, directories or both. * @param type a FileDirBoth EnumeratedAttribute. */ public void setType(FileDirBoth type) { this.type = type.getValue(); } /** * Set whether empty filesets will be skipped. If true and * no source files have been found or are newer than their * corresponding target files, the command will not be run. * @param skip whether to skip empty filesets. */ public void setSkipEmptyFilesets(boolean skip) { skipEmpty = skip; } /** * Specify the directory where target files are to be placed. * @param destDir the File object representing the destination directory. */ public void setDest(File destDir) { this.destDir = destDir; } /** * Set whether the source and target file names on Windows and OS/2 * must use the forward slash as file separator. * @param forwardSlash whether the forward slash will be forced. */ public void setForwardslash(boolean forwardSlash) { this.forwardSlash = forwardSlash; } /** * Limit the command line length by passing at maximum this many * sourcefiles at once to the command. * *
Set to <= 0 for unlimited - this is the default.
* * @param maxint maximum number of sourcefiles
* passed to the executable.
*
* @since Ant 1.6
*/
public void setMaxParallel(int max) {
maxParallel = max;
}
/**
* Set whether to send the source file name on the command line.
*
* Defaults to true.
*
* @param b whether to add the source file to the command line.
*
* @since Ant 1.6
*/
public void setAddsourcefile(boolean b) {
addSourceFile = b;
}
/**
* Set whether to operate in verbose mode.
* If true, a verbose summary will be printed after execution.
* @param b whether to operate in verbose mode.
*
* @since Ant 1.6
*/
public void setVerbose(boolean b) {
verbose = b;
}
/**
* Set whether to ignore nonexistent files from filelists.
* @param b whether to ignore missing files.
*
* @since Ant 1.6.2
*/
public void setIgnoremissing(boolean b) {
ignoreMissing = b;
}
/**
* Set whether to bypass timestamp comparisons for target files.
* @param b whether to bypass timestamp comparisons.
*
* @since Ant 1.6.3
*/
public void setForce(boolean b) {
force = b;
}
/**
* Create a placeholder indicating where on the command line
* the name of the source file should be inserted.
* @return Commandline.Marker.
*/
public Commandline.Marker createSrcfile() {
if (srcFilePos != null) {
throw new BuildException(getTaskType() + " doesn\'t support multiple "
+ "srcfile elements.", getLocation());
}
srcFilePos = cmdl.createMarker();
return srcFilePos;
}
/**
* Create a placeholder indicating where on the command line
* the name of the target file should be inserted.
* @return Commandline.Marker.
*/
public Commandline.Marker createTargetfile() {
if (targetFilePos != null) {
throw new BuildException(getTaskType() + " doesn\'t support multiple "
+ "targetfile elements.", getLocation());
}
targetFilePos = cmdl.createMarker();
srcIsFirst = (srcFilePos != null);
return targetFilePos;
}
/**
* Create a nested Mapper element to use for mapping
* source files to target files.
* @return Mapper.
* @throws 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(FileNameMapper fileNameMapper) {
createMapper().add(fileNameMapper);
}
/**
* Check the configuration of this ExecuteOn instance.
*/
protected void checkConfiguration() {
// * @TODO using taskName here is brittle, as a user could override it.
// * this should probably be modified to use the classname instead.
if ("execon".equals(getTaskName())) {
log("!! execon is deprecated. Use apply instead. !!");
}
super.checkConfiguration();
if (filesets.size() == 0 && resources == null) {
throw new BuildException("no resources specified",
getLocation());
}
if (targetFilePos != null && mapperElement == null) {
throw new BuildException("targetfile specified without mapper",
getLocation());
}
if (destDir != null && mapperElement == null) {
throw new BuildException("dest specified without mapper",
getLocation());
}
if (mapperElement != null) {
mapper = mapperElement.getImplementation();
}
}
/**
* Create the ExecuteStreamHandler instance that will be used
* during execution.
* @return ExecuteStreamHandler.
* @throws BuildException on error.
*/
protected ExecuteStreamHandler createHandler() throws BuildException {
//if we have a RedirectorElement, return a decoy
return (redirectorElement == null)
? super.createHandler() : new PumpStreamHandler();
}
/**
* Set up the I/O Redirector.
*/
protected void setupRedirector() {
super.setupRedirector();
redirector.setAppendProperties(true);
}
/**
* Run the specified Execute object.
* @param exe the Execute instance representing the external process.
* @throws BuildException on error
*/
protected void runExec(Execute exe) throws BuildException {
int totalFiles = 0;
int totalDirs = 0;
boolean haveExecuted = false;
try {
Vector fileNames = new Vector();
Vector baseDirs = new Vector();
for (int i = 0; i < filesets.size(); i++) {
String currentType = type;
AbstractFileSet fs = (AbstractFileSet) filesets.elementAt(i);
if (fs instanceof DirSet) {
if (!FileDirBoth.DIR.equals(type)) {
log("Found a nested dirset but type is " + type + ". "
+ "Temporarily switching to type=\"dir\" on the"
+ " assumption that you really did mean"
+ "