From 034b22b4f2114a36a4fbdf240e40615751e43cab Mon Sep 17 00:00:00 2001 From: Costin Manolache Date: Fri, 22 Feb 2002 23:28:31 +0000 Subject: [PATCH] Checking in the modified ( and new ) files. Most of the functionality in Main is moved into AntBean ( all public/protected methods are now wrappers around AntBean's functionality, for backward compat). Same in ProjectHelper - again, for backward compat the public/protected functions are unchanged, old code can still call the old methods and get similar behavior. In TaskAdapter - few improvements ( still more to go ) to allow a program to control tasks that are in a child loader. That's extremely important to get ant embeded and controlled in a programatic way ( Project may create tasks in a child class loader, so casting will fail - the only way to use them is by using introspection ). I've also added the ability to 'adapt' beans that have multiple methods, not only execute() Please review, try, let me know when I can commit it. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271514 13f79535-47bb-0310-9956-ffa450edef68 --- proposal/sandbox/embed/AntBean.java | 610 ++++++++++++++ proposal/sandbox/embed/Main.java | 422 ++++++++++ proposal/sandbox/embed/ProjectHelper.java | 418 ++++++++++ proposal/sandbox/embed/ProjectHelperImpl.java | 750 ++++++++++++++++++ proposal/sandbox/embed/README | 2 + proposal/sandbox/embed/TaskAdapter.java | 214 +++++ 6 files changed, 2416 insertions(+) create mode 100644 proposal/sandbox/embed/AntBean.java create mode 100644 proposal/sandbox/embed/Main.java create mode 100644 proposal/sandbox/embed/ProjectHelper.java create mode 100644 proposal/sandbox/embed/ProjectHelperImpl.java create mode 100644 proposal/sandbox/embed/README create mode 100644 proposal/sandbox/embed/TaskAdapter.java 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 ; + } + +}