blob: d1f574953126d3ff7de9835f0b1c7601d09d1a15 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 1999, 2000 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.ant.engine;
import java.util.*;
import org.apache.ant.AntException;
import org.apache.ant.tasks.Task;
/**
* The engine that actually invokes each Task. In addition to specifying a Task
* to execute, it may be desirable to specify the root Task that will define
* an execution cycle.
*/
public class TaskEngineImpl implements TaskEngine {
/**
* Analagous to a call stack, but with Tasks.
*/
protected Stack taskStack = new Stack();;
/**
* As the task stack is built, a mirror representation will also be
* contructed that will hold property values.
*/
protected Stack propertyStack = new Stack();
/**
* Keeps track of AntEngineListeners. We don't have to use Vector because we
* take care of synchronization on the add, remove, and iteration operations.
*/
protected ArrayList listenerList = new ArrayList();
private int engineLevel = 0;
/**
* Constructor is private so it cannot be instantiated. Users of this class
* will get an instance by using the getTaskEngine() method. This will allow
* us to have a simple Factory implementation. We may use a Singleton
* implementation, or a collection pool. The choice is up to us.
*/
private TaskEngineImpl() {
super();
}
/**
* Return a usable instance of a TaskEngine to the requestor. Nothing
* sophisticated yet, simple doles out a new instance each time.
*/
public static TaskEngine getTaskEngine() {
return new TaskEngineImpl();
}
/**
* Walk the list of Tasks backwards until the root is reached. Keep track of
* the Tasks along the way in a Stack. Return null if the root Task is not a
* parent of the provided Task.
*/
protected Stack getTaskStack(Task root, Task task) {
Stack stack = new Stack();
while (task != null) {
stack.push(task);
if (task == root) {
return stack;
}
task = task.getParent();
}
return null;
}
/**
* Returns the next Task to be executed from the taskStack. The task is not
* removed from the Stack.
*/
public Task getNextExecuteTask() {
try {
return (Task)taskStack.peek();
} catch (EmptyStackException esx) {
return null;
}
}
/**
* If no root is specified, we will assume that the user wants to execute
* the Task with no root. This is accomplished by using the Task parameter
* as its own root.
*/
public void execute(Task task) throws AntException {
execute(task, task);
}
/**
* This is the workhorse, however it has been made to be very simple. Given
* the ability to specify a path between root and the target Task, we build
* a trail of Tasks to connect the two. Next we execute each Task on the way
* between the two Tasks. Once we arrive at the Task to execute, we execute
* all of its chlidren.
*/
public void execute(Task root, Task task) throws AntException {
fireEngineStart();
try {
taskStack = getTaskStack(root, task);;
if (taskStack == null) {
throw new AntException(
"The execution root Task is not an ancestor of the execution Task.");
}
// Pop thru the stack and execute each Task we come across.
while (!taskStack.isEmpty()) {
executeTask(taskStack);
}
} finally {
fireEngineFinish();
}
}
/**
* A recursive routine that allows all Tasks in the stack to be executed. At
* the same time, the stack may grow to include new Tasks.
*/
protected void executeTask(Stack taskStack) throws AntException {
Task task = (Task)taskStack.pop();
fireTaskStart(task);
try {
// Add a new property holder for this task to the property stack. Note
// that the parent of the new holder is the current stack head.
if (task.isPropertyContainer()) {
if (propertyStack.isEmpty()) {
propertyStack.push(new HierarchicalHashtable());
} else {
propertyStack.push(new HierarchicalHashtable(
(HierarchicalHashtable)propertyStack.peek()));
}
}
// Allow Task to do whatever it may need to do before touching its
// children.
task.init(this);
// Iterate the Task's children and execute any priority Tasks.
Task[] tasks = task.getChildren();
for (int i = 0, c = tasks.length; i < c; i++) {
if (tasks[i].getExecutionMode() == Task.EXECUTION_MODE_PRIORITY) {
taskStack.push(tasks[i]);
executeTask(taskStack);
}
}
// Allow the Task to validate.
task.validate();
// Finally, execute the Task.
fireTaskExecute(task);
task.execute(this);
// We can discard the no londer needed property holder.
if (task.isPropertyContainer()) {
propertyStack.pop();
}
} catch (AntException ax) {
fireTaskException(task, ax);
} finally {
fireTaskFinish(task);
}
}
/**
* Causes an AntEvent to be generated and fired to all listeners.
*/
public void message(Task task, String message) {
fireTaskMessage(task, message);
}
////////////////////////////////////////////////////////////////////////////
// Listener Support //
////////////////////////////////////////////////////////////////////////////
public synchronized void addAntEngineListener(AntEngineListener listener) {
if (!listenerList.contains(listener)) {
listenerList.add(listener);
}
}
public synchronized void removeAntEngineListener(AntEngineListener listener) {
if (listenerList.contains(listener)) {
listenerList.remove(listener);
}
}
protected synchronized void fireEngineStart() {
if (engineLevel++ > 0) return;
AntEvent e = new AntEvent(this);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).engineStart(e);
}
}
protected synchronized void fireEngineFinish() {
if (--engineLevel > 0) return;
AntEvent e = new AntEvent(this);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).engineFinish(e);
}
}
protected synchronized void fireTaskStart(Task task) {
AntEvent e = new AntEvent(this, task);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).taskStart(e);
}
}
protected synchronized void fireTaskExecute(Task task) {
AntEvent e = new AntEvent(this, task);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).taskExecute(e);
}
}
protected synchronized void fireTaskFinish(Task task) {
AntEvent e = new AntEvent(this, task);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).taskFinish(e);
}
}
protected synchronized void fireTaskMessage(Task task, String message) {
AntEvent e = new AntEvent(this, task);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).taskMessage(e, message);
}
}
protected synchronized void fireTaskException(Task task, AntException exception) {
AntEvent e = new AntEvent(this, task);
for (int i = 0; i < listenerList.size(); i++) {
((AntEngineListener)listenerList.get(i)).taskException(e, exception);
}
}
////////////////////////////////////////////////////////////////////////////
// Property Support Methods //
////////////////////////////////////////////////////////////////////////////
/**
* This is the routine that will perform key substitution. Phrase will come
* in as "src/${someparam}" and be converted to the appropriate "normalized"
* string. I suppose while I'm doing this we should support phrases with
* nested keys, such as "src/${build${token}}". Also, we should properly
* handle cases where ${someparam} will evaluate to ${anotherparam}.
* <p></p>
* One thing that will be different from the Ant 1.2 mechanismoccurs when a
* parameter value is not found. The substitution routine inserts it back in
* the phrase unchanged. I have opted to insert a zero-length string
* instead.
* <p></p>
* I should add a switch to the engine that will give the user the ability
* to throw an exception if a key is not found. Pretty easy, except this
* method is a strange place for an AntException to be thrown. Perhaps I
* should use a RuntimeException instead...
* <p></p>
* A brief rundown on the logic here:
* I check for the first instances of a key prefix.
* If none found we return the phrase as is.
* If key prefix is found get location of next key prefix and suffix.
* If suffix is found first, we have found a key.
* If there is no suffix, we return the phrase.
*/
static final String KEY_PREFIX = "${";
static final String KEY_SUFFIX = "}";
protected String substitute(String phrase) {
StringBuffer sb = new StringBuffer(phrase);
int startPoint = 0;
while (startPoint >= 0 && startPoint < phrase.length()) {
int pre1 = startPoint + phrase.substring(startPoint).indexOf(KEY_PREFIX);
if (pre1 < 0) break;
int suf1 = phrase.substring(pre1 + KEY_PREFIX.length()).indexOf(KEY_SUFFIX);
if (suf1 < 0) break;
suf1 = suf1 + pre1 + KEY_PREFIX.length();
int pre2 = phrase.substring(pre1 + KEY_PREFIX.length()).indexOf(KEY_PREFIX);
if (pre2 < 0) {
pre2 = phrase.length() + 1;
} else {
pre2 = pre2 + pre1 + KEY_PREFIX.length();
}
if (suf1 < pre2) {
// we have found a token
String key = sb.substring(pre1 + KEY_PREFIX.length(), suf1);
sb.delete(pre1, suf1 + 1);
Object value = getPropertyValueNoSubstitution(key);
if (value != null) {
sb.insert(pre1, value.toString());
}
return substitute(sb.toString());
}
startPoint = pre2;
}
return sb.toString();
}
public List getPropertyNames() {
if (propertyStack.isEmpty()) return new ArrayList();
HierarchicalHashtable hash = (HierarchicalHashtable)propertyStack.peek();
return hash.getPropertyNames();
}
public Object getPropertyValue(String name) {
if (propertyStack.isEmpty()) return null;
HierarchicalHashtable hash = (HierarchicalHashtable)propertyStack.peek();
Object result = hash.getPropertyValue(name);
if (result instanceof String) {
return substitute((String)result);
} else {
return result;
}
}
protected Object getPropertyValueNoSubstitution(String name) {
if (propertyStack.isEmpty()) return null;
HierarchicalHashtable hash = (HierarchicalHashtable)propertyStack.peek();
return hash.getPropertyValue(name);
}
public void setPropertyValue(String name, Object value) {
if (propertyStack.isEmpty()) return;
HierarchicalHashtable hash = (HierarchicalHashtable)propertyStack.peek();
hash.setPropertyValue(name, value);
}
public void removePropertyValue(String name) {
if (propertyStack.isEmpty()) return;
HierarchicalHashtable hash = (HierarchicalHashtable)propertyStack.peek();
hash.remove(name);
}
}