diff --git a/proposal/antfarm/ant.ant b/proposal/antfarm/ant.ant
new file mode 100644
index 000000000..4c1a2425e
--- /dev/null
+++ b/proposal/antfarm/ant.ant
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/proposal/antfarm/bootstrap.bat b/proposal/antfarm/bootstrap.bat
new file mode 100755
index 000000000..e46e815fb
--- /dev/null
+++ b/proposal/antfarm/bootstrap.bat
@@ -0,0 +1,35 @@
+@if not exist boot mkdir boot
+@if not exist boot\tasks mkdir boot\tasks
+@if not exist boot\xml mkdir boot\xml
+@if not exist temp mkdir temp
+@if not exist temp\core mkdir temp\core
+@if not exist temp\xml mkdir temp\xml
+@if not exist temp\tasks mkdir temp\tasks
+
+javac -classpath "" -d temp\core core\org\apache\tools\ant\*.java core\org\apache\tools\ant\cmdline\*.java core\*.java
+@if errorlevel 1 goto end
+
+jar -cfm boot\ant.jar core\META-INF\manifest.mf -C temp\core .
+@if errorlevel 1 goto end
+
+javac -classpath "boot\ant.jar;jaxp\jaxp.jar;jaxp\crimson.jar" -d temp\xml xml\org\apache\tools\ant\xml\*.java
+@if errorlevel 1 goto end
+
+jar -cf boot\xml\ant-xml.jar -C temp\xml .
+@if errorlevel 1 goto end
+
+javac -classpath "boot\ant.jar" -d temp\tasks tasks\org\apache\tools\ant\tasks\*.java
+@if errorlevel 1 goto end
+
+copy tasks\java2sdk.ant temp\tasks\java2sdk.ant
+
+jar -cf boot\tasks\java2sdk.jar -C temp\tasks .
+@if errorlevel 1 goto end
+
+copy jaxp\jaxp.jar boot\xml\jaxp.jar
+copy jaxp\crimson.jar boot\xml\crimson.jar
+
+@rmdir /s /q temp
+
+
+:end
\ No newline at end of file
diff --git a/proposal/antfarm/build.bat b/proposal/antfarm/build.bat
new file mode 100755
index 000000000..6f58b8356
--- /dev/null
+++ b/proposal/antfarm/build.bat
@@ -0,0 +1 @@
+java -classpath boot\ant.jar -Dant.project.path=.;jaxp ant ant:all
\ No newline at end of file
diff --git a/proposal/antfarm/clean.bat b/proposal/antfarm/clean.bat
new file mode 100755
index 000000000..8d85ad77b
--- /dev/null
+++ b/proposal/antfarm/clean.bat
@@ -0,0 +1,3 @@
+if exist boot rmdir /s /q boot
+if exist temp rmdir /s /q temp
+if exist dist rmdir /s /q dist
diff --git a/proposal/antfarm/core/META-INF/manifest.mf b/proposal/antfarm/core/META-INF/manifest.mf
new file mode 100644
index 000000000..5de3b41cd
--- /dev/null
+++ b/proposal/antfarm/core/META-INF/manifest.mf
@@ -0,0 +1,2 @@
+Main-Class: org.apache.tools.ant.cmdline.Main
+
diff --git a/proposal/antfarm/core/ant.java b/proposal/antfarm/core/ant.java
new file mode 100644
index 000000000..555667b0d
--- /dev/null
+++ b/proposal/antfarm/core/ant.java
@@ -0,0 +1,9 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+import org.apache.tools.ant.cmdline.Main;
+
+public class ant {
+ public static void main(String[] args) throws Exception {
+ Main.main(args);
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/AntException.java b/proposal/antfarm/core/org/apache/tools/ant/AntException.java
new file mode 100644
index 000000000..2dcbb0058
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/AntException.java
@@ -0,0 +1,52 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.io.*;
+
+/**
+ * This class represents an error within Ant. The end
+ * user should not be able to trigger this exception under
+ * normal circumstances. The BuildException class should
+ * be used instead to indicate that a compile or some other task
+ * has failed.
+ *
+ * @author Matthew Foemmel
+ */
+public class AntException extends RuntimeException {
+ private Throwable cause;
+
+ public AntException(String msg) {
+ super(msg);
+ }
+
+ public AntException(String msg, Throwable cause) {
+ super(msg);
+
+ this.cause = cause;
+ }
+
+ public Throwable getCause() {
+ return cause;
+ }
+
+ public void printStackTrace() {
+ printStackTrace(System.err);
+ }
+
+ public void printStackTrace(PrintStream out) {
+ super.printStackTrace(out);
+
+ if (cause != null) {
+ cause.printStackTrace(out);
+ }
+ }
+
+ public void printStackTrace(PrintWriter out) {
+ super.printStackTrace(out);
+
+ if (cause != null) {
+ cause.printStackTrace(out);
+ }
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/AntSecurityManager.java b/proposal/antfarm/core/org/apache/tools/ant/AntSecurityManager.java
new file mode 100644
index 000000000..c337235d4
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/AntSecurityManager.java
@@ -0,0 +1,27 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * This security manager is installed by the Workspace class
+ * while tasks are being invoked so that System.exit calls can
+ * be intercepted. Any tasks that tries to call System.exit
+ * will cause an ExitException to be thrown instead of terminating
+ * the VM.
+ *
+ * @author Matthew Foemmel
+ */
+public class AntSecurityManager extends SecurityManager {
+ /**
+ * Throws an ExitException which should be caught at the task level and handled.
+ */
+ public void checkExit(int status) {
+ throw new ExitException(status);
+ }
+
+ /**
+ * Allows anything.
+ */
+ public void checkPermission(java.security.Permission p) {
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/BuildEvent.java b/proposal/antfarm/core/org/apache/tools/ant/BuildEvent.java
new file mode 100644
index 000000000..578185737
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/BuildEvent.java
@@ -0,0 +1,143 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.util.EventObject;
+
+/**
+ * This class encapsulates information about events that occur during
+ * a build.
+ *
+ * @see BuildListener
+ *
+ * @author Matthew Foemmel
+ */
+public class BuildEvent extends EventObject {
+ public static final int DEBUG = 1;
+ public static final int INFO = 2;
+ public static final int WARN = 3;
+ public static final int ERROR = 4;
+
+ private Workspace workspace;
+ private Project project;
+ private Target target;
+ private Task task;
+ private String message;
+ private int priority;
+ private BuildException exception;
+
+ /**
+ * Construct a BuildEvent for a workspace level event
+ *
+ * @param workspace the workspace that emitted the event.
+ */
+ public BuildEvent(Workspace workspace) {
+ super(workspace);
+ this.workspace = workspace;
+ this.project = null;
+ this.target = null;
+ this.task = null;
+ }
+
+ /**
+ * Construct a BuildEvent for a project level event
+ *
+ * @param project the project that emitted the event.
+ */
+ public BuildEvent(Project project) {
+ super(project);
+ this.workspace = project.getWorkspace();
+ this.project = project;
+ this.target = null;
+ this.task = null;
+ }
+
+ /**
+ * Construct a BuildEvent for a target level event
+ *
+ * @param target the target that emitted the event.
+ */
+ public BuildEvent(Target target) {
+ super(target);
+ this.workspace = target.getProject().getWorkspace();
+ this.project = target.getProject();
+ this.target = target;
+ this.task = null;
+ }
+
+ /**
+ * Construct a BuildEvent for a task level event
+ *
+ * @param task the task that emitted the event.
+ */
+ public BuildEvent(Task task) {
+ super(task);
+ this.workspace = task.getProject().getWorkspace();
+ this.project = task.getProject();
+ this.target = task.getTarget();
+ this.task = task;
+ }
+
+ public void setMessage(String message, int priority) {
+ this.message = message;
+ this.priority = priority;
+ }
+
+ public void setException(BuildException exception) {
+ this.exception = exception;
+ }
+
+ /**
+ * Returns the project that fired this event.
+ */
+ public Project getProject() {
+ return project;
+ }
+
+ /**
+ * Returns the target that fired this event.
+ */
+ public Target getTarget() {
+
+ return target;
+ }
+
+ /**
+ * Returns the task that fired this event.
+ */
+ public Task getTask() {
+ return task;
+ }
+
+ /**
+ * Returns the logging message. This field will only be set
+ * for "messageLogged" events.
+ *
+ * @see BuildListener#messageLogged(BuildEvent)
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * Returns the priority of the logging message. This field will only
+ * be set for "messageLogged" events.
+ *
+ * @see BuildListener#messageLogged(BuildEvent)
+ */
+ public int getPriority(){
+ return priority;
+ }
+
+ /**
+ * Returns the exception that was thrown, if any. This field will only
+ * be set for "taskFinished", "targetFinished", and "buildFinished" events.
+ *
+ * @see BuildListener#taskFinished(BuildEvent)
+ * @see BuildListener#targetFinished(BuildEvent)
+ * @see BuildListener#buildFinished(BuildEvent)
+ */
+ public BuildException getException() {
+ return exception;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/BuildException.java b/proposal/antfarm/core/org/apache/tools/ant/BuildException.java
new file mode 100644
index 000000000..bf29e8b27
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/BuildException.java
@@ -0,0 +1,52 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * Indicates that an error during the build, such as a compiler error,
+ * a typo in a build file, etc. Errors resulting from coding
+ * errors within ant or a misconfigured setup should use
+ * AntException.
+ *
+ * @see AntException
+ *
+ * @author Matthew Foemmel
+ */
+public class BuildException extends Exception {
+ private String location;
+
+ /**
+ * Constructs a new exception with the specified message.
+ */
+ public BuildException(String message) {
+ super(message);
+ }
+
+ /**
+ * Constructs a new exception with the specified message and location.
+ */
+ public BuildException(String message, String location) {
+ super(message);
+
+ this.location = location;
+ }
+
+ /**
+ * Returns the location in the build file where this error.
+ * occured.
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ /**
+ * Sets the location in the build file where this error occured.
+ */
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ public String toString() {
+ return (location == null) ? getMessage() : (location + ": " + getMessage());
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/BuildListener.java b/proposal/antfarm/core/org/apache/tools/ant/BuildListener.java
new file mode 100644
index 000000000..e9f16db32
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/BuildListener.java
@@ -0,0 +1,79 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.util.EventListener;
+
+/**
+ * Objects that implement this interface can be notified when
+ * things happened during a build.
+ *
+ * @see BuildEvent
+ * @see Project#addBuildListener(BuildListener)
+ *
+ * @author Matthew Foemmel
+ */
+public interface BuildListener extends EventListener {
+
+ /**
+ * Fired before any targets are started.
+ */
+ public void buildStarted(BuildEvent event);
+
+ /**
+ * Fired after the last target has finished. This event
+ * will still be thrown if an error occured during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void buildFinished(BuildEvent event);
+
+ /**
+ * Fired before a project file is parsed.
+ */
+ public void importStarted(BuildEvent event);
+
+ /**
+ * Fired after a project file is parsed.
+ */
+ public void importFinished(BuildEvent event);
+
+ /**
+ * Fired when a target is started.
+ *
+ * @see BuildEvent#getTarget()
+ */
+ public void targetStarted(BuildEvent event);
+
+ /**
+ * Fired when a target has finished. This event will
+ * still be thrown if an error occured during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void targetFinished(BuildEvent event);
+
+ /**
+ * Fired when a task is started.
+ *
+ * @see BuildEvent#getTask()
+ */
+ public void taskStarted(BuildEvent event);
+
+ /**
+ * Fired when a task has finished. This event will still
+ * be throw if an error occured during the build.
+ *
+ * @see BuildEvent#getException()
+ */
+ public void taskFinished(BuildEvent event);
+
+ /**
+ * Fired whenever a message is logged.
+ *
+ * @see BuildEvent#getMessage()
+ * @see BuildEvent#getPriority()
+ */
+ public void messageLogged(BuildEvent event);
+
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/ExitException.java b/proposal/antfarm/core/org/apache/tools/ant/ExitException.java
new file mode 100644
index 000000000..414d0b3a8
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/ExitException.java
@@ -0,0 +1,21 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * Thrown by the AntSecurityManager whenever a task tries
+ * to call System.exit().
+ *
+ * @author Matthew Foemmel
+ */
+public class ExitException extends RuntimeException {
+ private int status;
+
+ public ExitException(int status) {
+ this.status = status;
+ }
+
+ public int getStatus() {
+ return status;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Import.java b/proposal/antfarm/core/org/apache/tools/ant/Import.java
new file mode 100644
index 000000000..4ea4f4672
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Import.java
@@ -0,0 +1,36 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * Represents an import statement from a project.
+ *
+ * @author Matthew Foemmel
+ */
+public class Import {
+ private Project project;
+ private String name;
+ private String location;
+
+ public Import(Project project, String name) {
+ this.project = project;
+ this.name = name;
+ this.location = location;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Importer.java b/proposal/antfarm/core/org/apache/tools/ant/Importer.java
new file mode 100644
index 000000000..b561a47d3
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Importer.java
@@ -0,0 +1,14 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.util.*;
+
+/**
+ * Used by a workspace to read project files.
+ *
+ * @author Matthew Foemmel
+ */
+public interface Importer {
+ public void importProject(Project project) throws BuildException;
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Load.java b/proposal/antfarm/core/org/apache/tools/ant/Load.java
new file mode 100644
index 000000000..7825ddc0d
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Load.java
@@ -0,0 +1,46 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.net.*;
+
+/**
+ * The only task that gets loaded by default. It can be used
+ * to dynamically load any other required tasks.
+ *
+ * @author Matthew Foemmel
+ */
+public class Load extends Task {
+ private String name;
+ private String classname;
+
+ public void execute() throws BuildException {
+ try {
+ getWorkspace().debug("Loading " + name);
+ ClassLoader loader = new URLClassLoader(
+ new URL[] { getProject().getBase() },
+ getWorkspace().getClass().getClassLoader());
+
+ getWorkspace().registerTask(name, loader.loadClass(classname));
+ }
+ catch(ClassNotFoundException exc) {
+ throw new BuildException("Class \"" + classname + "\" not found");
+ }
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getClassname() {
+ return classname;
+ }
+
+ public void setClassname(String classname) {
+ this.classname = classname;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Project.java b/proposal/antfarm/core/org/apache/tools/ant/Project.java
new file mode 100644
index 000000000..e4c823035
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Project.java
@@ -0,0 +1,179 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+
+/**
+ * Stores the information for a single project file. Each project
+ * has its own namespace for variable names and target names.
+ *
+ * @author Matthew Foemmel
+ */
+public class Project {
+ private Workspace workspace;
+ private String name;
+ private URL base;
+ private String location;
+ private List imports;
+ private Map targets;
+ private Map variables;
+
+ /**
+ * Constructs a new project. Should only be called by the Workspace class.
+ */
+ Project(Workspace workspace, String name) {
+ this.workspace = workspace;
+ this.name = name;
+ this.location = null;
+ this.imports = new ArrayList();
+ this.targets = new HashMap();
+ this.variables = new HashMap();
+ }
+
+ /**
+ * Returns the workspace that this project belongs to.
+ */
+ public Workspace getWorkspace() {
+ return workspace;
+ }
+
+ /**
+ * Returns the name of this project.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * The directory or jar file where this project file was located.
+ */
+ public URL getBase() {
+ return base;
+ }
+
+ /**
+ * The directory where this project file was located.
+ *
+ * @throws AntException if this project was loaded from a jar and not a directory.
+ */
+ public File getBaseDir() {
+ if (base.getProtocol().equals("file")) {
+ return new File(base.getFile());
+ }
+ else {
+ throw new AntException(base.toString() + " is not a directory");
+ }
+ }
+
+ public void setBase(URL base) {
+ this.base = base;
+
+ if (base.getProtocol().equals("file")) {
+ variables.put("ant.base.dir", base.getFile());
+ }
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ /**
+ * Creates an empty target with the specied name.
+ */
+ public Target createTarget(String name) throws BuildException {
+ Target target = new Target(this, name);
+ Target prevTarget = (Target) targets.put(name, target);
+
+ if (prevTarget != null) {
+ String msg = "Target with name \"" + name + "\" already exists";
+ if (prevTarget.getLocation() != null) {
+ msg = msg + " at " + prevTarget.getLocation();
+ }
+ throw new BuildException(msg);
+ }
+
+ return target;
+ }
+
+ /**
+ * Returns the target with the specified name.
+ *
+ * @throws AntException if the target doesn't exist.
+ */
+ public Target getTarget(String name) throws BuildException {
+ Target target = (Target) targets.get(name);
+ if (target == null) {
+ throw new BuildException("Target \"" + name + "\" not found");
+ }
+ return target;
+ }
+
+ public Collection getTargets() {
+ return targets.values();
+ }
+
+ /**
+ * Indicates the this project relies on variables or targets in another project.
+ */
+ public Import createImport(String name) {
+ Import imp = new Import(this, name);
+ imports.add(imp);
+ return imp;
+ }
+
+ /**
+ * Returns the list of projects that this project imports.
+ */
+ public List getImports() {
+ return imports;
+ }
+
+ /**
+ * Returns the value of the variable. Variables from other
+ * projects may be referenced by using the ':' operator.
+ */
+ public String getVariable(String name) throws BuildException {
+ int pos = name.indexOf(Workspace.SCOPE_SEPARATOR);
+ if (pos == -1) {
+ String value = (String) variables.get(name);
+ if (value == null) {
+ throw new BuildException("Variable \"" + name + "\" not defined");
+ }
+ return value;
+ }
+ else {
+ String projectName = name.substring(0, pos);
+ String variableName = name.substring(pos + 1);
+ Project project = workspace.getProject(projectName);
+ return project.getVariable(variableName);
+ }
+ }
+
+ /**
+ * Sets the value of the variable. Variables from other
+ * projects may be referenced by using the ':' operator.
+ */
+ public void setVariable(String name, String value) throws BuildException {
+ int pos = name.indexOf(Workspace.SCOPE_SEPARATOR);
+ if (pos == -1) {
+ variables.put(name, value);
+ }
+ else {
+ String projectName = name.substring(0, pos);
+ String variableName = name.substring(pos + 1);
+ Project project = workspace.getProject(projectName);
+ project.setVariable(variableName, value);
+ }
+ }
+
+ public char getPathSeparator() {
+ return ':';
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Target.java b/proposal/antfarm/core/org/apache/tools/ant/Target.java
new file mode 100644
index 000000000..ddf4a9481
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Target.java
@@ -0,0 +1,72 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.util.*;
+
+/**
+ * Represents a set of actions to be executed, which may or may
+ * not depend on other sets of actions.
+ *
+ * @author Matthew Foemmel
+ */
+public class Target {
+ private Project project;
+ private String name;
+ private String location;
+ private List tasks;
+ private List depends;
+
+ /**
+ * Called by the Project class to create new targets.
+ */
+ Target(Project project, String name) {
+ this.project = project;
+ this.name = name;
+ this.location = null;
+ this.tasks = new ArrayList();
+ this.depends = new ArrayList();
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ public List getTasks() {
+ return tasks;
+ }
+
+ public void addDepend(String depend) {
+ // If no project was specified, use this target's project
+ if (depend.indexOf(Workspace.SCOPE_SEPARATOR) == -1) {
+ depend = getProject().getName() + Workspace.SCOPE_SEPARATOR + depend;
+ }
+ depends.add(depend);
+ }
+
+ public List getDepends() {
+ return depends;
+ }
+
+ /**
+ * Creates a task proxy for this target. The proxy will
+ * be converted into an actual task object at build time.
+ */
+ public TaskProxy createTaskProxy(String name) {
+ TaskProxy proxy = new TaskProxy(this, name);
+ tasks.add(proxy);
+ return proxy;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Task.java b/proposal/antfarm/core/org/apache/tools/ant/Task.java
new file mode 100644
index 000000000..cacae8bc2
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Task.java
@@ -0,0 +1,42 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * Abstract superclass for all task objects. Any class that
+ * extends this class can be plugged into a workspace by using the "load"
+ * task.
+ *
+ * @author Matthew Foemmel
+ */
+public abstract class Task {
+ private Workspace workspace;
+ private Project project;
+ private Target target;
+
+ public abstract void execute() throws BuildException;
+
+ public Workspace getWorkspace() {
+ return workspace;
+ }
+
+ void setWorkspace(Workspace workspace) {
+ this.workspace = workspace;
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ void setProject(Project project) {
+ this.project = project;
+ }
+
+ public Target getTarget() {
+ return target;
+ }
+
+ void setTarget(Target target) {
+ this.target = target;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/TaskData.java b/proposal/antfarm/core/org/apache/tools/ant/TaskData.java
new file mode 100644
index 000000000..7d49bc432
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/TaskData.java
@@ -0,0 +1,240 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.beans.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+/**
+ * This class stores info about a bean's properties so that
+ * the actual bean can be instantiated at a later time. This data
+ * is used to store info about a task, since the actual
+ * task class might not be loaded until after parsing is completed.
+ *
+ * @see TaskProxy
+ *
+ * @author Matthew Foemmel
+ */
+public class TaskData {
+ private TaskProxy proxy;
+ private String location;
+ private String text;
+ private Map properties;
+
+ /**
+ * Constructs a new TaskData under the specified task.
+ */
+ public TaskData(TaskProxy proxy) {
+ this.proxy = proxy;
+ this.location = null;
+ this.properties = new HashMap();
+ }
+
+ /**
+ * Returns the task proxy that this data is associated with.
+ */
+ public TaskProxy getTaskProxy() {
+ return proxy;
+ }
+
+ /**
+ * Returns the location in the build fiole where this data was defined.
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ /**
+ * Returns the location in the build fiole where this data was defined.
+ */
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ /**
+ * Sets the text for this bean data, for cases where the bean is a simple
+ * type like String or int.
+ */
+ public void setText(String text) {
+ this.text = text;
+ }
+
+
+ /**
+ * Sets the value of a property on the bean. Multiple properties can be
+ * added with the same name only if the property on the bean is an array.
+ */
+ public TaskData addProperty(String name) {
+ TaskData data = new TaskData(proxy);
+ getProperties(name).add(data);
+ return data;
+ }
+
+ /**
+ * Returns the list of property values for the specified name.
+ */
+ private List getProperties(String name) {
+ List result = (List) properties.get(name);
+ if (result == null) {
+ result = new ArrayList();
+ properties.put(name, result);
+ }
+ return result;
+ }
+
+ /**
+ * Creates a new bean instance and initializes its properties.
+ */
+ public Object createBean(Class type) throws BuildException {
+ Object bean = null;
+
+ // See if an editor exists for this type
+ PropertyEditor editor = PropertyEditorManager.findEditor(type);
+
+ if (editor == null) {
+ // We don't know how to handle text for types without editors
+ if (text != null) {
+ throw new BuildException("Unexpected text \"" + text + "\"", location);
+ }
+
+ try {
+ bean = type.newInstance();
+ }
+ catch(InstantiationException exc) {
+ throw new AntException("Unable to instantiate " + type.getName(), exc);
+ }
+ catch(IllegalAccessException exc) {
+ throw new AntException("Unable to access constructor for " + type.getName(), exc);
+ }
+ }
+ else {
+ try {
+ // Let the editor parse the text
+ editor.setAsText(parseVariables(text));
+ }
+ catch(NumberFormatException exc) {
+ throw new BuildException("\"" + text + "\" is not a valid number", location);
+ }
+
+ bean = editor.getValue();
+ }
+
+ // Update the fields on the bean
+ updateProperties(bean);
+
+ return bean;
+ }
+
+ /**
+ * Sets all of the property values on the bean.
+ */
+ private void updateProperties(Object bean) throws BuildException {
+
+ // Call setProperty for each property that's been defined
+ Iterator itr = properties.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry entry = (Map.Entry) itr.next();
+ String name = (String) entry.getKey();
+ List values = (List) entry.getValue();
+ setProperty(bean, name, values);
+ }
+ }
+
+ /**
+ * Finds the PropertyDescriptor for the specifed property and sets it.
+ */
+ private void setProperty(Object bean, String name, List value) throws BuildException {
+ PropertyDescriptor[] descriptors = getPropertyDescriptors(bean.getClass());
+
+ // Search for the property with the matching name
+ for (int i = 0; i < descriptors.length; i++) {
+ if (descriptors[i].getName().equals(name)) {
+ setProperty(bean, descriptors[i], value);
+ return;
+ }
+ }
+
+ throw new BuildException("Unexpected attribute \"" + name + "\"", location);
+ }
+
+ /**
+ * Sets a single property on a bean.
+ */
+ private static void setProperty(Object obj, PropertyDescriptor descriptor, List values) throws BuildException {
+ Object value = null;
+
+ Class type = descriptor.getPropertyType();
+
+ if (type.isArray()) {
+ value = createBeans(type.getComponentType(), values);
+ }
+ else if (values.size() == 1) {
+ TaskData data = (TaskData) values.get(0);
+ value = data.createBean(type);
+
+ }
+
+ try {
+ descriptor.getWriteMethod().invoke(obj, new Object[] { value });
+ }
+ catch(IllegalAccessException exc) {
+ throw new AntException("Unable to access write method for \"" + descriptor.getName() + "\"", exc);
+ }
+ catch(InvocationTargetException exc) {
+ throw new AntException("Unable to set property \"" + descriptor.getName() + "\"", exc.getTargetException());
+ }
+ }
+
+ /**
+ * Creates a number of beans with the same type using the list of TaskData's
+ */
+ private static Object[] createBeans(Class type, List values) throws BuildException {
+ Object[] beans = (Object[]) Array.newInstance(type, values.size());
+ int i = 0;
+ Iterator itr = values.iterator();
+ while (itr.hasNext()) {
+ TaskData data = (TaskData) itr.next();
+ beans[i++] = data.createBean(type);
+ }
+ return beans;
+ }
+
+ /**
+ * Uses the Introspector class to lookup the property descriptors for the class.
+ */
+ private static PropertyDescriptor[] getPropertyDescriptors(Class type) {
+ try {
+ return Introspector.getBeanInfo(type, Object.class).getPropertyDescriptors();
+ }
+ catch(IntrospectionException exc) {
+ throw new AntException("Unable to get bean info for " + type.getName());
+ }
+ }
+
+ /**
+ * Replaces any variables in the input string with their values.
+ */
+ private String parseVariables(String input) throws BuildException {
+ StringBuffer output = new StringBuffer();
+
+ int start = 0;
+ int end = 0;
+ while ((start = input.indexOf('{', end)) != -1) {
+ output.append(input.substring(end,start));
+ end = input.indexOf('}', start);
+ if (end != -1) {
+ String name = input.substring(++start, end++);
+ String value = proxy.getTarget().getProject().getVariable(name);
+ if (value == null) {
+ throw new BuildException("The variable \"" + name + "\" has not been defined");
+ }
+ output.append(value);
+ }
+ }
+
+ output.append(input.substring(end));
+
+ return output.toString();
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/TaskProxy.java b/proposal/antfarm/core/org/apache/tools/ant/TaskProxy.java
new file mode 100644
index 000000000..13e7f0e15
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/TaskProxy.java
@@ -0,0 +1,53 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+/**
+ * This class stores the information needed to
+ * instantiate a task object. It basically consists of
+ * the task name and a TaskData object, which stores the
+ * values of the fields.
+ *
+ * @author Matthew Foemmel
+ */
+public class TaskProxy {
+ private Target target;
+ private String name;
+ private TaskData data;
+ private String location;
+
+ public TaskProxy(Target target, String name) {
+ this.target = target;
+ this.name = name;
+ this.data = new TaskData(this);
+ }
+
+ public Target getTarget() {
+ return target;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public void setLocation(String location) {
+ this.location = location;
+ }
+
+ public TaskData getData() {
+ return data;
+ }
+
+ /**
+ * Finds the class for this task name, and creates an
+ * instance of it using TaskData.createBean().
+ */
+ public Task createTask() throws BuildException {
+ Class type = target.getProject().getWorkspace().getTaskClass(name);
+ return (Task) data.createBean(type);
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/Workspace.java b/proposal/antfarm/core/org/apache/tools/ant/Workspace.java
new file mode 100644
index 000000000..4cc99c09c
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/Workspace.java
@@ -0,0 +1,449 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant;
+
+import java.util.*;
+
+/**
+ * The main class in the Ant class hierarchy. A workspace contains
+ * multiple projects, which in turn contain multiple targets, which
+ * in turn contain multiple task proxies. The workspace also handles
+ * the sorting and execution of targets during a build.
+ *
+ * @author Matthew Foemmel
+ */
+public class Workspace {
+ public static final char SCOPE_SEPARATOR = ':';
+
+ private Importer importer;
+ private Map projects;
+ private Map tasks;
+ private List listeners;
+
+ private Task currentTask = null;
+
+ /**
+ * Constructs new Ant workspace with no projects. The only
+ * task that will be registered is the "load" task.
+ *
+ * The importer is used to handle the actual reading of build files.
+ * In theory, different importers could be used to read project info from
+ * DOM trees, serialized objects, databases, etc.
+ */
+ public Workspace(Importer importer) {
+ this.importer = importer;
+ this.projects = new HashMap();
+ this.tasks = new HashMap();
+ this.listeners = new ArrayList();
+
+ registerTask("load", Load.class);
+ }
+
+ /**
+ * Assigns a task class to a name.
+ */
+ public void registerTask(String name, Class type) {
+ tasks.put(name, type);
+ }
+
+ /**
+ * Returns the class for a task with the specified name.
+ */
+ public Class getTaskClass(String name) throws BuildException {
+ Class type = (Class) tasks.get(name);
+ if (type == null) {
+ throw new BuildException("No task named \"" + name + "\" has been loaded");
+ }
+ return type;
+ }
+
+ /**
+ * Creates a project with the specified name. The project initially
+ * contains no targets.
+ */
+ public Project createProject(String name) {
+ Project project = new Project(this, name);
+ projects.put(name, project);
+ return project;
+ }
+
+ /**
+ * Returns the project with the specified name, or throws
+ * an exception if no project exists with that name.
+ */
+ public Project getProject(String name) throws BuildException {
+ Project project = (Project) projects.get(name);
+ if (project == null) {
+ throw new BuildException("Project \"" + name + "\" not found");
+ }
+ return project;
+ }
+
+ /**
+ * Builds all of the targets in the list. Target names must
+ * be of the form projectname:targetname.
+ */
+ public boolean build(List fullNames) throws BuildException {
+
+ // This lets the tasks intercept System.exit() calls
+ SecurityManager sm = System.getSecurityManager();
+ System.setSecurityManager(new AntSecurityManager());
+
+ fireBuildStarted();
+
+ try {
+ // Parse the project files...
+ importTargets(fullNames);
+
+ // ...figure out the build order...
+ List toDoList = sortTargets(fullNames);
+
+ // ...and build the targets
+ Iterator itr = toDoList.iterator();
+ while (itr.hasNext()) {
+ Target target = (Target) itr.next();
+ buildTarget(target);
+ }
+ fireBuildFinished(null);
+ return true;
+ }
+ catch(BuildException exc) {
+ fireBuildFinished(exc);
+ return false;
+ }
+ finally {
+ System.setSecurityManager(sm);
+ }
+ }
+
+ /**
+ * Adds a listener to the workspace.
+ */
+ public void addBuildListener(BuildListener listener) {
+ listeners.add(listener);
+ }
+
+ /**
+ * Removes a listener to the workspace.
+ */
+ public void removeBuildListener(BuildListener listener) {
+ listeners.remove(listener);
+ }
+
+ /**
+ * Fires a messageLogged event with DEBUG priority
+ */
+ public void debug(String message) {
+ fireMessageLogged(message, BuildEvent.DEBUG);
+ }
+
+ /**
+ * Fires a messageLogged event with INFO priority
+ */
+ public void info(String message) {
+ fireMessageLogged(message, BuildEvent.INFO);
+ }
+
+ /**
+ * Fires a messageLogged event with WARN priority
+ */
+ public void warn(String message) {
+ fireMessageLogged(message, BuildEvent.WARN);
+ }
+
+ /**
+ * Fires a messageLogged event with ERROR priority
+ */
+ public void error(String message) {
+ fireMessageLogged(message, BuildEvent.ERROR);
+ }
+
+ /**
+ * Imports into the workspace all of the projects required to
+ * build a set of targets.
+ */
+ private void importTargets(List fullNames) throws BuildException {
+ Iterator itr = fullNames.iterator();
+ while (itr.hasNext()) {
+ String fullName = (String) itr.next();
+ String projectName = getProjectName(fullName);
+ importProject(projectName);
+ }
+ }
+
+ /**
+ * Imports the project into the workspace, as well as any others
+ * that the project depends on.
+ */
+ public Project importProject(String projectName) throws BuildException {
+ Project project = (Project) projects.get(projectName);
+
+ // Don't parse a project file more than once
+ if (project == null) {
+
+ // Parse the project file
+ project = createProject(projectName);
+
+ fireImportStarted(project);
+ try {
+ importer.importProject(project);
+ fireImportFinished(project, null);
+ }
+ catch(BuildException exc) {
+ fireImportFinished(project, exc);
+ throw exc;
+ }
+
+ // Parse any imported projects as well
+ Iterator itr = project.getImports().iterator();
+ while (itr.hasNext()) {
+ Import imp = (Import) itr.next();
+ importProject(imp.getName());
+ }
+ }
+
+ return project;
+ }
+
+
+
+ /**
+ * Builds a specific target. This assumes that the targets it depends
+ * on have already been built.
+ */
+ private void buildTarget(Target target) throws BuildException {
+ fireTargetStarted(target);
+
+ try {
+ List tasks = target.getTasks();
+ Iterator itr = tasks.iterator();
+ while (itr.hasNext()) {
+ TaskProxy proxy = (TaskProxy) itr.next();
+ executeTask(target, proxy);
+ }
+
+ fireTargetFinished(target, null);
+ }
+ catch(BuildException exc) {
+ fireTargetFinished(target, null);
+ throw exc;
+ }
+ }
+
+ /**
+ * Instantiates the task from the proxy and executes.
+ */
+ private void executeTask(Target target, TaskProxy proxy) throws BuildException {
+ Task task = proxy.createTask();
+ task.setWorkspace(this);
+ task.setProject(target.getProject());
+ task.setTarget(target);
+
+ fireTaskStarted(task);
+ currentTask = task;
+ try {
+ task.execute();
+
+ fireTaskFinished(task, null);
+ }
+ catch(BuildException exc) {
+ exc.setLocation(proxy.getLocation());
+ fireTaskFinished(task, exc);
+ throw exc;
+ }
+ finally {
+ currentTask = null;
+ }
+ }
+
+ /**
+ * Does a topological sort on a list of target names. Returns
+ * a list of Target objects in the order to be executed.
+ */
+ private List sortTargets(List fullNames) throws BuildException {
+ List results = new ArrayList();
+ sortTargets(results, new Stack(), fullNames);
+ return results;
+ }
+
+ private void sortTargets(List results, Stack visited, List fullNames) throws BuildException {
+ Iterator itr = fullNames.iterator();
+ while (itr.hasNext()) {
+ String fullName = (String) itr.next();
+
+ // Check for cycles
+ if (visited.contains(fullName)) {
+ throwCyclicDependency(visited, fullName);
+ }
+
+ // Check if we're already added this target to the list
+ Target target = getTarget(fullName);
+ if (results.contains(target)) {
+ continue;
+ }
+
+ visited.push(fullName);
+ sortTargets(results, visited, target.getDepends());
+ results.add(target);
+ visited.pop();
+ }
+ }
+
+ /**
+ * Creates and throws an exception indicating a cyclic dependency.
+ */
+ private void throwCyclicDependency(Stack visited, String fullName) throws BuildException {
+ StringBuffer msg = new StringBuffer("Cyclic dependency: ");
+ for (int i = 0; i < visited.size(); i++) {
+ msg.append((String)visited.get(i));
+ msg.append(" -> ");
+ }
+ msg.append(fullName);
+ throw new BuildException(msg.toString());
+ }
+
+ /**
+ * Parses the full target name into is project and target components,
+ * then locates the Target object.
+ */
+ private Target getTarget(String fullName) throws BuildException {
+ String projectName = getProjectName(fullName);
+ String targetName = getTargetName(fullName);
+
+ Project project = (Project) projects.get(projectName);
+ if (project == null) {
+ throw new BuildException("Project \"" + projectName + "\" not found");
+ }
+
+ Target target = project.getTarget(targetName);
+ if (target == null) {
+ throw new BuildException("Target \"" + fullName + "\" not found");
+ }
+
+ return target;
+ }
+
+ /**
+ * Returns the project portion of a full target name.
+ */
+ public static String getProjectName(String fullName) throws BuildException {
+ int pos = fullName.indexOf(SCOPE_SEPARATOR);
+ if (pos == -1 || pos == 0) {
+ throw new BuildException("\"" + fullName + "\" is not a valid target name");
+ }
+
+ return fullName.substring(0, pos);
+ }
+
+ /**
+ * Returns the target portion of a full target name.
+ */
+ public static String getTargetName(String fullName) throws BuildException {
+ int pos = fullName.indexOf(SCOPE_SEPARATOR);
+ if (pos == -1 || pos == 0) {
+ throw new BuildException("\"" + fullName + "\" is not a valid target name");
+ }
+
+ return fullName.substring(pos + 1);
+ }
+
+ private void fireMessageLogged(String message, int priority) {
+ BuildEvent event;
+ if (currentTask == null) {
+ event = new BuildEvent(this);
+ }
+ else {
+ event = new BuildEvent(currentTask);
+ }
+ event.setMessage(message, priority);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.messageLogged(event);
+ }
+ }
+
+ private void fireBuildStarted() {
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ BuildEvent event = new BuildEvent(this);
+ listener.buildStarted(event);
+ }
+ }
+
+ private void fireBuildFinished(BuildException exc) {
+ BuildEvent event = new BuildEvent(this);
+ event.setException(exc);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.buildFinished(event);
+ }
+ }
+
+ private void fireImportStarted(Project project) {
+ BuildEvent event = new BuildEvent(project);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.importStarted(event);
+ }
+ }
+
+ private void fireImportFinished(Project project, BuildException exc) {
+ BuildEvent event = new BuildEvent(project);
+ event.setException(exc);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.importFinished(event);
+ }
+ }
+
+ private void fireTargetStarted(Target target) {
+ BuildEvent event = new BuildEvent(target);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.targetStarted(event);
+ }
+ }
+
+ private void fireTargetFinished(Target target, BuildException exc) {
+ BuildEvent event = new BuildEvent(target);
+ event.setException(exc);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.targetFinished(event);
+ }
+ }
+
+ private void fireTaskStarted(Task task) {
+ BuildEvent event = new BuildEvent(task);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.taskStarted(event);
+ }
+ }
+
+ private void fireTaskFinished(Task task, BuildException exc) {
+ BuildEvent event = new BuildEvent(task);
+ event.setException(exc);
+
+ Iterator itr = listeners.iterator();
+ while (itr.hasNext()) {
+ BuildListener listener = (BuildListener) itr.next();
+ listener.taskFinished(event);
+ }
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/cmdline/DefaultLogger.java b/proposal/antfarm/core/org/apache/tools/ant/cmdline/DefaultLogger.java
new file mode 100644
index 000000000..893351ffc
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/cmdline/DefaultLogger.java
@@ -0,0 +1,57 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.cmdline;
+
+import java.io.*;
+import org.apache.tools.ant.*;
+
+/**
+ * @author Matthew Foemmel
+ */
+public class DefaultLogger implements BuildListener {
+ private PrintStream out;
+
+ public DefaultLogger(PrintStream out) {
+ this.out = out;
+ }
+
+ public void buildStarted(BuildEvent event) {
+ }
+
+ public void buildFinished(BuildEvent event) {
+ BuildException exc = event.getException();
+ out.println();
+ if (exc == null) {
+ out.println("BUILD SUCCESSFUL");
+ }
+ else {
+ out.println("BUILD FAILED");
+ out.println();
+ out.println(exc);
+ }
+ }
+
+ public void importStarted(BuildEvent event) {
+ out.println("Importing: " + event.getProject().getName());
+ }
+
+ public void importFinished(BuildEvent event) {
+ }
+
+ public void targetStarted(BuildEvent event) {
+ out.println("\n[" + event.getProject().getName() + ":" + event.getTarget().getName() + "]");
+ }
+
+ public void targetFinished(BuildEvent event) {
+ }
+
+ public void taskStarted(BuildEvent event) {
+ }
+
+ public void taskFinished(BuildEvent event) {
+ }
+
+ public void messageLogged(BuildEvent event) {
+ out.println(" " + event.getMessage());
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/core/org/apache/tools/ant/cmdline/Main.java b/proposal/antfarm/core/org/apache/tools/ant/cmdline/Main.java
new file mode 100644
index 000000000..5f26e6b6f
--- /dev/null
+++ b/proposal/antfarm/core/org/apache/tools/ant/cmdline/Main.java
@@ -0,0 +1,125 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.cmdline;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+
+/**
+ * Runs the command line version of ant. Takes a list of
+ * fully qualified targets and builds them.
+ * Any jars in the "tasks" directory will be automatically added
+ * to the project path.
+ *
+ * @author Matthew Foemmel
+ */
+public class Main {
+
+ /**
+ * Builds the specified targets.
+ */
+ public static void main(String[] args) {
+ File installDir = findInstallDir();
+ setupProjectPath(installDir);
+ Importer importer = loadImporter(installDir);
+
+ Workspace workspace = new Workspace(importer);
+ workspace.addBuildListener(new DefaultLogger(System.out));
+
+ List targets = Arrays.asList(args);
+
+ try {
+ if (workspace.build(targets)) {
+ System.exit(0);
+ }
+ else {
+ System.exit(1);
+ }
+ }
+ catch(Exception exc) {
+ exc.printStackTrace();
+ System.exit(2);
+ }
+ }
+
+ /**
+ * Finds the ant.jar file in the classpath.
+ */
+ private static File findInstallDir() {
+ StringTokenizer itr = new StringTokenizer(
+ System.getProperty("java.class.path"),
+ System.getProperty("path.separator"));
+
+ while (itr.hasMoreTokens()) {
+ File file = new File(itr.nextToken());
+ if (file.getName().equals("ant.jar")) {
+ // Found it
+ File dir = file.getParentFile();
+ if (dir == null) {
+ dir = new File(".");
+ }
+ return dir;
+ }
+ }
+
+ System.err.println("Unable to locate ant.jar");
+ System.exit(1);
+ return null;
+ }
+
+ /**
+ * Locates the "tasks" directory relative to the ant.jar file.
+ */
+ private static void setupProjectPath(File installDir) {
+ StringBuffer path = new StringBuffer(System.getProperty("ant.project.path", "."));
+
+ File taskDir = new File(installDir, "tasks");
+ if (taskDir.exists()) {
+ File[] taskjars = taskDir.listFiles();
+ for (int i = 0; i < taskjars.length; i++) {
+ path.append(System.getProperty("path.separator"));
+ path.append(taskjars[i].getPath());
+ }
+ }
+
+ System.setProperty("ant.project.path", path.toString());
+ System.out.println(path.toString());
+ }
+
+ /**
+ * Creates a class loader using the jars from the "xml" directory, and
+ * loads the XmlImporter class.
+ */
+ private static Importer loadImporter(File installDir) {
+ File xmlDir = new File(installDir, "xml");
+ if (xmlDir.exists()) {
+ File[] xmlJars = xmlDir.listFiles();
+ URL[] urls = new URL[xmlJars.length];
+ for (int i = 0; i < xmlJars.length; i++) {
+ try {
+ urls[i] = xmlJars[i].toURL();
+ }
+ catch(MalformedURLException exc) {
+ exc.printStackTrace();
+ }
+ }
+
+ try {
+ URLClassLoader loader = new URLClassLoader(urls);
+ return (Importer) loader.loadClass("org.apache.tools.ant.xml.XmlImporter").newInstance();
+ }
+ catch(Exception exc) {
+ exc.printStackTrace();
+ System.exit(1);
+ }
+ }
+ else {
+ System.err.println("Unable to find xml directory");
+ System.exit(1);
+ }
+
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/readme.txt b/proposal/antfarm/readme.txt
new file mode 100644
index 000000000..36789bb4e
--- /dev/null
+++ b/proposal/antfarm/readme.txt
@@ -0,0 +1,32 @@
+*** Installing ***
+
+To get things started you'll need to run the bootstrap.bat file, which will manually compile a version of ant into the "boot" directory. From then on you can use Ant to build Ant by running the build.bat file. (There aren't any unix scripts yet, unfortunately. Any help here would be appreciated!)
+
+To "install" ant, you just need to have the ant.jar file in your classpath. Ant will figure out the rest. To run it, type:
+
+java ant project:target
+
+When ant is run, the Main class scans the classpath to find the ant.jar file, and can figure out from there where the rest of the files are. In particular, the jars in the "tasks" directory get added to the project path automatically. And the jars in the "xml" directory get loaded using a separate class loader, so that they don't conflict with the xml parsers that various tasks might be using.
+
+
+*** Running ***
+
+For now, the targets specified con the command line must be in the form project:target. For example, if you want to build target "all" in file "foo.ant", the target name would be "foo:all". In the future there will be a way to specify the default project, so that only the target name would need to be specified.
+
+Ant searches along the "project path" to find projects specified on the command line or in "import" statements. The project path defaults to ".", ie the current directory, but can be overridden by setting the "ant.project.path" system property. Variables and targets from other projects can be accessed by prefixing them with the project name and a colon.
+
+
+*** Concepts ***
+
+The main thing I'd like people to check out is the whole workspace concept, ie the ability to pull multiple ant files into a single build. I personally think this will make it easier to reuse ant files from other projects, and avoid recursive make syndrome at the same time. Plus, I think this model lends itself to CJAN implementation quite nicely.
+
+I've also tried to make the tasks more compliant with the javabean spec. As a result of this, the word "property" became so overloaded that I decided to use the term "variable" for values defined in ant projects. If anyone feels strongly about it I'll change it back.
+
+The depends attribute on targets uses a whitespace delimited list of target names, instead of comma separated. This is more consistent with the "list" datatype in the xml schema spec.
+
+The parser is namespace aware, and attributes for any namespace other than the default namespace are ignored.
+
+The code relies heavily on JDK 1.2 features. Making it JDK 1.1 compatible would be a lot of work, but is definitely doable.
+
+There's obviously a lot of stuff missing from this prototype, such as datatypes, real tasks, a way to access system properties, default targets, etc. If enough people like the basic design I'll start tackling those next...
+
diff --git a/proposal/antfarm/tasks/java2sdk.ant b/proposal/antfarm/tasks/java2sdk.ant
new file mode 100644
index 000000000..fa6b4510f
--- /dev/null
+++ b/proposal/antfarm/tasks/java2sdk.ant
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Copy.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Copy.java
new file mode 100644
index 000000000..2e8917242
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Copy.java
@@ -0,0 +1,46 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import java.io.*;
+import org.apache.tools.ant.*;
+
+public class Copy extends Task {
+ private String src;
+ private String dest;
+
+ public void execute() throws BuildException {
+ try {
+ FileInputStream in = new FileInputStream(src);
+ FileOutputStream out = new FileOutputStream(dest);
+
+ byte[] buf = new byte[4096];
+ int len = 0;
+ while ((len = in.read(buf)) != -1) {
+ out.write(buf, 0, len);
+ }
+ }
+ catch(FileNotFoundException exc) {
+ throw new BuildException("File not found");
+ }
+ catch(IOException exc) {
+ throw new AntException("Error copying files", exc);
+ }
+ }
+
+ public String getSrc() {
+ return src;
+ }
+
+ public void setSrc(String src) {
+ this.src = src;
+ }
+
+ public String getDest() {
+ return dest;
+ }
+
+ public void setDest(String dest) {
+ this.dest = dest;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Echo.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Echo.java
new file mode 100644
index 000000000..06cebfc7c
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Echo.java
@@ -0,0 +1,21 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import org.apache.tools.ant.*;
+
+public class Echo extends Task {
+ private String message;
+
+ public void execute() throws BuildException {
+ System.out.println(message);
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Fileset.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Fileset.java
new file mode 100644
index 000000000..d6da3b256
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Fileset.java
@@ -0,0 +1,43 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import java.io.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+
+public class Fileset {
+ private String src;
+
+ public String getSrc() {
+ return src;
+ }
+
+ public void setSrc(String src) {
+ this.src = src;
+ }
+
+ public void getFiles(List results) throws BuildException {
+ if (src == null) {
+ throw new BuildException("Missing property \"src\"", null); //LOCATION
+ }
+
+ File dir = new File(src);
+ if (!dir.exists()) {
+ throw new BuildException(src + " does not exist", null); // LOCATION!!!
+ }
+ getFiles(dir, results);
+ }
+
+ private void getFiles(File file, List results) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ for (int i = 0; i < files.length; i++) {
+ getFiles(files[i], results);
+ }
+ }
+ else if (file.getPath().endsWith(".java")) {
+ results.add(file.getPath());
+ }
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Jar.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Jar.java
new file mode 100644
index 000000000..315e91c5e
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Jar.java
@@ -0,0 +1,85 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+
+public class Jar extends Task {
+ private String jarfile;
+ private String basedir;
+ private String manifest;
+
+ public String getJarfile() {
+ return jarfile;
+ }
+
+ public void setJarfile(String jarfile) {
+ this.jarfile = jarfile;
+ }
+
+ public String getBasedir() {
+ return basedir;
+ }
+
+ public void setBasedir(String basedir) {
+ this.basedir = basedir;
+ }
+
+ public String getManifest() {
+ return manifest;
+ }
+
+ public void setManifest(String manifest) {
+ this.manifest = manifest;
+ }
+
+ public void execute() throws BuildException {
+ File dir = new File(jarfile).getParentFile();
+ if (dir != null) {
+ dir.mkdirs();
+ }
+ List argList = new ArrayList();
+ if (manifest == null) {
+ argList.add("-cf");
+ }
+ else {
+ argList.add("-cmf");
+ argList.add(manifest);
+ }
+ argList.add(jarfile);
+ argList.add("-C");
+ argList.add(basedir);
+ argList.add(".");
+
+ String[] args = (String[]) argList.toArray(new String[argList.size()]);
+
+ try {
+ Class type = getClass().getClassLoader().loadClass("sun.tools.jar.Main");
+ Method method = type.getMethod("main", new Class[] { args.getClass() });
+
+ getWorkspace().info("Running jar...");
+
+ method.invoke(null, new Object[] { args });
+ }
+ catch(InvocationTargetException exc) {
+ Throwable cause = exc.getTargetException();
+ if (cause instanceof ExitException) {
+ if (((ExitException)cause).getStatus() != 0) {
+ throw new BuildException("Build failed");
+ }
+ }
+ else {
+ throw new AntException("Error running jar", exc);
+ }
+ }
+ catch(ClassNotFoundException exc) {
+ throw new AntException("Jar class not found. Makes sure tools.jar is in your classpath");
+ }
+ catch(Exception exc) {
+ throw new AntException("Error running jar", exc);
+ }
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Javac.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Javac.java
new file mode 100644
index 000000000..eaed496ce
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/Javac.java
@@ -0,0 +1,93 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import java.lang.reflect.*;
+import java.io.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+
+public class Javac extends Task {
+ private Fileset[] fileset;
+ private String dest;
+ private String classpath;
+ private String compilerclass = null;
+
+ public void execute() throws BuildException {
+ if (compilerclass == null) {
+ compilerclass = "com.sun.tools.javac.Main";
+ }
+
+ List argList = new ArrayList();
+ argList.add("-d");
+ argList.add(dest);
+
+ if (classpath != null) {
+ argList.add("-classpath");
+
+ // Replace the project's path separator with the system's path separator
+ argList.add(classpath.replace(getProject().getPathSeparator(), File.pathSeparatorChar));
+ }
+
+ for (int i = 0; i < fileset.length; i++) {
+ fileset[i].getFiles(argList);
+ }
+
+ String[] args = (String[]) argList.toArray(new String[argList.size()]);
+
+ try {
+ new File(dest).mkdirs();
+
+ Class type = getClass().getClassLoader().loadClass(compilerclass);
+ Method method = type.getMethod("main", new Class[] { args.getClass() });
+
+ getWorkspace().info("Running javac...");
+
+ method.invoke(null, new Object[] { args });
+ }
+ catch(InvocationTargetException exc) {
+ Throwable cause = exc.getTargetException();
+ if (cause instanceof ExitException) {
+ if (((ExitException)cause).getStatus() != 0) {
+ throw new BuildException("Compile failed");
+ }
+ }
+ else {
+ throw new AntException("Error running compiler", exc);
+ }
+ }
+ catch(ClassNotFoundException exc) {
+ throw new BuildException("Compiler class not found. Makes sure tools.jar is in your classpath");
+ }
+ catch(IllegalAccessException exc) {
+ throw new AntException("Unable to access compiler class", exc);
+ }
+ catch(NoSuchMethodException exc) {
+ throw new AntException("Unable to find main method on compiler class", exc);
+ }
+ }
+
+ public String getDest() {
+ return dest;
+ }
+
+ public void setDest(String dest) {
+ this.dest = dest;
+ }
+
+ public String getClasspath() {
+ return classpath;
+ }
+
+ public void setClasspath(String classpath) {
+ this.classpath = classpath;
+ }
+
+ public Fileset[] getFileset() {
+ return fileset;
+ }
+
+ public void setFileset(Fileset[] fileset) {
+ this.fileset = fileset;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/JavacLoader.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/JavacLoader.java
new file mode 100644
index 000000000..7c7d0fc11
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/JavacLoader.java
@@ -0,0 +1,31 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import java.io.*;
+import java.net.*;
+import org.apache.tools.ant.*;
+
+public class JavacLoader extends Task {
+ public void execute() throws BuildException {
+ try {
+ URL toolsJar = findToolsJar();
+ ClassLoader loader = new URLClassLoader(
+ new URL[] { getProject().getBase(), toolsJar },
+ getWorkspace().getClass().getClassLoader());
+
+ getWorkspace().registerTask("javac", loader.loadClass("org.apache.tools.ant.tasks.Javac"));
+ }
+ catch(MalformedURLException exc) {
+ throw new AntException("Bad URL", exc);
+ }
+ catch(ClassNotFoundException exc) {
+ throw new BuildException("Class not found");
+ }
+ }
+
+ private URL findToolsJar() throws MalformedURLException {
+ // I assume this won't work everywhere...
+ return new File(new File(System.getProperty("java.home")), "../lib/tools.jar").toURL();
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/tasks/org/apache/tools/ant/tasks/SetVariable.java b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/SetVariable.java
new file mode 100644
index 000000000..e1f6a8f98
--- /dev/null
+++ b/proposal/antfarm/tasks/org/apache/tools/ant/tasks/SetVariable.java
@@ -0,0 +1,30 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.tasks;
+
+import org.apache.tools.ant.*;
+
+public class SetVariable extends Task {
+ private String name;
+ private String value;
+
+ public void execute() throws BuildException {
+ getProject().setVariable(name, value);
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/xml/org/apache/tools/ant/xml/ProjectHandler.java b/proposal/antfarm/xml/org/apache/tools/ant/xml/ProjectHandler.java
new file mode 100644
index 000000000..ef561d771
--- /dev/null
+++ b/proposal/antfarm/xml/org/apache/tools/ant/xml/ProjectHandler.java
@@ -0,0 +1,289 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.xml;
+
+import java.io.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.apache.tools.ant.*;
+import org.xml.sax.*;
+import org.xml.sax.ext.*;
+import org.xml.sax.helpers.*;
+
+/**
+ * This class populates a Project object via SAX events.
+ */
+public class ProjectHandler extends DefaultHandler /* implements LexicalHandler */ {
+ private Workspace workspace;
+ private Project project;
+ private Locator locator;
+
+ /**
+ * The top of this stack represents the "current" event handler.
+ */
+ private Stack handlers;
+
+ /**
+ * Constructs a SAX handler for the specified project.
+ */
+ public ProjectHandler(Project project) {
+ this.project = project;
+ this.workspace = project.getWorkspace();
+ this.handlers = new Stack();
+ this.handlers.push(new RootHandler());
+ }
+
+ public Project getProject() {
+ return project;
+ }
+
+ public void setDocumentLocator(Locator locator) {
+ this.locator = locator;
+ }
+
+ protected String getLocation() {
+ return locator.getPublicId() + ":" + locator.getLineNumber();
+ }
+
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ // Delegate to the current handler
+ ((ContentHandler)handlers.peek()).startElement(namespaceURI, localName, qName, atts);
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ // Delegate to the current handler
+ ((ContentHandler)handlers.peek()).endElement(namespaceURI, localName, qName);
+ }
+
+ public void characters(char[] ch, int start, int length) {
+ //XXX need to implement text content
+ }
+
+ public void processingInstruction(String target, String data) {
+ System.out.println("@" + target + "@" + data + "@");
+ }
+
+ /*
+ public void comment(char[] ch, int start, int length) {)
+ public void endCDATA() {}
+ public void endDTD() {}
+ public void endEntity(java.lang.String name) {}
+ public void startCDATA() {}
+ public void startDTD(String name, String publicId, String systemId) {}
+ public void startEntity(java.lang.String name) {}
+ */
+
+ /**
+ * This class handles any top level SAX events.
+ */
+ private class RootHandler extends DefaultHandler {
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (isAntNamespace(namespaceURI) && localName.equals("project")) {
+ handlers.push(new ProjectElemHandler(qName, atts));
+ }
+ else {
+ throw new SAXParseException("Unexpected element \"" + qName + "\"", locator);
+ }
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ handlers.pop();
+ }
+ }
+
+ /**
+ * This class handles events that occur with a "project" element.
+ */
+ private class ProjectElemHandler extends DefaultHandler {
+ public ProjectElemHandler(String qName, Attributes atts) throws SAXException {
+ String projectName = null;
+
+ for (int i = 0; i < atts.getLength(); i++) {
+ if (!isAntNamespace(atts.getURI(i))) {
+ continue;
+ }
+
+ String name = atts.getQName(i);
+ String value = atts.getValue(i);
+ if (name.equals("name")) {
+ projectName = value;
+ }
+ else {
+ throw new SAXParseException("Unexpected attribute \"" + name + "\"", locator);
+ }
+ }
+
+ if (projectName == null) {
+ throw new SAXParseException("Missing attribute \"name\"", locator);
+ }
+
+ if (!projectName.equals(project.getName())) {
+ throw new SAXParseException("A project named \"" + projectName + "\" must be located in a file called \"" + projectName + ".ant\"", locator);
+ }
+ }
+
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (isAntNamespace(namespaceURI) && localName.equals("target")) {
+ handlers.push(new TargetElemHandler(project, qName, atts));
+ }
+ else if (isAntNamespace(namespaceURI) && localName.equals("import")) {
+ handlers.push(new ImportElemHandler(project, qName, atts));
+ }
+ else {
+ throw new SAXParseException("Unexpected element \"" + qName + "\"", locator);
+ }
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ handlers.pop();
+ }
+ }
+
+ /**
+ * This class handles events that occur with a "target" element.
+ */
+ private class TargetElemHandler extends DefaultHandler {
+ private Target target;
+
+ public TargetElemHandler(Project project, String qName, Attributes atts) throws SAXException {
+ String targetName = null;
+ String dependencies = "";
+
+ for (int i = 0; i < atts.getLength(); i++) {
+ if (!isAntNamespace(atts.getURI(i))) {
+ continue;
+ }
+
+ String name = atts.getQName(i);
+ String value = atts.getValue(i);
+ if (name.equals("name")) {
+ targetName = value;
+ }
+ else if (name.equals("depends")) {
+ dependencies = value;
+ }
+ else {
+ throw new SAXParseException("Unexpected attribute \"" + name + "\"", locator);
+ }
+ }
+
+ if (targetName == null) {
+ throw new SAXParseException("Missing attribute \"name\"", locator);
+ }
+
+ try {
+ target = project.createTarget(targetName);
+ target.setLocation(getLocation());
+ parseDepends(dependencies);
+ }
+ catch(BuildException exc) {
+ throw new SAXException(exc);
+ }
+ }
+
+ /**
+ * Parses the list of space-separated project names.
+ */
+ private void parseDepends(String depends) {
+ StringTokenizer tokenizer = new StringTokenizer(depends);
+ while (tokenizer.hasMoreTokens()) {
+ String targetName = tokenizer.nextToken();
+ target.addDepend(targetName);
+ }
+ }
+
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (!isAntNamespace(namespaceURI)) {
+ throw new SAXParseException("Unexpected attribute \"" + qName + "\"", locator);
+ }
+
+ TaskProxy proxy = target.createTaskProxy(qName);
+ proxy.setLocation(getLocation());
+ handlers.push(new TaskElemHandler(proxy.getData(), qName, atts));
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ handlers.pop();
+ }
+ }
+
+ /**
+ * This class handles events that occur with a "import" element.
+ */
+ private class ImportElemHandler extends DefaultHandler {
+ public ImportElemHandler(Project project, String qName, Attributes atts) throws SAXException {
+ String importName = null;
+
+ for (int i = 0; i < atts.getLength(); i++) {
+ if (!isAntNamespace(atts.getURI(i))) {
+ continue;
+ }
+
+ String name = atts.getQName(i);
+ String value = atts.getValue(i);
+ if (name.equals("name")) {
+ importName = value;
+ }
+ else {
+ throw new SAXParseException("Unexpected attribute \"" + name + "\"", locator);
+ }
+ }
+
+ if (importName == null) {
+ throw new SAXParseException("Missing attribute \"name\"", locator);
+ }
+
+ Import imp = project.createImport(importName);
+ imp.setLocation(getLocation());
+ }
+
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ throw new SAXParseException("Unexpected element \"" + qName + "\"", locator);
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ handlers.pop();
+ }
+ }
+
+ /**
+ * This class handles events that occur with a task element.
+ */
+ private class TaskElemHandler extends DefaultHandler {
+ private TaskData data;
+
+ public TaskElemHandler(TaskData data, String qName, Attributes atts) throws SAXException {
+ this.data = data;
+
+ for (int i = 0; i < atts.getLength(); i++) {
+ if (!isAntNamespace(atts.getURI(i))) {
+ continue;
+ }
+
+ String name = atts.getQName(i);
+ String value = atts.getValue(i);
+ TaskData child = data.addProperty(name);
+ child.setLocation(getLocation());
+ child.setText(value);
+ }
+ }
+
+ public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
+ if (!isAntNamespace(namespaceURI)) {
+ throw new SAXParseException("Unexpected element \"" + qName + "\"", locator);
+ }
+
+ TaskData child = data.addProperty(qName);
+ child.setLocation(getLocation());
+ handlers.push(new TaskElemHandler(child, qName, atts));
+ }
+
+ public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
+ handlers.pop();
+ }
+ }
+
+ private static boolean isAntNamespace(String uri) {
+ return uri == null ? false : uri.equals("");
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlExporter.java b/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlExporter.java
new file mode 100644
index 000000000..1a4f12df4
--- /dev/null
+++ b/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlExporter.java
@@ -0,0 +1,45 @@
+package org.apache.tools.ant.xml;
+
+import java.io.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+
+public class XmlExporter {
+ public void exportProject(Project project, Writer out) throws IOException {
+ out.write("\n");
+
+ Iterator itr = project.getTargets().iterator();
+ while (itr.hasNext()) {
+ Target target = (Target) itr.next();
+ writeTarget(target, out);
+ }
+
+ out.write("\n");
+ }
+
+ private void writeTarget(Target target, Writer out) throws IOException {
+ out.write("\t\n");
+ out.write("\t\n");
+ }
+
+ public String concat(List depends) throws IOException {
+ StringBuffer buf = new StringBuffer();
+ Iterator itr = depends.iterator();
+ while (itr.hasNext()) {
+ String depend = (String) itr.next();
+ buf.append(depend);
+ if (itr.hasNext()) {
+ buf.append(" ");
+ }
+ }
+ return buf.toString();
+ }
+
+ public static void main(String[] args) throws Exception {
+ Workspace workspace = new Workspace(new XmlImporter());
+ Project project = workspace.importProject("ant");
+ Writer out = new OutputStreamWriter(System.out);
+ new XmlExporter().exportProject(project, out);
+ out.flush();
+ }
+}
\ No newline at end of file
diff --git a/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlImporter.java b/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlImporter.java
new file mode 100644
index 000000000..5db0ccfd2
--- /dev/null
+++ b/proposal/antfarm/xml/org/apache/tools/ant/xml/XmlImporter.java
@@ -0,0 +1,152 @@
+/* Copyright (c) 2000 The Apache Software Foundation */
+
+package org.apache.tools.ant.xml;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import javax.xml.parsers.*;
+import org.apache.tools.ant.*;
+import org.xml.sax.*;
+
+/**
+ * This class knows how to locate xml project files
+ * and import them into the workspace.
+ */
+public class XmlImporter implements Importer {
+ private URL[] path;
+
+ /**
+ * Constructs an importer for a workspace.
+ */
+ public XmlImporter() {
+ this.path = getProjectPath();
+ }
+
+ /**
+ * Imports the project with the specified name.
+ */
+ public void importProject(Project project) throws BuildException {
+ // Locate the project file
+ URLConnection conn = findProjectFile(project);
+
+ // Parse the xml
+ parseProjectFile(project, conn);
+ }
+
+ /**
+ * Find the .ant file for this project. Searches each directory and
+ * jar in the project path.
+ */
+ private URLConnection findProjectFile(Project project) throws BuildException {
+ String fileName = project.getName() + ".ant";
+ for (int i = 0; i < path.length; i++) {
+ try {
+ URL url = new URL(path[i], fileName);
+ URLConnection conn = url.openConnection();
+ conn.connect();
+ project.setBase(path[i]);
+ project.setLocation(url.toString());
+ return conn;
+ }
+ catch(FileNotFoundException exc) {
+ // The file ins't in this directory/jar, keep looking
+ }
+ catch(IOException exc) {
+ // Not sure what to do here...
+ exc.printStackTrace();
+ }
+ }
+
+ throw new BuildException("Project \"" + project.getName() + "\" not found");
+ }
+
+ /**
+ * Parse the xml file.
+ */
+ private void parseProjectFile(Project project, URLConnection conn) throws BuildException {
+ ProjectHandler handler = new ProjectHandler(project);
+
+ try {
+ InputSource source = new InputSource(conn.getInputStream());
+ source.setPublicId(conn.getURL().toString());
+ SAXParser parser = parserFactory.newSAXParser();
+ /* parser.getXMLReader().setProperty("http://xml.org/sax/properties/lexical-handler", handler); */
+ parser.parse(source, handler);
+ }
+ catch(SAXParseException exc) {
+ if (exc.getException() instanceof BuildException) {
+ throw (BuildException) exc.getException();
+ }
+
+ throw new BuildException(exc.getMessage(), exc.getPublicId() + ":" + exc.getLineNumber());
+ }
+ catch(SAXException exc) {
+ if (exc.getException() instanceof BuildException) {
+ throw (BuildException) exc.getException();
+ }
+ else {
+ throw new AntException("Parse error", exc);
+ }
+ }
+ catch(ParserConfigurationException exc) {
+ throw new AntException("Parser configuration error", exc);
+ }
+ catch(FileNotFoundException exc) {
+ // This should never happen, since conn.connect()
+ // has already been called successfully
+ throw new AntException("Project file not found", exc);
+ }
+ catch(IOException exc) {
+ throw new AntException("Error reading project file", exc);
+ }
+
+ return;
+ }
+
+ /**
+ * Parses the project path (specified using the "ant.project.path"
+ * system propertyinto URL objects.
+ */
+ private static URL[] getProjectPath() {
+ String s = System.getProperty("ant.project.path", ".");
+
+ StringTokenizer tokens = new StringTokenizer(s, System.getProperty("path.separator"));
+ int i = 0;
+ URL[] path = new URL[tokens.countTokens()];
+ while (tokens.hasMoreTokens()) {
+ String token = tokens.nextToken();
+
+ try {
+ if (token.endsWith(".jar")) {
+ path[i] = new URL("jar:file:" + token + "!/");
+ }
+ else if (token.endsWith("/")) {
+ path[i] = new URL("file:" + token);
+ }
+ else {
+ path[i] = new URL("file:" + token + "/");
+ }
+ }
+ catch(MalformedURLException exc) {
+ exc.printStackTrace();
+ }
+
+ i++;
+ }
+
+ return path;
+ }
+
+
+ /**
+ * JAXP stuff.
+ */
+ private static SAXParserFactory parserFactory;
+
+ static {
+ parserFactory = SAXParserFactory.newInstance();
+ parserFactory.setValidating(false);
+ parserFactory.setNamespaceAware(true);
+ }
+}