diff --git a/proposal/sandbox/embed/AntBean.java b/proposal/sandbox/embed/AntBean.java new file mode 100644 index 000000000..b2170c0cb --- /dev/null +++ b/proposal/sandbox/embed/AntBean.java @@ -0,0 +1,610 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 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", "Ant", 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 + * . + */ + +package org.apache.tools.ant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.PrintStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import java.util.Properties; +import java.util.Enumeration; + +/** + * A bean to use to embed ant in a project. + * + * Based on Main.java. + * + * Note: this is the result of refactoring Main. Some methods are not + * usefull for embeded use or may have better names ( I used the + * option name from Main, for consistency ). I marked them with @experimental. + * + * @experimental: the current API is not yet stable. + * + * @author duncan@x180.com + * @author Costin Manolache + * @since ant1.5 + */ +public class AntBean extends Task { + + /** The default build file name */ + public final static String DEFAULT_BUILD_FILENAME = "build.xml"; + + /** Our current message output status. Follows Project.MSG_XXX */ + private int msgOutputLevel = Project.MSG_INFO; + + /** File that we are using for configuration */ + private File buildFile; /** null */ + private String searchForThis=null; + + /** Stream that we are using for logging */ + private PrintStream out = System.out; + + /** Stream that we are using for logging error messages */ + private PrintStream err = System.err; + + /** The build targets */ + private Vector targets = new Vector(5); + + /** Set of properties that can be used by tasks */ + private Properties definedProps = new Properties(); + + /** Names of classes to add as listeners to project */ + private Vector listeners = new Vector(5); + + /** File names of property files to load on startup */ + private Vector propertyFiles = new Vector(5); + + /** + * The Ant logger class. There may be only one logger. It will have the + * right to use the 'out' PrintStream. The class must implements the BuildLogger + * interface + */ + private String loggerClassname = null; + + /** + * Indicates whether output to the log is to be unadorned. + */ + private boolean emacsMode = false; + + private ClassLoader coreLoader; + + private ProjectHelper helper=null; + + private Project newProject=null; + + private boolean redirectOutput=true; + + + public AntBean() { + } + + // -------------------- Bean properties -------------------- + // extracted from Main's command line processing code + + /** Global verbosity level + */ + public void setOutputLevel( int level ) { + msgOutputLevel=level; + } + + public void setBuildfile( String name ) { + buildFile = new File(name); + } + + /** Add a listener class name + */ + public void addListener( String s ) { + listeners.addElement(s); + } + + /** Set the logger class name ( -logger option in command + * line ). + * + * @experimental LoggerClassName would be a better name + */ + public void setLogger( String s ) { + if (loggerClassname != null) { + System.out.println("Only one logger class may be specified."); + return; + } + loggerClassname = s; + } + + /** Emacs mode for the output + */ + public void setEmacs( boolean b ) { + emacsMode = b; + } + + /** The name of the build file to execute, by + * searching in the filesystem. + */ + public void setFind( String s ) { + if (s==null) { + searchForThis = s; + } else { + searchForThis = DEFAULT_BUILD_FILENAME; + } + } + + /** Same as -propertyfile + */ + public void addPropertyfile( String s ) { + propertyFiles.addElement(s); + } + + /** Set the core loader, to be used to execute. + */ + public void setCoreLoader( ClassLoader coreLoader ) { + coreLoader=coreLoader; + } + + /** Add a user-defined property + */ + public void setUserProperty( String name, String value ) { + definedProps.put( name, value ); + } + + + /** Add a target to be executed + */ + public void addTarget(String arg ) { + targets.addElement(arg); + } + + /** Log file. It'll redirect the System output and logs to this + * file. Supported by -logfile argument in ant - probably + * a bad idea if you embed ant in an application. + * + * @experimental - I don't think it's a good idea. + */ + public void setLogfile( String name ) { + try { + File logFile = new File(name); + out = new PrintStream(new FileOutputStream(logFile)); + err = out; + System.setOut(out); + System.setErr(out); + } catch (IOException ioe) { + String msg = "Cannot write on the specified log file. " + + "Make sure the path exists and you have write permissions."; + System.out.println(msg); + return; + } + } + + /** Redirect the output and set a security manager before + * executing ant. Defaults to true for backward comptibility, + * you should set it to false if you embed ant. + */ + public void setRedirectOutput( boolean b ) { + redirectOutput=b; + } + + // -------------------- Property getters -------------------- + + + /** Return the build file. If it was not explicitely specified, search + * for it in the parent directories + * + *

Takes the "find" property as a suffix to append to each + * parent directory in seach of a build file. Once the + * root of the file-system has been reached an exception + * is thrown. + */ + public File getBuildFile() + throws BuildException + { + // if buildFile was not specified on the command line, + if (buildFile == null) { + // but -find then search for it + if (searchForThis != null) { + buildFile = findBuildFile(System.getProperty("user.dir"), + searchForThis); + } else { + buildFile = new File(DEFAULT_BUILD_FILENAME); + } + } + getProject().setUserProperty("ant.file" , buildFile.getAbsolutePath() ); + return buildFile; + } + + /** Return an (initialized) project constructed using the current + * settings. + * This will not load the build.xml file - you can 'load' the + * project object with tasks manually or execute 'standalone' + * tasks in the context of the project. + */ + public Project getProject() { + if( newProject!=null ) + return newProject; + loadProperties(); + + helper=ProjectHelper.getProjectHelper(); + newProject = helper.createProject(coreLoader); + newProject.setCoreLoader(coreLoader); + + addBuildListeners(newProject); + + newProject.fireBuildStarted(); + + newProject.init(); + newProject.setUserProperty("ant.version", getAntVersion()); + + // set user-define properties + Enumeration e = definedProps.keys(); + while (e.hasMoreElements()) { + String arg = (String)e.nextElement(); + String value = (String)definedProps.get(arg); + newProject.setUserProperty(arg, value); + } + + return newProject; + } + + private static String antVersion = null; + + /** @experimental + * Ant version should be combined with the ProjectHelper version and type, + * since it'll determine the set of features supported by ant ( at the xml + * level ). + */ + public static synchronized String getAntVersion() throws BuildException { + if (antVersion == null) { + try { + Properties props = new Properties(); + InputStream in = + Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt"); + props.load(in); + in.close(); + + String lSep = System.getProperty("line.separator"); + StringBuffer msg = new StringBuffer(); + msg.append("Apache Ant version "); + msg.append(props.getProperty("VERSION")); + msg.append(" compiled on "); + msg.append(props.getProperty("DATE")); + antVersion = msg.toString(); + } catch (IOException ioe) { + throw new BuildException("Could not load the version information:" + + ioe.getMessage()); + } catch (NullPointerException npe) { + throw new BuildException("Could not load the version information."); + } + } + return antVersion; + } + + + + // -------------------- Bean methods -------------------- + Throwable error = null; + + + /** Clean up allocated resources and finish the processing of the + * current Project. + */ + public void done() { + newProject.fireBuildFinished(error); + } + + + /** + * Process an XML file and execute the targets. + * + * This method can be called multiple times, eventually after setting different + * build file and different targets - all executions will happen in the + * same execution context ( project ). + */ + public void processBuildXml() throws BuildException { + checkBuildFile(); + File buildFile=getBuildFile(); + Project newProject=getProject(); + + // first use the ProjectHelper to create the project object + // from the given build file. + String noParserMessage = + "No JAXP compliant XML parser found. Please visit http://xml.apache.org for a suitable parser"; + try { + Class.forName("javax.xml.parsers.SAXParserFactory"); + helper.parse(newProject, buildFile); + } catch (NoClassDefFoundError ncdfe) { + throw new BuildException(noParserMessage, ncdfe); + } catch (ClassNotFoundException cnfe) { + throw new BuildException(noParserMessage, cnfe); + } catch (NullPointerException npe) { + throw new BuildException(noParserMessage, npe); + } + + // make sure that we have a target to execute + if (targets.size() == 0) { + targets.addElement(newProject.getDefaultTarget()); + } + + newProject.executeTargets(targets); + } + + public void execute() throws BuildException { + + try { + if( redirectOutput ) { + pushSystemOut(); + } + + processBuildXml(); + } catch(RuntimeException exc) { + error = exc; + throw exc; + } catch(Error err) { + error = err; + throw err; + } finally { + done(); + if( redirectOutput ) + popSystemOut(); + } + } + + // -------------------- Private methods -------------------- + + private void checkBuildFile() throws BuildException { + File buildFile=getBuildFile(); + + // make sure buildfile exists + if (!buildFile.exists()) { + System.out.println("Buildfile: " + buildFile + " does not exist!"); + throw new BuildException("Build failed"); + } + + // make sure it's not a directory (this falls into the ultra + // paranoid lets check everything catagory + if (buildFile.isDirectory()) { + System.out.println("What? Buildfile: " + buildFile + " is a dir!"); + throw new BuildException("Build failed"); + } + + // track when we started + if (msgOutputLevel >= Project.MSG_INFO) { + System.out.println("Buildfile: " + buildFile); + } + } + + private PrintStream oldErr=null; + private PrintStream oldOut=null; + private SecurityManager oldsm = null; + + private void pushSystemOut() { + oldErr = System.err; + oldOut = System.out; + + // use a system manager that prevents from System.exit() + // only in JDK > 1.1 + if ( !Project.JAVA_1_0.equals(Project.getJavaVersion()) && + !Project.JAVA_1_1.equals(Project.getJavaVersion()) ){ + oldsm = System.getSecurityManager(); + + //SecurityManager can not be installed here for backwards + //compatability reasons (PD). Needs to be loaded prior to + //ant class if we are going to implement it. + //System.setSecurityManager(new NoExitSecurityManager()); + } + System.setOut(new PrintStream(new DemuxOutputStream(getProject(), false))); + System.setErr(new PrintStream(new DemuxOutputStream(getProject(), true))); + } + + private void popSystemOut() { + // put back the original security manager + //The following will never eval to true. (PD) + if (oldsm != null){ + System.setSecurityManager(oldsm); + } + + if( oldOut!=null && oldErr!=null ) { + System.setOut(oldOut); + System.setErr(oldErr); + } + } + + protected void addBuildListeners(Project newProject) { + + // Add the default listener + newProject.addBuildListener(createLogger()); + + for (int i = 0; i < listeners.size(); i++) { + String className = (String) listeners.elementAt(i); + try { + BuildListener listener = + (BuildListener) Class.forName(className).newInstance(); + newProject.addBuildListener(listener); + } + catch(Throwable exc) { + throw new BuildException("Unable to instantiate listener " + className, exc); + } + } + } + + /** + * Creates the default build logger for sending build events to the ant log. + */ + protected BuildLogger createLogger() { + BuildLogger logger = null; + if (loggerClassname != null) { + try { + logger = (BuildLogger)(Class.forName(loggerClassname).newInstance()); + } catch (ClassCastException e) { + System.err.println("The specified logger class " + loggerClassname + + " does not implement the BuildLogger interface"); + throw new RuntimeException(); + } catch (Exception e) { + System.err.println("Unable to instantiate specified logger class " + + loggerClassname + " : " + e.getClass().getName()); + throw new RuntimeException(); + } + } + else { + logger = new DefaultLogger(); + } + + logger.setMessageOutputLevel(msgOutputLevel); + logger.setOutputPrintStream(out); + logger.setErrorPrintStream(err); + logger.setEmacsMode(emacsMode); + + return logger; + } + + /** Load all propertyFiles + */ + private void loadProperties() + { + // Load the property files specified by -propertyfile + for (int propertyFileIndex=0; + propertyFileIndex < propertyFiles.size(); + propertyFileIndex++) { + String filename = (String) propertyFiles.elementAt(propertyFileIndex); + Properties props = new Properties(); + FileInputStream fis = null; + try { + fis = new FileInputStream(filename); + props.load(fis); + } + catch (IOException e) { + System.out.println("Could not load property file " + + filename + ": " + e.getMessage()); + } finally { + if (fis != null){ + try { + fis.close(); + } catch (IOException e){ + } + } + } + + // ensure that -D properties take precedence + Enumeration propertyNames = props.propertyNames(); + while (propertyNames.hasMoreElements()) { + String name = (String) propertyNames.nextElement(); + if (definedProps.getProperty(name) == null) { + definedProps.put(name, props.getProperty(name)); + } + } + } + } + + // -------------------- XXX Move to FileUtil -------------------- + + /** + * Helper to get the parent file for a given file. + * + *

Added to simulate File.getParentFile() from JDK 1.2. + * + * @param file File + * @return Parent file or null if none + */ + private File getParentFile(File file) { + String filename = file.getAbsolutePath(); + file = new File(filename); + filename = file.getParent(); + + if (filename != null && msgOutputLevel >= Project.MSG_VERBOSE) { + System.out.println("Searching in "+filename); + } + + return (filename == null) ? null : new File(filename); + } + + /** + * Search parent directories for the build file. + * + *

Takes the given target as a suffix to append to each + * parent directory in seach of a build file. Once the + * root of the file-system has been reached an exception + * is thrown. + * + * @param suffix Suffix filename to look for in parents. + * @return A handle to the build file + * + * @exception BuildException Failed to locate a build file + */ + private File findBuildFile(String start, String suffix) throws BuildException { + if (msgOutputLevel >= Project.MSG_INFO) { + System.out.println("Searching for " + suffix + " ..."); + } + + File parent = new File(new File(start).getAbsolutePath()); + File file = new File(parent, suffix); + + // check if the target file exists in the current directory + while (!file.exists()) { + // change to parent directory + parent = getParentFile(parent); + + // if parent is null, then we are at the root of the fs, + // complain that we can't find the build file. + if (parent == null) { + throw new BuildException("Could not locate a build file!"); + } + + // refresh our file handle + file = new File(parent, suffix); + } + + return file; + } + +} diff --git a/proposal/sandbox/embed/Main.java b/proposal/sandbox/embed/Main.java new file mode 100644 index 000000000..cc62e0997 --- /dev/null +++ b/proposal/sandbox/embed/Main.java @@ -0,0 +1,422 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 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", "Ant", 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 + * . + */ + +package org.apache.tools.ant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.PrintStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Vector; +import java.util.Properties; +import java.util.Enumeration; + +/** + * Command line entry point into Ant. This class is entered via the + * cannonical "public static void main" entry point and reads the + * command line arguments. It then assembles and executes an Ant + * project. + *

+ * If you integrating Ant into some other tool, this is not the class + * to use as an entry point. Please see the source code of this + * class to see how it manipulates the Ant project classes. + * + * @author duncan@x180.com + */ +public class Main { + + private AntBean ant=new AntBean(); + + /** + * Indicates if this ant should be run. + */ + private boolean readyToRun = false; + + /** + * Indicates we should only parse and display the project help information + */ + private boolean projectHelp = false; + + /** + * Prints the message of the Throwable if it's not null. + */ + private static void printMessage(Throwable t) { + String message = t.getMessage(); + if (message != null) { + System.err.println(message); + } + } + + /** + * Entry point method. + */ + public static void start(String[] args, Properties additionalUserProperties, + ClassLoader coreLoader) { + Main m = null; + + try { + m = new Main(args); + } catch(Throwable exc) { + printMessage(exc); + System.exit(1); + } + AntBean ant=m.ant; + ant.setCoreLoader( coreLoader ); + + if (additionalUserProperties != null) { + for (Enumeration e = additionalUserProperties.keys(); e.hasMoreElements(); ) { + String key = (String) e.nextElement(); + String property = additionalUserProperties.getProperty(key); + ant.setUserProperty(key, property); + } + } + + try { + Project project=ant.getProject(); + + ant.execute(); + + System.exit(0); + } catch (BuildException be) { + // ?? What is that, and how should it be implemented + // XXX if (m.err != System.err) { + printMessage(be); + //} + System.exit(1); + } catch(Throwable exc) { + exc.printStackTrace(); + printMessage(exc); + System.exit(1); + } + } + + + + /** + * Command line entry point. This method kicks off the building + * of a project object and executes a build using either a given + * target or the default target. + * + * @param args Command line args. + */ + public static void main(String[] args) { + start(args, null, null); + } + + protected Main(String[] args) throws BuildException { + + String searchForThis = null; + + // cycle through given args + + for (int i = 0; i < args.length; i++) { + String arg = args[i]; + + if (arg.equals("-help")) { + printUsage(); + return; + } else if (arg.equals("-version")) { + printVersion(); + return; + } else if (arg.equals("-quiet") || arg.equals("-q")) { + ant.setOutputLevel( Project.MSG_WARN ); + } else if (arg.equals("-verbose") || arg.equals("-v")) { + printVersion(); + ant.setOutputLevel( Project.MSG_VERBOSE ); + } else if (arg.equals("-debug")) { + printVersion(); + ant.setOutputLevel( Project.MSG_DEBUG ); + } else if (arg.equals("-logfile") || arg.equals("-l")) { + try { + ant.setLogfile( args[i+1] ); + i++; + } catch (ArrayIndexOutOfBoundsException aioobe) { + String msg = "You must specify a log file when " + + "using the -log argument"; + System.out.println(msg); + return; + } + } else if (arg.equals("-buildfile") || arg.equals("-file") || arg.equals("-f")) { + try { + ant.setBuildfile( args[i+1] ); + i++; + } catch (ArrayIndexOutOfBoundsException aioobe) { + String msg = "You must specify a buildfile when " + + "using the -buildfile argument"; + System.out.println(msg); + return; + } + } else if (arg.equals("-listener")) { + try { + ant.addListener(args[i+1]); + i++; + } catch (ArrayIndexOutOfBoundsException aioobe) { + String msg = "You must specify a classname when " + + "using the -listener argument"; + System.out.println(msg); + return; + } + } else if (arg.startsWith("-D")) { + + /* Interestingly enough, we get to here when a user + * uses -Dname=value. However, in some cases, the JDK + * goes ahead * and parses this out to args + * {"-Dname", "value"} + * so instead of parsing on "=", we just make the "-D" + * characters go away and skip one argument forward. + * + * I don't know how to predict when the JDK is going + * to help or not, so we simply look for the equals sign. + */ + + String name = arg.substring(2, arg.length()); + String value = null; + int posEq = name.indexOf("="); + if (posEq > 0) { + value = name.substring(posEq+1); + name = name.substring(0, posEq); + } else if (i < args.length-1) { + value = args[++i]; + } + + ant.setUserProperty(name, value); + + } else if (arg.equals("-logger")) { + try { + ant.setLogger( args[++i] ); + } catch (ArrayIndexOutOfBoundsException aioobe) { + System.out.println("You must specify a classname when " + + "using the -logger argument"); + return; + } + } else if (arg.equals("-emacs")) { + ant.setEmacs( true ); + } else if (arg.equals("-projecthelp")) { + // set the flag to display the targets and quit + projectHelp = true; + } else if (arg.equals("-find")) { + // eat up next arg if present, default to build.xml + if (i < args.length-1) { + ant.setFind( args[++i] ); + } else { + ant.setFind( null ); + } + } else if (arg.startsWith("-propertyfile")) { + try { + ant.addPropertyfile( args[i+1] ); + i++; + } catch (ArrayIndexOutOfBoundsException aioobe) { + String msg = "You must specify a property filename when " + + "using the -propertyfile argument"; + System.out.println(msg); + return; + } + } else if (arg.startsWith("-")) { + // we don't have any more args to recognize! + String msg = "Unknown argument: " + arg; + System.out.println(msg); + printUsage(); + return; + } else { + // if it's no other arg, it may be the target + ant.addTarget(arg); + } + } + + readyToRun = true; + } + + + protected void addBuildListeners(Project project) { + ant.addBuildListeners( project ); + } + + /** + * Prints the usage of how to use this class to System.out + */ + private static void printUsage() { + String lSep = System.getProperty("line.separator"); + StringBuffer msg = new StringBuffer(); + msg.append("ant [options] [target [target2 [target3] ...]]" + lSep); + msg.append("Options: " + lSep); + msg.append(" -help print this message" + lSep); + msg.append(" -projecthelp print project help information" + lSep); + msg.append(" -version print the version information and exit" + lSep); + msg.append(" -quiet be extra quiet" + lSep); + msg.append(" -verbose be extra verbose" + lSep); + msg.append(" -debug print debugging information" + lSep); + msg.append(" -emacs produce logging information without adornments" + lSep); + msg.append(" -logfile use given file for log" + lSep); + msg.append(" -logger the class which is to perform logging" + lSep); + msg.append(" -listener add an instance of class as a project listener" + lSep); + msg.append(" -buildfile use given buildfile" + lSep); + msg.append(" -D= use value for given property" + lSep); + msg.append(" -propertyfile load all properties from file with -D" + lSep); + msg.append(" properties taking precedence" + lSep); + msg.append(" -find search for buildfile towards the root of the" + lSep); + msg.append(" filesystem and use it" + lSep); + System.out.println(msg.toString()); + } + + private static void printVersion() throws BuildException { + System.out.println(AntBean.getAntVersion()); + } + + public static synchronized String getAntVersion() throws BuildException { + return AntBean.getAntVersion(); + } + + /** + * Print the project description, if any + */ + private static void printDescription(Project project) { + if (project.getDescription() != null) { + System.out.println(project.getDescription()); + } + } + + /** + * Print out a list of all targets in the current buildfile + */ + private static void printTargets(Project project, boolean printSubTargets) { + // find the target with the longest name + int maxLength = 0; + Enumeration ptargets = project.getTargets().elements(); + String targetName; + String targetDescription; + Target currentTarget; + // split the targets in top-level and sub-targets depending + // on the presence of a description + Vector topNames = new Vector(); + Vector topDescriptions = new Vector(); + Vector subNames = new Vector(); + + while (ptargets.hasMoreElements()) { + currentTarget = (Target)ptargets.nextElement(); + targetName = currentTarget.getName(); + targetDescription = currentTarget.getDescription(); + // maintain a sorted list of targets + if (targetDescription == null) { + int pos = findTargetPosition(subNames, targetName); + subNames.insertElementAt(targetName, pos); + } else { + int pos = findTargetPosition(topNames, targetName); + topNames.insertElementAt(targetName, pos); + topDescriptions.insertElementAt(targetDescription, pos); + if (targetName.length() > maxLength) { + maxLength = targetName.length(); + } + } + } + + printTargets(topNames, topDescriptions, "Main targets:", maxLength); + + if( printSubTargets ) { + printTargets(subNames, null, "Subtargets:", 0); + } + + String defaultTarget = project.getDefaultTarget(); + if (defaultTarget != null && !"".equals(defaultTarget)) { // shouldn't need to check but... + System.out.println( "Default target: " + defaultTarget ); + } + } + + /** + * Search for the insert position to keep names a sorted list of Strings + */ + private static int findTargetPosition(Vector names, String name) { + int res = names.size(); + for (int i=0; i. + */ + +package org.apache.tools.ant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Locale; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.xml.sax.AttributeList; + + +/** + * Configures a Project (complete with Targets and Tasks) based on + * a XML build file. It'll rely on a plugin to do the actual processing + * of the xml file. + * + * This class also provide static wrappers for common introspection. + * + * All helper plugins must provide backward compatiblity with the + * original ant patterns, unless a different behavior is explicitely + * specified. For example, if namespace is used on the tag + * the helper can expect the entire build file to be namespace-enabled. + * Namespaces or helper-specific tags can provide meta-information to + * the helper, allowing it to use new ( or different policies ). + * + * However, if no namespace is used the behavior should be exactly + * identical with the default helper. + * + * @author duncan@x180.com + */ +public /*abstract*/ class ProjectHelper { + + /** + * Configures the Project with the contents of the specified XML file. + * ( should it be deprecated ? Using getProjectHelper(), parse() + * is cleaner ) + */ + public static void configureProject(Project project, File buildFile) + throws BuildException + { + ProjectHelper helper=ProjectHelper.getProjectHelper(); + helper.parse(project, buildFile); + } + + public ProjectHelper() { + } + + /** + * Constructs a new Ant parser for the specified XML file. + * @deprecated Use the plugin mechanism instead. + */ + private ProjectHelper(Project project, File buildFile) { + // this.project = project; + // this.buildFile = new File(buildFile.getAbsolutePath()); + // buildFileParent = new File(this.buildFile.getParent()); + } + + public Project createProject(ClassLoader coreLoader) { + return new Project(); + } + + /** + * Process an input source for the project. + * + * All processors must support at least File sources. It is usefull to also support + * InputSource - this allows the input to come from a non-filesystem source + * (like input stream of a POST, or a soap body ). + */ + public /*abstract*/ void parse(Project project, Object source) + throws BuildException + { + throw new BuildException("You must use a real ProjectHelper implementation"); + } + + /* -------------------- Helper discovery -------------------- */ + public static final String HELPER_PROPERTY = + "org.apache.tools.ant.ProjectHelper"; + + public static final String SERVICE_ID = + "/META-INF/services/org.apache.tools.ant.ProjectHelper"; + + + /** Discover a project helper instance. + */ + public static ProjectHelper getProjectHelper() + throws BuildException + { + // Identify the class loader we will be using. Ant may be + // in a webapp or embeded in a different app + ClassLoader classLoader = getContextClassLoader(); + ProjectHelper helper=null; + + // First, try the system property + try { + String helperClass = System.getProperty(HELPER_PROPERTY); + if (helperClass != null) { + helper = newHelper(helperClass, classLoader); + } + } catch (SecurityException e) { + // It's ok, we'll try next option + ; + } + + // A JDK1.3 'service' ( like in JAXP ). That will plug a helper + // automatically if in CLASSPATH, with the right META-INF/services. + if( helper==null ) { + try { + InputStream is=null; + if (classLoader == null) { + is=ClassLoader.getSystemResourceAsStream( SERVICE_ID ); + } else { + is=classLoader.getResourceAsStream( SERVICE_ID ); + } + + if( is != null ) { + // This code is needed by EBCDIC and other strange systems. + // It's a fix for bugs reported in xerces + BufferedReader rd; + try { + rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); + } catch (java.io.UnsupportedEncodingException e) { + rd = new BufferedReader(new InputStreamReader(is)); + } + + String helperClassName = rd.readLine(); + rd.close(); + + if (helperClassName != null && + ! "".equals(helperClassName)) { + + helper= newHelper( helperClassName, classLoader ); + } + } + } catch( Exception ex ) { + ; + } + } + + // Default + return new ProjectHelperImpl(); + } + + private static ProjectHelper newHelper(String helperClass, + ClassLoader classLoader) + throws BuildException + { + + try { + Class clazz = null; + if (classLoader == null) { + clazz = Class.forName(helperClass); + } else { + clazz = classLoader.loadClass(helperClass); + } + return ((ProjectHelper) clazz.newInstance()); + } catch (Exception e) { + throw new BuildException(e); + } + } + + /** + */ + public static ClassLoader getContextClassLoader() + throws BuildException + { + // Are we running on a JDK 1.2 or later system? + Method method = null; + try { + method = Thread.class.getMethod("getContextClassLoader", null); + } catch (NoSuchMethodException e) { + // we are running on JDK 1.1 + return null; + } + + // Get the thread context class loader (if there is one) + ClassLoader classLoader = null; + try { + classLoader = (ClassLoader) + method.invoke(Thread.currentThread(), null); + } catch (IllegalAccessException e) { + throw new BuildException + ("Unexpected IllegalAccessException", e); + } catch (InvocationTargetException e) { + throw new BuildException + ("Unexpected InvocationTargetException", e); + } + + // Return the selected class loader + return (classLoader); + } + + + /* -------------------- Common utilities and wrappers -------------------- */ + + /** Configure a java object using ant's rules. + */ + public static void configure(Object target, AttributeList attrs, + Project project) throws BuildException { + TaskAdapter adapter=null; + if( target instanceof TaskAdapter ) { + adapter=(TaskAdapter)target; + target=adapter.getProxy(); + } + + IntrospectionHelper ih = + IntrospectionHelper.getHelper(target.getClass()); + if( adapter != null ) + adapter.setIntrospectionHelper( ih ); + + // XXX What's that ? + project.addBuildListener(ih); + + for (int i = 0; i < attrs.getLength(); i++) { + // reflect these into the target + String value=replaceProperties(project, attrs.getValue(i), + project.getProperties() ); + String name=attrs.getName(i).toLowerCase(Locale.US); + try { + if (adapter!=null ) { + adapter.setAttribute( name, value ); + } else { + ih.setAttribute(project, target, + name, value); + } + } catch (BuildException be) { + // id attribute must be set externally + // XXX Shuldn't it be 'name' ( i.e. lower-cased ) ? + if (!attrs.getName(i).equals("id")) { + throw be; + } + } + } + } + + /** + * Adds the content of #PCDATA sections to an element. + */ + public static void addText(Project project, Object target, char[] buf, int start, int end) + throws BuildException { + addText(project, target, new String(buf, start, end)); + } + + /** + * Adds the content of #PCDATA sections to an element. + */ + public static void addText(Project project, Object target, String text) + throws BuildException { + + if (text == null ) { + return; + } + + if(target instanceof TaskAdapter) { + target = ((TaskAdapter) target).getProxy(); + } + + IntrospectionHelper.getHelper(target.getClass()).addText(project, target, text); + } + + /** + * Stores a configured child element into its parent object + */ + public static void storeChild(Project project, Object parent, Object child, String tag) { + IntrospectionHelper ih = IntrospectionHelper.getHelper(parent.getClass()); + ih.storeElement(project, parent, child, tag); + } + + /** + * Replace ${} style constructions in the given value with the string value of + * the corresponding data types. + * + * @param value the string to be scanned for property references. + * @since 1.5 + */ + public static String replaceProperties(Project project, String value) + throws BuildException { + return project.replaceProperties(value); + } + + /** + * Replace ${} style constructions in the given value with the string value of + * the corresponding data types. + * + * @param value the string to be scanned for property references. + */ + public static String replaceProperties(Project project, String value, Hashtable keys) + throws BuildException { + if (value == null) { + return null; + } + + Vector fragments = new Vector(); + Vector propertyRefs = new Vector(); + parsePropertyString(value, fragments, propertyRefs); + + StringBuffer sb = new StringBuffer(); + Enumeration i = fragments.elements(); + Enumeration j = propertyRefs.elements(); + while (i.hasMoreElements()) { + String fragment = (String)i.nextElement(); + if (fragment == null) { + String propertyName = (String)j.nextElement(); + if (!keys.containsKey(propertyName)) { + project.log("Property ${" + propertyName + "} has not been set", Project.MSG_VERBOSE); + } + fragment = (keys.containsKey(propertyName)) ? (String) keys.get(propertyName) + : "${" + propertyName + "}"; + } + sb.append(fragment); + } + + return sb.toString(); + } + + /** + * This method will parse a string containing ${value} style + * property values into two lists. The first list is a collection + * of text fragments, while the other is a set of string property names + * null entries in the first list indicate a property reference from the + * second list. + */ + public static void parsePropertyString(String value, Vector fragments, Vector propertyRefs) + throws BuildException { + int prev = 0; + int pos; + while ((pos = value.indexOf("$", prev)) >= 0) { + if (pos > 0) { + fragments.addElement(value.substring(prev, pos)); + } + + if( pos == (value.length() - 1)) { + fragments.addElement("$"); + prev = pos + 1; + } + else if (value.charAt(pos + 1) != '{' ) { + fragments.addElement(value.substring(pos + 1, pos + 2)); + prev = pos + 2; + } else { + int endName = value.indexOf('}', pos); + if (endName < 0) { + throw new BuildException("Syntax error in property: " + + value ); + } + String propertyName = value.substring(pos + 2, endName); + fragments.addElement(null); + propertyRefs.addElement(propertyName); + prev = endName + 1; + } + } + + if (prev < value.length()) { + fragments.addElement(value.substring(prev)); + } + } + +} diff --git a/proposal/sandbox/embed/ProjectHelperImpl.java b/proposal/sandbox/embed/ProjectHelperImpl.java new file mode 100644 index 000000000..a825a687a --- /dev/null +++ b/proposal/sandbox/embed/ProjectHelperImpl.java @@ -0,0 +1,750 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 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", "Ant", 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 + * . + */ + +package org.apache.tools.ant; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.Hashtable; +import java.util.Vector; +import java.util.Enumeration; +import java.util.Locale; +import org.xml.sax.Locator; +import org.xml.sax.InputSource; +import org.xml.sax.HandlerBase; +import org.xml.sax.SAXParseException; +import org.xml.sax.SAXException; +import org.xml.sax.DocumentHandler; +import org.xml.sax.AttributeList; +import org.xml.sax.helpers.XMLReaderAdapter; + +import javax.xml.parsers.SAXParserFactory; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.ParserConfigurationException; + +// Note: Use of local classes can create problems with various compilers +// like gcc or even jikes. In addition it makes the code harder to read +// for many beginners ( at least for me - Costin ). +// I changed the code to avoid the tricks that the compiler does, now +// jikes works ( for me ). Note that declaring the classes 'private' +// is probably overriden by the compiler - a feature of the internal class impl. + +/** + * "Original" implementation of the project helper. Or at least + * what is present in ant1.4. + * + * @author duncan@x180.com + */ +public class ProjectHelperImpl extends ProjectHelper { + private static SAXParserFactory parserFactory = null; + + protected Project project; + protected Object source; + protected File buildFile; + protected File buildFileParent; + private org.xml.sax.Parser parser; + private Locator locator; + + /** Return a handler for project. This can be used by xml helpers to fallback to + the original behavior ( non-namespace aware ). + When the callback is received, if no namespaces are used ( or no + new attributes, etc ) then the easiest way to achieve backward compatibility + is to use the original. + + A helper needs to call this method, which will switch the HandlerBase in + the SAX parser and return it to the original on + + @experimental This is likely to change + */ + public HandlerBase defaultProjectHandler( Project project, + org.xml.sax.Parser parser, + String tag, AttributeList attrs, + DocumentHandler parent ) + throws SAXParseException + { + this.project=project; + this.parser=parser; + ProjectHandler h=new ProjectHandler(this, parent); + h.init( tag, attrs ); + return h; + } + + /** + * Parses the project file. + */ + public void parse(Project project, Object source) throws BuildException { + if( ! (source instanceof File) ) + throw new BuildException( "Only File source is supported by the default helper"); + + File buildFile=(File)source; + this.project = project; + this.buildFile = new File(buildFile.getAbsolutePath()); + buildFileParent = new File(this.buildFile.getParent()); + + FileInputStream inputStream = null; + InputSource inputSource = null; + + try { + SAXParser saxParser = getParserFactory().newSAXParser(); + try { + parser = saxParser.getParser(); + } catch (SAXException exc) { + parser = new XMLReaderAdapter(saxParser.getXMLReader()); + } + + String uri = "file:" + buildFile.getAbsolutePath().replace('\\', '/'); + for (int index = uri.indexOf('#'); index != -1; index = uri.indexOf('#')) { + uri = uri.substring(0, index) + "%23" + uri.substring(index+1); + } + + inputStream = new FileInputStream(buildFile); + inputSource = new InputSource(inputStream); + inputSource.setSystemId(uri); + project.log("parsing buildfile " + buildFile + " with URI = " + uri, Project.MSG_VERBOSE); + + HandlerBase hb = new RootHandler(this); + parser.setDocumentHandler(hb); + parser.setEntityResolver(hb); + parser.setErrorHandler(hb); + parser.setDTDHandler(hb); + parser.parse(inputSource); + } + catch(ParserConfigurationException exc) { + throw new BuildException("Parser has not been configured correctly", exc); + } + catch(SAXParseException exc) { + Location location = + new Location(buildFile.toString(), exc.getLineNumber(), exc.getColumnNumber()); + + Throwable t = exc.getException(); + if (t instanceof BuildException) { + BuildException be = (BuildException) t; + if (be.getLocation() == Location.UNKNOWN_LOCATION) { + be.setLocation(location); + } + throw be; + } + + throw new BuildException(exc.getMessage(), t, location); + } + catch(SAXException exc) { + Throwable t = exc.getException(); + if (t instanceof BuildException) { + throw (BuildException) t; + } + throw new BuildException(exc.getMessage(), t); + } + catch(FileNotFoundException exc) { + throw new BuildException(exc); + } + catch(IOException exc) { + throw new BuildException("Error reading project file", exc); + } + finally { + if (inputStream != null) { + try { + inputStream.close(); + } + catch (IOException ioe) { + // ignore this + } + } + } + } + + /** + * The common superclass for all sax event handlers in Ant. Basically + * throws an exception in each method, so subclasses should override + * what they can handle. + * + * Each type of xml element (task, target, etc) in ant will + * have its own subclass of AbstractHandler. + * + * In the constructor, this class takes over the handling of sax + * events from the parent handler, and returns + * control back to the parent in the endElement method. + */ + private static class AbstractHandler extends HandlerBase { + protected DocumentHandler parentHandler; + protected ProjectHelperImpl helper; + + public AbstractHandler(ProjectHelperImpl helper, DocumentHandler parentHandler) { + this.parentHandler = parentHandler; + this.helper=helper; + + // Start handling SAX events + helper.parser.setDocumentHandler(this); + } + + public void startElement(String tag, AttributeList attrs) throws SAXParseException { + throw new SAXParseException("Unexpected element \"" + tag + "\"", helper.locator); + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + String s = new String(buf, start, end).trim(); + + if (s.length() > 0) { + throw new SAXParseException("Unexpected text \"" + s + "\"", helper.locator); + } + } + + /** + * Called when this element and all elements nested into it have been + * handled. + */ + protected void finished() {} + + public void endElement(String name) throws SAXException { + + finished(); + // Let parent resume handling SAX events + helper.parser.setDocumentHandler(parentHandler); + } + } + + /** + * Handler for the root element. It's only child must be the "project" element. + */ + private static class RootHandler extends HandlerBase { + private ProjectHelperImpl helper; + + public RootHandler( ProjectHelperImpl helper ) { + this.helper=helper; + } + + /** + * resolve file: URIs as relative to the build file. + */ + public InputSource resolveEntity(String publicId, + String systemId) { + + helper.project.log("resolving systemId: " + systemId, Project.MSG_VERBOSE); + + if (systemId.startsWith("file:")) { + String path = systemId.substring(5); + int index = path.indexOf("file:"); + + // we only have to handle these for backward compatibility + // since they are in the FAQ. + while (index != -1) { + path = path.substring(0, index) + path.substring(index + 5); + index = path.indexOf("file:"); + } + + String entitySystemId = path; + index = path.indexOf("%23"); + // convert these to # + while (index != -1) { + path = path.substring(0, index) + "#" + path.substring(index + 3); + index = path.indexOf("%23"); + } + + File file = new File(path); + if (!file.isAbsolute()) { + file = new File(helper.buildFileParent, path); + } + + try { + InputSource inputSource = new InputSource(new FileInputStream(file)); + inputSource.setSystemId("file:" + entitySystemId); + return inputSource; + } catch (FileNotFoundException fne) { + helper.project.log(file.getAbsolutePath()+" could not be found", + Project.MSG_WARN); + } + } + // use default if not file or file not found + return null; + } + + public void startElement(String tag, AttributeList attrs) throws SAXParseException { + if (tag.equals("project")) { + new ProjectHandler(helper, this).init(tag, attrs); + } else { + throw new SAXParseException("Config file is not of expected XML type", helper.locator); + } + } + + public void setDocumentLocator(Locator locator) { + helper.locator = locator; + } + } + + /** + * Handler for the top level "project" element. + */ + private static class ProjectHandler extends AbstractHandler { + public ProjectHandler(ProjectHelperImpl helper, DocumentHandler parentHandler) { + super(helper, parentHandler); + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + String def = null; + String name = null; + String id = null; + String baseDir = null; + + for (int i = 0; i < attrs.getLength(); i++) { + String key = attrs.getName(i); + String value = attrs.getValue(i); + + if (key.equals("default")) { + def = value; + } else if (key.equals("name")) { + name = value; + } else if (key.equals("id")) { + id = value; + } else if (key.equals("basedir")) { + baseDir = value; + } else { + throw new SAXParseException("Unexpected attribute \"" + attrs.getName(i) + "\"", helper.locator); + } + } + + if (def == null) { + throw new SAXParseException("The default attribute of project is required", + helper.locator); + } + + + helper.project.setDefaultTarget(def); + + if (name != null) { + helper.project.setName(name); + helper.project.addReference(name, helper.project); + } + + if (id != null) { + helper.project.addReference(id, helper.project); + } + + if (helper.project.getProperty("basedir") != null) { + helper.project.setBasedir(helper.project.getProperty("basedir")); + } else { + if (baseDir == null) { + helper.project.setBasedir(helper.buildFileParent.getAbsolutePath()); + } else { + // check whether the user has specified an absolute path + if ((new File(baseDir)).isAbsolute()) { + helper.project.setBasedir(baseDir); + } else { + helper.project.setBaseDir(helper.project.resolveFile(baseDir, helper.buildFileParent)); + } + } + } + + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (name.equals("taskdef")) { + handleTaskdef(name, attrs); + } else if (name.equals("typedef")) { + handleTypedef(name, attrs); + } else if (name.equals("property")) { + handleProperty(name, attrs); + } else if (name.equals("target")) { + handleTarget(name, attrs); + } else if (helper.project.getDataTypeDefinitions().get(name) != null) { + handleDataType(name, attrs); + } else { + throw new SAXParseException("Unexpected element \"" + name + "\"", helper.locator); + } + } + + private void handleTaskdef(String name, AttributeList attrs) throws SAXParseException { + (new TaskHandler(helper, this, null, null, null)).init(name, attrs); + } + + private void handleTypedef(String name, AttributeList attrs) throws SAXParseException { + (new TaskHandler(helper, this, null, null, null)).init(name, attrs); + } + + private void handleProperty(String name, AttributeList attrs) throws SAXParseException { + (new TaskHandler(helper, this, null, null, null)).init(name, attrs); + } + + private void handleTarget(String tag, AttributeList attrs) throws SAXParseException { + new TargetHandler(helper, this).init(tag, attrs); + } + + private void handleDataType(String name, AttributeList attrs) throws SAXParseException { + new DataTypeHandler(helper, this).init(name, attrs); + } + + } + + /** + * Handler for "target" elements. + */ + private static class TargetHandler extends AbstractHandler { + private Target target; + + public TargetHandler(ProjectHelperImpl helper, DocumentHandler parentHandler) { + super(helper, parentHandler); + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + String name = null; + String depends = ""; + String ifCond = null; + String unlessCond = null; + String id = null; + String description = null; + + for (int i = 0; i < attrs.getLength(); i++) { + String key = attrs.getName(i); + String value = attrs.getValue(i); + + if (key.equals("name")) { + name = value; + } else if (key.equals("depends")) { + depends = value; + } else if (key.equals("if")) { + ifCond = value; + } else if (key.equals("unless")) { + unlessCond = value; + } else if (key.equals("id")) { + id = value; + } else if (key.equals("description")) { + description = value; + } else { + throw new SAXParseException("Unexpected attribute \"" + key + "\"", helper.locator); + } + } + + if (name == null) { + throw new SAXParseException("target element appears without a name attribute", helper.locator); + } + + target = new Target(); + target.setName(name); + target.setIf(ifCond); + target.setUnless(unlessCond); + target.setDescription(description); + helper.project.addTarget(name, target); + + if (id != null && !id.equals("")) { + helper.project.addReference(id, target); + } + + // take care of dependencies + + if (depends.length() > 0) { + target.setDepends(depends); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (helper.project.getDataTypeDefinitions().get(name) != null) { + new DataTypeHandler(helper, this, target).init(name, attrs); + } else { + new TaskHandler(helper, this, target, null, target).init(name, attrs); + } + } + } + + /** + * Handler for all task elements. + */ + private static class TaskHandler extends AbstractHandler { + private Target target; + private TaskContainer container; + private Task task; + private RuntimeConfigurable parentWrapper; + private RuntimeConfigurable wrapper = null; + + public TaskHandler(ProjectHelperImpl helper, + DocumentHandler parentHandler, + TaskContainer container, + RuntimeConfigurable parentWrapper, + Target target) + { + super(helper, parentHandler); + this.container = container; + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void init(String tag, AttributeList attrs) throws SAXParseException { + try { + task = helper.project.createTask(tag); + } catch (BuildException e) { + // swallow here, will be thrown again in + // UnknownElement.maybeConfigure if the problem persists. + } + + if (task == null) { + task = new UnknownElement(tag); + task.setProject(helper.project); + task.setTaskType(tag); + task.setTaskName(tag); + } + + task.setLocation(new Location(helper.buildFile.toString(), + helper.locator.getLineNumber(), + helper.locator.getColumnNumber())); + helper.configureId(task, attrs); + + // Top level tasks don't have associated targets + if (target != null) { + task.setOwningTarget(target); + container.addTask(task); + task.init(); + wrapper = task.getRuntimeConfigurableWrapper(); + wrapper.setAttributes(attrs); + if (parentWrapper != null) { + parentWrapper.addChild(wrapper); + } + } else { + task.init(); + configure(task, attrs, helper.project); + } + } + + protected void finished() { + if (task != null && target == null) { + task.execute(); + } + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + if (wrapper == null) { + try { + addText(helper.project, task, buf, start, end); + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), helper.locator, exc); + } + } else { + wrapper.addText(buf, start, end); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (task instanceof TaskContainer) { + // task can contain other tasks - no other nested elements possible + new TaskHandler(helper, this, (TaskContainer)task, wrapper, target).init(name, attrs); + } + else { + new NestedElementHandler(helper, this, task, wrapper, target).init(name, attrs); + } + } + } + + /** + * Handler for all nested properties. + */ + private static class NestedElementHandler extends AbstractHandler { + private Object parent; + private Object child; + private RuntimeConfigurable parentWrapper; + private RuntimeConfigurable childWrapper = null; + private TaskAdapter adapter=null; + private Target target; + + public NestedElementHandler(ProjectHelperImpl helper, + DocumentHandler parentHandler, + Object parent, + RuntimeConfigurable parentWrapper, + Target target) { + super(helper, parentHandler); + + if (parent instanceof TaskAdapter) { + this.adapter= (TaskAdapter)parent; + this.parent = adapter.getProxy(); + } else { + this.parent = parent; + } + this.parentWrapper = parentWrapper; + this.target = target; + } + + public void init(String propType, AttributeList attrs) throws SAXParseException { + Class parentClass = parent.getClass(); + IntrospectionHelper ih = + IntrospectionHelper.getHelper(parentClass); + if( adapter!=null ) { + adapter.setIntrospectionHelper( ih ); + } + + try { + String elementName = propType.toLowerCase(Locale.US); + if (parent instanceof UnknownElement) { + UnknownElement uc = new UnknownElement(elementName); + uc.setProject(helper.project); + ((UnknownElement) parent).addChild(uc); + child = uc; + } else { + child = ih.createElement(helper.project, parent, elementName); + } + + helper.configureId(child, attrs); + + if (parentWrapper != null) { + childWrapper = new RuntimeConfigurable(child, propType); + childWrapper.setAttributes(attrs); + parentWrapper.addChild(childWrapper); + } else { + configure(child, attrs, helper.project); + ih.storeElement(helper.project, parent, child, elementName); + } + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), helper.locator, exc); + } + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + if (parentWrapper == null) { + try { + addText(helper.project, child, buf, start, end); + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), helper.locator, exc); + } + } else { + childWrapper.addText(buf, start, end); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + if (child instanceof TaskContainer) { + // taskcontainer nested element can contain other tasks - no other + // nested elements possible + new TaskHandler(helper, this, (TaskContainer)child, childWrapper, target).init(name, attrs); + } + else { + new NestedElementHandler(helper, this, child, childWrapper, target).init(name, attrs); + } + } + } + + /** + * Handler for all data types at global level. + */ + private static class DataTypeHandler extends AbstractHandler { + private Target target; + private Object element; + private RuntimeConfigurable wrapper = null; + + public DataTypeHandler(ProjectHelperImpl helper, DocumentHandler parentHandler) { + this(helper, parentHandler, null); + } + + public DataTypeHandler(ProjectHelperImpl helper, + DocumentHandler parentHandler, + Target target) + { + super(helper, parentHandler); + this.target = target; + } + + public void init(String propType, AttributeList attrs) throws SAXParseException { + try { + element = helper.project.createDataType(propType); + if (element == null) { + throw new BuildException("Unknown data type "+propType); + } + + if (target != null) { + wrapper = new RuntimeConfigurable(element, propType); + wrapper.setAttributes(attrs); + target.addDataType(wrapper); + } else { + configure(element, attrs, helper.project); + helper.configureId(element, attrs); + } + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), helper.locator, exc); + } + } + + public void characters(char[] buf, int start, int end) throws SAXParseException { + try { + addText(helper.project, element, buf, start, end); + } catch (BuildException exc) { + throw new SAXParseException(exc.getMessage(), helper.locator, exc); + } + } + + public void startElement(String name, AttributeList attrs) throws SAXParseException { + new NestedElementHandler(helper, this, element, wrapper, target).init(name, attrs); + } + } + + public ProjectHelperImpl() { + } + + private static SAXParserFactory getParserFactory() { + if (parserFactory == null) { + parserFactory = SAXParserFactory.newInstance(); + } + + return parserFactory; + } + + /** + * Scan AttributeList for the id attribute and maybe add a + * reference to project. + * + *

Moved out of {@link #configure configure} to make it happen + * at parser time.

+ */ + private void configureId(Object target, AttributeList attr) { + String id = attr.getValue("id"); + if (id != null) { + project.addReference(id, target); + } + } + +} diff --git a/proposal/sandbox/embed/README b/proposal/sandbox/embed/README new file mode 100644 index 000000000..35782ee72 --- /dev/null +++ b/proposal/sandbox/embed/README @@ -0,0 +1,2 @@ +Copy the files in o.a.t.ant, recompile. + diff --git a/proposal/sandbox/embed/TaskAdapter.java b/proposal/sandbox/embed/TaskAdapter.java new file mode 100644 index 000000000..5a6dad92c --- /dev/null +++ b/proposal/sandbox/embed/TaskAdapter.java @@ -0,0 +1,214 @@ +/* + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2002 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", "Ant", 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 + * . + */ + +package org.apache.tools.ant; + +import java.lang.reflect.Method; + + + +/** + * Use introspection to "adapt" an arbitrary Bean ( not extending Task, but with similar + * patterns). + * + * The adapter can also be used to wrap tasks that are loaded in a different class loader + * by ant, when used in programatic mode. + * + * @author Costin Manolache + */ +public class TaskAdapter extends Task { + + private Object proxy; + private String methodName="execute"; + + /** + * Checks a class, whether it is suitable to be adapted by TaskAdapter. + * + * Checks conditions only, which are additionally required for a tasks + * adapted by TaskAdapter. Thus, this method should be called by + * {@link Project#checkTaskClass}. + * + * Throws a BuildException and logs as Project.MSG_ERR for + * conditions, that will cause the task execution to fail. + * Logs other suspicious conditions with Project.MSG_WARN. + */ + public static void checkTaskClass(final Class taskClass, final Project project) { + // Any task can be used via adapter. If it doesn't have any execute() + // method, no problem - it will do nothing, but still get an 'id' + // and be registered in the project reference table and useable by other + // tasks. + + if( true ) + return; + + // don't have to check for interface, since then + // taskClass would be abstract too. + try { + final Method executeM = taskClass.getMethod( "execute", null ); + // don't have to check for public, since + // getMethod finds public method only. + // don't have to check for abstract, since then + // taskClass would be abstract too. + if(!Void.TYPE.equals(executeM.getReturnType())) { + final String message = + "return type of execute() should be void but was \""+ + executeM.getReturnType()+"\" in " + taskClass; + project.log(message, Project.MSG_WARN); + } + } catch(NoSuchMethodException e) { + final String message = "No public execute() in " + taskClass; + project.log(message, Project.MSG_ERR); + throw new BuildException(message); + } + } + + private IntrospectionHelper ih; + + void setIntrospectionHelper( IntrospectionHelper ih ) { + this.ih=ih; + } + + IntrospectionHelper getIntrospectionHelper() { + if( ih==null ) { + ih = IntrospectionHelper.getHelper(target.getClass()); + } + return ih; + } + + /** Experimental, non-public method for better 'adaptation' + * + */ + void setAttribute( String name, String value ) + throws BuildException + { + try { + ih.setAttribute( project, proxy, name, value ); + } catch( BuildException ex ) { + if( "do".equals( name ) ) { + setDo( value ); + } else { + throw ex; + } + } + } + + /** Set the 'action' method. This allow beans implementing multiple + * actions or using methods other than 'execute()' to be used in ant + * without any modification. + * + * @ant:experimental + */ + public void setDo(String methodName ) { + this.methodName=methodName; + } + + /** + * Do the execution. + */ + public void execute() throws BuildException { + Method setProjectM = null; + try { + Class c = proxy.getClass(); + setProjectM = + c.getMethod( "setProject", new Class[] {Project.class}); + if(setProjectM != null) { + setProjectM.invoke(proxy, new Object[] {project}); + } + } catch (NoSuchMethodException e) { + // ignore this if the class being used as a task does not have + // a set project method. + } catch( Exception ex ) { + log("Error setting project in " + proxy.getClass(), + Project.MSG_ERR); + throw new BuildException( ex ); + } + + + Method executeM=null; + try { + Class c=proxy.getClass(); + executeM=c.getMethod( methodName, new Class[0] ); + if( executeM == null ) { + log("No public " + methodName + "() in " + proxy.getClass(), Project.MSG_ERR); + throw new BuildException("No public " + methodName +"() in " + proxy.getClass()); + } + executeM.invoke(proxy, null); + return; + } catch (java.lang.reflect.InvocationTargetException ie) { + log("Error in " + proxy.getClass(), Project.MSG_ERR); + Throwable t = ie.getTargetException(); + if (t instanceof BuildException) { + throw ((BuildException) t); + } else { + throw new BuildException(t); + } + } catch( Exception ex ) { + log("Error in " + proxy.getClass(), Project.MSG_ERR); + throw new BuildException( ex ); + } + + } + + /** + * Set the target object class + */ + public void setProxy(Object o) { + this.proxy = o; + } + + public Object getProxy() { + return this.proxy ; + } + +}