/* * 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; } }