/* * Copyright (c) 2003-2005 Ant-Contrib project. All rights reserved. * * Licensed 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 net.sf.antcontrib.logic; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.MacroDef; import org.apache.tools.ant.taskdefs.MacroInstance; import org.apache.tools.ant.taskdefs.Parallel; import org.apache.tools.ant.types.DirSet; import org.apache.tools.ant.types.FileSet; import org.apache.tools.ant.types.Path; /*** * Task definition for the for task. This is based on * the foreach task but takes a sequential element * instead of a target and only works for ant >= 1.6Beta3 * @author Peter Reilly */ public class ForTask extends Task { private String list; private String param; private String delimiter = ","; private Path currPath; private boolean trim; private boolean keepgoing = false; private MacroDef macroDef; private List hasIterators = new ArrayList(); private boolean parallel = false; private Integer threadCount; private Parallel parallelTasks; /** * Creates a new For instance. * This checks if the ant version is correct to run this task. */ public ForTask() { } /** * Attribute whether to execute the loop in parallel or in sequence. * @param parallel if true execute the tasks in parallel. Default is false. */ public void setParallel(boolean parallel) { this.parallel = parallel; } /*** * Set the maximum amount of threads we're going to allow * to execute in parallel * @param threadCount the number of threads to use */ public void setThreadCount(int threadCount) { if (threadCount < 1) { throw new BuildException("Illegal value for threadCount " + threadCount + " it should be > 0"); } this.threadCount = new Integer(threadCount); } /** * Set the trim attribute. * * @param trim if true, trim the value for each iterator. */ public void setTrim(boolean trim) { this.trim = trim; } /** * Set the keepgoing attribute, indicating whether we * should stop on errors or continue heedlessly onward. * * @param keepgoing a boolean, if true then we act in * the keepgoing manner described. */ public void setKeepgoing(boolean keepgoing) { this.keepgoing = keepgoing; } /** * Set the list attribute. * * @param list a list of delimiter separated tokens. */ public void setList(String list) { this.list = list; } /** * Set the delimiter attribute. * * @param delimiter the delimiter used to separate the tokens in * the list attribute. The default is ",". */ public void setDelimiter(String delimiter) { this.delimiter = delimiter; } /** * Set the param attribute. * This is the name of the macrodef attribute that * gets set for each iterator of the sequential element. * * @param param the name of the macrodef attribute. */ public void setParam(String param) { this.param = param; } private Path getOrCreatePath() { if (currPath == null) { currPath = new Path(getProject()); } return currPath; } /** * This is a path that can be used instread of the list * attribute to interate over. If this is set, each * path element in the path is used for an interator of the * sequential element. * * @param path the path to be set by the ant script. */ public void addConfigured(Path path) { getOrCreatePath().append(path); } /** * This is a path that can be used instread of the list * attribute to interate over. If this is set, each * path element in the path is used for an interator of the * sequential element. * * @param path the path to be set by the ant script. */ public void addConfiguredPath(Path path) { addConfigured(path); } /** * @return a MacroDef#NestedSequential object to be configured */ public Object createSequential() { macroDef = new MacroDef(); macroDef.setProject(getProject()); return macroDef.createSequential(); } /** * Run the for task. * This checks the attributes and nested elements, and * if there are ok, it calls doTheTasks() * which constructes a macrodef task and a * for each interation a macrodef instance. */ public void execute() { if (parallel) { parallelTasks = (Parallel) getProject().createTask("parallel"); if (threadCount != null) { parallelTasks.setThreadCount(threadCount.intValue()); } } if (list == null && currPath == null && hasIterators.size() == 0) { throw new BuildException( "You must have a list or path to iterate through"); } if (param == null) { throw new BuildException( "You must supply a property name to set on" + " each iteration in param"); } if (macroDef == null) { throw new BuildException( "You must supply an embedded sequential " + "to perform"); } doTheTasks(); if (parallel) { parallelTasks.perform(); } } private void doSequentialIteration(String val) { MacroInstance instance = new MacroInstance(); instance.setProject(getProject()); instance.setOwningTarget(getOwningTarget()); instance.setMacroDef(macroDef); instance.setDynamicAttribute(param.toLowerCase(), val); if (!parallel) { instance.execute(); } else { parallelTasks.addTask(instance); } } private void doTheTasks() { int errorCount = 0; int taskCount = 0; // Create a macro attribute if (macroDef.getAttributes().isEmpty()) { MacroDef.Attribute attribute = new MacroDef.Attribute(); attribute.setName(param); macroDef.addConfiguredAttribute(attribute); } // Take Care of the list attribute if (list != null) { StringTokenizer st = new StringTokenizer(list, delimiter); while (st.hasMoreTokens()) { String tok = st.nextToken(); if (trim) { tok = tok.trim(); } try { taskCount++; doSequentialIteration(tok); } catch (BuildException bx) { if (keepgoing) { log(tok + ": " + bx.getMessage(), Project.MSG_ERR); errorCount++; } else { throw bx; } } } } if (keepgoing && (errorCount != 0)) { throw new BuildException( "Keepgoing execution: " + errorCount + " of " + taskCount + " iterations failed."); } // Take Care of the path element String[] pathElements = new String[0]; if (currPath != null) { pathElements = currPath.list(); } for (int i = 0; i < pathElements.length; i++) { File nextFile = new File(pathElements[i]); try { taskCount++; doSequentialIteration(nextFile.getAbsolutePath()); } catch (BuildException bx) { if (keepgoing) { log(nextFile + ": " + bx.getMessage(), Project.MSG_ERR); errorCount++; } else { throw bx; } } } if (keepgoing && (errorCount != 0)) { throw new BuildException( "Keepgoing execution: " + errorCount + " of " + taskCount + " iterations failed."); } // Take care of iterators for (Iterator i = hasIterators.iterator(); i.hasNext();) { Iterator it = ((HasIterator) i.next()).iterator(); while (it.hasNext()) { String s = it.next().toString(); try { taskCount++; doSequentialIteration(s); } catch (BuildException bx) { if (keepgoing) { log(s + ": " + bx.getMessage(), Project.MSG_ERR); errorCount++; } else { throw bx; } } } } if (keepgoing && (errorCount != 0)) { throw new BuildException( "Keepgoing execution: " + errorCount + " of " + taskCount + " iterations failed."); } } /** * Add a Map, iterate over the values * * @param map a Map object - iterate over the values. */ public void add(Map map) { hasIterators.add(new MapIterator(map)); } /** * Add a fileset to be iterated over. * * @param fileset a FileSet value */ public void add(FileSet fileset) { getOrCreatePath().addFileset(fileset); } /** * Add a fileset to be iterated over. * * @param fileset a FileSet value */ public void addFileSet(FileSet fileset) { add(fileset); } /** * Add a dirset to be iterated over. * * @param dirset a DirSet value */ public void add(DirSet dirset) { getOrCreatePath().addDirset(dirset); } /** * Add a dirset to be iterated over. * * @param dirset a DirSet value */ public void addDirSet(DirSet dirset) { add(dirset); } /** * Add a collection that can be iterated over. * * @param collection a Collection value. */ public void add(Collection collection) { hasIterators.add(new ReflectIterator(collection)); } /** * Add an iterator to be iterated over. * * @param iterator an Iterator value */ public void add(Iterator iterator) { hasIterators.add(new IteratorIterator(iterator)); } /** * Add an object that has an Iterator iterator() method * that can be iterated over. * * @param obj An object that can be iterated over. */ public void add(Object obj) { hasIterators.add(new ReflectIterator(obj)); } /** * Interface for the objects in the iterator collection. */ private interface HasIterator { Iterator iterator(); } private static class IteratorIterator implements HasIterator { private Iterator iterator; public IteratorIterator(Iterator iterator) { this.iterator = iterator; } public Iterator iterator() { return this.iterator; } } private static class MapIterator implements HasIterator { private Map map; public MapIterator(Map map) { this.map = map; } public Iterator iterator() { return map.values().iterator(); } } private static class ReflectIterator implements HasIterator { private Object obj; private Method method; public ReflectIterator(Object obj) { this.obj = obj; try { method = obj.getClass().getMethod( "iterator", new Class[] {}); } catch (Throwable t) { throw new BuildException( "Invalid type " + obj.getClass() + " used in For task, it does" + " not have a public iterator method"); } } public Iterator iterator() { try { return (Iterator) method.invoke(obj, new Object[] {}); } catch (Throwable t) { throw new BuildException(t); } } } }