001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.taskdefs;
020:
021: import java.io.File;
022: import java.io.IOException;
023: import java.util.Hashtable;
024: import java.util.Iterator;
025: import java.util.Vector;
026: import org.apache.tools.ant.BuildException;
027: import org.apache.tools.ant.DirectoryScanner;
028: import org.apache.tools.ant.Project;
029: import org.apache.tools.ant.types.Commandline;
030: import org.apache.tools.ant.types.AbstractFileSet;
031: import org.apache.tools.ant.types.DirSet;
032: import org.apache.tools.ant.types.EnumeratedAttribute;
033: import org.apache.tools.ant.types.FileList;
034: import org.apache.tools.ant.types.FileSet;
035: import org.apache.tools.ant.types.Mapper;
036: import org.apache.tools.ant.types.Resource;
037: import org.apache.tools.ant.types.ResourceCollection;
038: import org.apache.tools.ant.types.resources.FileResource;
039: import org.apache.tools.ant.types.resources.Union;
040: import org.apache.tools.ant.util.FileNameMapper;
041: import org.apache.tools.ant.util.SourceFileScanner;
042:
043: /**
044: * Executes a given command, supplying a set of files as arguments.
045: *
046: * @since Ant 1.2
047: *
048: * @ant.task category="control" name="apply"
049: */
050: public class ExecuteOn extends ExecTask {
051:
052: // CheckStyle:VisibilityModifier OFF - bc
053:
054: // filesets has been protected so we need to keep that even after
055: // switching to resource collections. In fact, they will still
056: // get a different treatment form the other resource collections
057: // even in execute since we have some subtle special features like
058: // switching type to "dir" when we encounter a DirSet that would
059: // be more difficult to achieve otherwise.
060:
061: protected Vector filesets = new Vector(); // contains AbstractFileSet
062: // (both DirSet and FileSet)
063: private Union resources = null;
064: private boolean relative = false;
065: private boolean parallel = false;
066: private boolean forwardSlash = false;
067: protected String type = FileDirBoth.FILE;
068: protected Commandline.Marker srcFilePos = null;
069: private boolean skipEmpty = false;
070: protected Commandline.Marker targetFilePos = null;
071: protected Mapper mapperElement = null;
072: protected FileNameMapper mapper = null;
073: protected File destDir = null;
074: private int maxParallel = -1;
075: private boolean addSourceFile = true;
076: private boolean verbose = false;
077: private boolean ignoreMissing = true;
078: private boolean force = false;
079:
080: /**
081: * Has <srcfile> been specified before <targetfile>
082: */
083: protected boolean srcIsFirst = true;
084:
085: // CheckStyle:VisibilityModifier ON
086: /**
087: * Add a set of files upon which to operate.
088: * @param set the FileSet to add.
089: */
090: public void addFileset(FileSet set) {
091: filesets.addElement(set);
092: }
093:
094: /**
095: * Add a set of directories upon which to operate.
096: *
097: * @param set the DirSet to add.
098: *
099: * @since Ant 1.6
100: */
101: public void addDirset(DirSet set) {
102: filesets.addElement(set);
103: }
104:
105: /**
106: * Add a list of source files upon which to operate.
107: * @param list the FileList to add.
108: */
109: public void addFilelist(FileList list) {
110: add(list);
111: }
112:
113: /**
114: * Add a collection of resources upon which to operate.
115: * @param rc resource collection to add.
116: * @since Ant 1.7
117: */
118: public void add(ResourceCollection rc) {
119: if (resources == null) {
120: resources = new Union();
121: }
122: resources.add(rc);
123: }
124:
125: /**
126: * Set whether the filenames should be passed on the command line as
127: * absolute or relative pathnames. Paths are relative to the base
128: * directory of the corresponding fileset for source files or the
129: * dest attribute for target files.
130: * @param relative whether to pass relative pathnames.
131: */
132: public void setRelative(boolean relative) {
133: this .relative = relative;
134: }
135:
136: /**
137: * Set whether to execute in parallel mode.
138: * If true, run the command only once, appending all files as arguments.
139: * If false, command will be executed once for every file. Defaults to false.
140: * @param parallel whether to run in parallel.
141: */
142: public void setParallel(boolean parallel) {
143: this .parallel = parallel;
144: }
145:
146: /**
147: * Set whether the command works only on files, directories or both.
148: * @param type a FileDirBoth EnumeratedAttribute.
149: */
150: public void setType(FileDirBoth type) {
151: this .type = type.getValue();
152: }
153:
154: /**
155: * Set whether empty filesets will be skipped. If true and
156: * no source files have been found or are newer than their
157: * corresponding target files, the command will not be run.
158: * @param skip whether to skip empty filesets.
159: */
160: public void setSkipEmptyFilesets(boolean skip) {
161: skipEmpty = skip;
162: }
163:
164: /**
165: * Specify the directory where target files are to be placed.
166: * @param destDir the File object representing the destination directory.
167: */
168: public void setDest(File destDir) {
169: this .destDir = destDir;
170: }
171:
172: /**
173: * Set whether the source and target file names on Windows and OS/2
174: * must use the forward slash as file separator.
175: * @param forwardSlash whether the forward slash will be forced.
176: */
177: public void setForwardslash(boolean forwardSlash) {
178: this .forwardSlash = forwardSlash;
179: }
180:
181: /**
182: * Limit the command line length by passing at maximum this many
183: * sourcefiles at once to the command.
184: *
185: * <p>Set to <= 0 for unlimited - this is the default.</p>
186: *
187: * @param max <code>int</code> maximum number of sourcefiles
188: * passed to the executable.
189: *
190: * @since Ant 1.6
191: */
192: public void setMaxParallel(int max) {
193: maxParallel = max;
194: }
195:
196: /**
197: * Set whether to send the source file name on the command line.
198: *
199: * <p>Defaults to <code>true</code>.
200: *
201: * @param b whether to add the source file to the command line.
202: *
203: * @since Ant 1.6
204: */
205: public void setAddsourcefile(boolean b) {
206: addSourceFile = b;
207: }
208:
209: /**
210: * Set whether to operate in verbose mode.
211: * If true, a verbose summary will be printed after execution.
212: * @param b whether to operate in verbose mode.
213: *
214: * @since Ant 1.6
215: */
216: public void setVerbose(boolean b) {
217: verbose = b;
218: }
219:
220: /**
221: * Set whether to ignore nonexistent files from filelists.
222: * @param b whether to ignore missing files.
223: *
224: * @since Ant 1.6.2
225: */
226: public void setIgnoremissing(boolean b) {
227: ignoreMissing = b;
228: }
229:
230: /**
231: * Set whether to bypass timestamp comparisons for target files.
232: * @param b whether to bypass timestamp comparisons.
233: *
234: * @since Ant 1.6.3
235: */
236: public void setForce(boolean b) {
237: force = b;
238: }
239:
240: /**
241: * Create a placeholder indicating where on the command line
242: * the name of the source file should be inserted.
243: * @return <code>Commandline.Marker</code>.
244: */
245: public Commandline.Marker createSrcfile() {
246: if (srcFilePos != null) {
247: throw new BuildException(getTaskType()
248: + " doesn\'t support multiple "
249: + "srcfile elements.", getLocation());
250: }
251: srcFilePos = cmdl.createMarker();
252: return srcFilePos;
253: }
254:
255: /**
256: * Create a placeholder indicating where on the command line
257: * the name of the target file should be inserted.
258: * @return <code>Commandline.Marker</code>.
259: */
260: public Commandline.Marker createTargetfile() {
261: if (targetFilePos != null) {
262: throw new BuildException(getTaskType()
263: + " doesn\'t support multiple "
264: + "targetfile elements.", getLocation());
265: }
266: targetFilePos = cmdl.createMarker();
267: srcIsFirst = (srcFilePos != null);
268: return targetFilePos;
269: }
270:
271: /**
272: * Create a nested Mapper element to use for mapping
273: * source files to target files.
274: * @return <code>Mapper</code>.
275: * @throws BuildException if more than one mapper is defined.
276: */
277: public Mapper createMapper() throws BuildException {
278: if (mapperElement != null) {
279: throw new BuildException(
280: "Cannot define more than one mapper", getLocation());
281: }
282: mapperElement = new Mapper(getProject());
283: return mapperElement;
284: }
285:
286: /**
287: * Add a nested FileNameMapper.
288: * @param fileNameMapper the mapper to add.
289: * @since Ant 1.6.3
290: */
291: public void add(FileNameMapper fileNameMapper) {
292: createMapper().add(fileNameMapper);
293: }
294:
295: /**
296: * Check the configuration of this ExecuteOn instance.
297: */
298: protected void checkConfiguration() {
299: // * @TODO using taskName here is brittle, as a user could override it.
300: // * this should probably be modified to use the classname instead.
301: if ("execon".equals(getTaskName())) {
302: log("!! execon is deprecated. Use apply instead. !!");
303: }
304: super .checkConfiguration();
305: if (filesets.size() == 0 && resources == null) {
306: throw new BuildException("no resources specified",
307: getLocation());
308: }
309: if (targetFilePos != null && mapperElement == null) {
310: throw new BuildException(
311: "targetfile specified without mapper",
312: getLocation());
313: }
314: if (destDir != null && mapperElement == null) {
315: throw new BuildException("dest specified without mapper",
316: getLocation());
317: }
318: if (mapperElement != null) {
319: mapper = mapperElement.getImplementation();
320: }
321: }
322:
323: /**
324: * Create the ExecuteStreamHandler instance that will be used
325: * during execution.
326: * @return <code>ExecuteStreamHandler</code>.
327: * @throws BuildException on error.
328: */
329: protected ExecuteStreamHandler createHandler()
330: throws BuildException {
331: //if we have a RedirectorElement, return a decoy
332: return (redirectorElement == null) ? super .createHandler()
333: : new PumpStreamHandler();
334: }
335:
336: /**
337: * Set up the I/O Redirector.
338: */
339: protected void setupRedirector() {
340: super .setupRedirector();
341: redirector.setAppendProperties(true);
342: }
343:
344: /**
345: * Run the specified Execute object.
346: * @param exe the Execute instance representing the external process.
347: * @throws BuildException on error
348: */
349: protected void runExec(Execute exe) throws BuildException {
350: int totalFiles = 0;
351: int totalDirs = 0;
352: boolean haveExecuted = false;
353: try {
354: Vector fileNames = new Vector();
355: Vector baseDirs = new Vector();
356: for (int i = 0; i < filesets.size(); i++) {
357: String currentType = type;
358: AbstractFileSet fs = (AbstractFileSet) filesets
359: .elementAt(i);
360: if (fs instanceof DirSet) {
361: if (!FileDirBoth.DIR.equals(type)) {
362: log(
363: "Found a nested dirset but type is "
364: + type
365: + ". "
366: + "Temporarily switching to type=\"dir\" on the"
367: + " assumption that you really did mean"
368: + " <dirset> not <fileset>.",
369: Project.MSG_DEBUG);
370: currentType = FileDirBoth.DIR;
371: }
372: }
373: File base = fs.getDir(getProject());
374:
375: DirectoryScanner ds = fs
376: .getDirectoryScanner(getProject());
377:
378: if (!FileDirBoth.DIR.equals(currentType)) {
379: String[] s = getFiles(base, ds);
380: for (int j = 0; j < s.length; j++) {
381: totalFiles++;
382: fileNames.addElement(s[j]);
383: baseDirs.addElement(base);
384: }
385: }
386: if (!FileDirBoth.FILE.equals(currentType)) {
387: String[] s = getDirs(base, ds);
388: for (int j = 0; j < s.length; j++) {
389: totalDirs++;
390: fileNames.addElement(s[j]);
391: baseDirs.addElement(base);
392: }
393: }
394: if (fileNames.size() == 0 && skipEmpty) {
395: int includedCount = ((!FileDirBoth.DIR
396: .equals(currentType)) ? ds
397: .getIncludedFilesCount() : 0)
398: + ((!FileDirBoth.FILE.equals(currentType)) ? ds
399: .getIncludedDirsCount()
400: : 0);
401:
402: log("Skipping fileset for directory "
403: + base
404: + ". It is "
405: + ((includedCount > 0) ? "up to date."
406: : "empty."), Project.MSG_INFO);
407: continue;
408: }
409: if (!parallel) {
410: String[] s = new String[fileNames.size()];
411: fileNames.copyInto(s);
412: for (int j = 0; j < s.length; j++) {
413: String[] command = getCommandline(s[j], base);
414: log(Commandline.describeCommand(command),
415: Project.MSG_VERBOSE);
416: exe.setCommandline(command);
417:
418: if (redirectorElement != null) {
419: setupRedirector();
420: redirectorElement.configure(redirector,
421: s[j]);
422: }
423: if (redirectorElement != null || haveExecuted) {
424: // need to reset the stream handler to restart
425: // reading of pipes;
426: // go ahead and do it always w/ nested redirectors
427: exe.setStreamHandler(redirector
428: .createHandler());
429: }
430: runExecute(exe);
431: haveExecuted = true;
432: }
433: fileNames.removeAllElements();
434: baseDirs.removeAllElements();
435: }
436: }
437:
438: if (resources != null) {
439: Iterator iter = resources.iterator();
440: while (iter.hasNext()) {
441: Resource res = (Resource) iter.next();
442:
443: if (!res.isExists() && ignoreMissing) {
444: continue;
445: }
446:
447: File base = null;
448: String name = res.getName();
449: if (res instanceof FileResource) {
450: FileResource fr = (FileResource) res;
451: base = fr.getBaseDir();
452: if (base == null) {
453: name = fr.getFile().getAbsolutePath();
454: }
455: }
456:
457: if (restrict(new String[] { name }, base).length == 0) {
458: continue;
459: }
460:
461: if ((!res.isDirectory() || !res.isExists())
462: && !FileDirBoth.DIR.equals(type)) {
463: totalFiles++;
464: } else if (res.isDirectory()
465: && !FileDirBoth.FILE.equals(type)) {
466: totalDirs++;
467: } else {
468: continue;
469: }
470:
471: baseDirs.add(base);
472: fileNames.add(name);
473:
474: if (!parallel) {
475: String[] command = getCommandline(name, base);
476: log(Commandline.describeCommand(command),
477: Project.MSG_VERBOSE);
478: exe.setCommandline(command);
479:
480: if (redirectorElement != null) {
481: setupRedirector();
482: redirectorElement.configure(redirector,
483: name);
484: }
485: if (redirectorElement != null || haveExecuted) {
486: // need to reset the stream handler to restart
487: // reading of pipes;
488: // go ahead and do it always w/ nested redirectors
489: exe.setStreamHandler(redirector
490: .createHandler());
491: }
492: runExecute(exe);
493: haveExecuted = true;
494: fileNames.removeAllElements();
495: baseDirs.removeAllElements();
496: }
497: }
498: }
499: if (parallel && (fileNames.size() > 0 || !skipEmpty)) {
500: runParallel(exe, fileNames, baseDirs);
501: haveExecuted = true;
502: }
503: if (haveExecuted) {
504: log("Applied " + cmdl.getExecutable() + " to "
505: + totalFiles + " file"
506: + (totalFiles != 1 ? "s" : "") + " and "
507: + totalDirs + " director"
508: + (totalDirs != 1 ? "ies" : "y") + ".",
509: verbose ? Project.MSG_INFO
510: : Project.MSG_VERBOSE);
511: }
512: } catch (IOException e) {
513: throw new BuildException("Execute failed: " + e, e,
514: getLocation());
515: } finally {
516: // close the output file if required
517: logFlush();
518: redirector.setAppendProperties(false);
519: redirector.setProperties();
520: }
521: }
522:
523: /**
524: * Construct the command line for parallel execution.
525: *
526: * @param srcFiles The filenames to add to the commandline.
527: * @param baseDirs filenames are relative to this dir.
528: * @return the command line in the form of a String[].
529: */
530: protected String[] getCommandline(String[] srcFiles, File[] baseDirs) {
531: final char fileSeparator = File.separatorChar;
532: Vector targets = new Vector();
533: if (targetFilePos != null) {
534: Hashtable addedFiles = new Hashtable();
535: for (int i = 0; i < srcFiles.length; i++) {
536: String[] subTargets = mapper.mapFileName(srcFiles[i]);
537: if (subTargets != null) {
538: for (int j = 0; j < subTargets.length; j++) {
539: String name = null;
540: if (!relative) {
541: name = (new File(destDir, subTargets[j]))
542: .getAbsolutePath();
543: } else {
544: name = subTargets[j];
545: }
546: if (forwardSlash && fileSeparator != '/') {
547: name = name.replace(fileSeparator, '/');
548: }
549: if (!addedFiles.contains(name)) {
550: targets.addElement(name);
551: addedFiles.put(name, name);
552: }
553: }
554: }
555: }
556: }
557: String[] targetFiles = new String[targets.size()];
558: targets.copyInto(targetFiles);
559:
560: if (!addSourceFile) {
561: srcFiles = new String[0];
562: }
563: String[] orig = cmdl.getCommandline();
564: String[] result = new String[orig.length + srcFiles.length
565: + targetFiles.length];
566:
567: int srcIndex = orig.length;
568: if (srcFilePos != null) {
569: srcIndex = srcFilePos.getPosition();
570: }
571: if (targetFilePos != null) {
572: int targetIndex = targetFilePos.getPosition();
573:
574: if (srcIndex < targetIndex
575: || (srcIndex == targetIndex && srcIsFirst)) {
576:
577: // 0 --> srcIndex
578: System.arraycopy(orig, 0, result, 0, srcIndex);
579:
580: // srcIndex --> targetIndex
581: System.arraycopy(orig, srcIndex, result, srcIndex
582: + srcFiles.length, targetIndex - srcIndex);
583:
584: // targets are already absolute file names
585: System.arraycopy(targetFiles, 0, result, targetIndex
586: + srcFiles.length, targetFiles.length);
587:
588: // targetIndex --> end
589: System.arraycopy(orig, targetIndex, result, targetIndex
590: + srcFiles.length + targetFiles.length,
591: orig.length - targetIndex);
592: } else {
593: // 0 --> targetIndex
594: System.arraycopy(orig, 0, result, 0, targetIndex);
595:
596: // targets are already absolute file names
597: System.arraycopy(targetFiles, 0, result, targetIndex,
598: targetFiles.length);
599:
600: // targetIndex --> srcIndex
601: System.arraycopy(orig, targetIndex, result, targetIndex
602: + targetFiles.length, srcIndex - targetIndex);
603:
604: // srcIndex --> end
605: System.arraycopy(orig, srcIndex, result, srcIndex
606: + srcFiles.length + targetFiles.length,
607: orig.length - srcIndex);
608: srcIndex += targetFiles.length;
609: }
610:
611: } else { // no targetFilePos
612:
613: // 0 --> srcIndex
614: System.arraycopy(orig, 0, result, 0, srcIndex);
615: // srcIndex --> end
616: System.arraycopy(orig, srcIndex, result, srcIndex
617: + srcFiles.length, orig.length - srcIndex);
618: }
619: // fill in source file names
620: for (int i = 0; i < srcFiles.length; i++) {
621: if (!relative) {
622: result[srcIndex + i] = (new File(baseDirs[i],
623: srcFiles[i])).getAbsolutePath();
624: } else {
625: result[srcIndex + i] = srcFiles[i];
626: }
627: if (forwardSlash && fileSeparator != '/') {
628: result[srcIndex + i] = result[srcIndex + i].replace(
629: fileSeparator, '/');
630: }
631: }
632: return result;
633: }
634:
635: /**
636: * Construct the command line for serial execution.
637: *
638: * @param srcFile The filename to add to the commandline.
639: * @param baseDir filename is relative to this dir.
640: * @return the command line in the form of a String[].
641: */
642: protected String[] getCommandline(String srcFile, File baseDir) {
643: return getCommandline(new String[] { srcFile },
644: new File[] { baseDir });
645: }
646:
647: /**
648: * Return the list of files from this DirectoryScanner that should
649: * be included on the command line.
650: * @param baseDir the File base directory.
651: * @param ds the DirectoryScanner to use for file scanning.
652: * @return a String[] containing the filenames.
653: */
654: protected String[] getFiles(File baseDir, DirectoryScanner ds) {
655: return restrict(ds.getIncludedFiles(), baseDir);
656: }
657:
658: /**
659: * Return the list of Directories from this DirectoryScanner that
660: * should be included on the command line.
661: * @param baseDir the File base directory.
662: * @param ds the DirectoryScanner to use for file scanning.
663: * @return a String[] containing the directory names.
664: */
665: protected String[] getDirs(File baseDir, DirectoryScanner ds) {
666: return restrict(ds.getIncludedDirectories(), baseDir);
667: }
668:
669: /**
670: * Return the list of files or directories from this FileList that
671: * should be included on the command line.
672: * @param list the FileList to check.
673: * @return a String[] containing the directory names.
674: *
675: * @since Ant 1.6.2
676: */
677: protected String[] getFilesAndDirs(FileList list) {
678: return restrict(list.getFiles(getProject()), list
679: .getDir(getProject()));
680: }
681:
682: private String[] restrict(String[] s, File baseDir) {
683: return (mapper == null || force) ? s : new SourceFileScanner(
684: this ).restrict(s, baseDir, destDir, mapper);
685: }
686:
687: /**
688: * Run the command in "parallel" mode, making sure that at most
689: * maxParallel sourcefiles get passed on the command line.
690: * @param exe the Executable to use.
691: * @param fileNames the Vector of filenames.
692: * @param baseDirs the Vector of base directories corresponding to fileNames.
693: * @throws IOException on I/O errors.
694: * @throws BuildException on other errors.
695: * @since Ant 1.6
696: */
697: protected void runParallel(Execute exe, Vector fileNames,
698: Vector baseDirs) throws IOException, BuildException {
699: String[] s = new String[fileNames.size()];
700: fileNames.copyInto(s);
701: File[] b = new File[baseDirs.size()];
702: baseDirs.copyInto(b);
703:
704: if (maxParallel <= 0 || s.length == 0 /* this is skipEmpty == false */) {
705: String[] command = getCommandline(s, b);
706: log(Commandline.describeCommand(command),
707: Project.MSG_VERBOSE);
708: exe.setCommandline(command);
709: runExecute(exe);
710: } else {
711: int stillToDo = fileNames.size();
712: int currentOffset = 0;
713: while (stillToDo > 0) {
714: int currentAmount = Math.min(stillToDo, maxParallel);
715: String[] cs = new String[currentAmount];
716: System
717: .arraycopy(s, currentOffset, cs, 0,
718: currentAmount);
719: File[] cb = new File[currentAmount];
720: System
721: .arraycopy(b, currentOffset, cb, 0,
722: currentAmount);
723: String[] command = getCommandline(cs, cb);
724: log(Commandline.describeCommand(command),
725: Project.MSG_VERBOSE);
726: exe.setCommandline(command);
727: if (redirectorElement != null) {
728: setupRedirector();
729: redirectorElement.configure(redirector, null);
730: }
731: if (redirectorElement != null || currentOffset > 0) {
732: // need to reset the stream handler to restart
733: // reading of pipes;
734: // go ahead and do it always w/ nested redirectors
735: exe.setStreamHandler(redirector.createHandler());
736: }
737: runExecute(exe);
738:
739: stillToDo -= currentAmount;
740: currentOffset += currentAmount;
741: }
742: }
743: }
744:
745: /**
746: * Enumerated attribute with the values "file", "dir" and "both"
747: * for the type attribute.
748: */
749: public static class FileDirBoth extends EnumeratedAttribute {
750: /** File value */
751: public static final String FILE = "file";
752: /** Dir value */
753: public static final String DIR = "dir";
754:
755: /**
756: * @see EnumeratedAttribute#getValues
757: */
758: /** {@inheritDoc}. */
759: public String[] getValues() {
760: return new String[] { FILE, DIR, "both" };
761: }
762: }
763: }
|