1 /*
2  * Copyright  2000-2004 The Apache Software Foundation
3  *
4  *  Licensed under the Apache License, Version 2.0 (the "License");
5  *  you may not use this file except in compliance with the License.
6  *  You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10 *  Unless required by applicable law or agreed to in writing, software
11 *  distributed under the License is distributed on an "AS IS" BASIS,
12 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 *  See the License for the specific language governing permissions and
14 *  limitations under the License.
15 *
16 */
17package org.tp23.antinstaller.antmod;
18
19import java.io.File;
20import java.io.FileInputStream;
21import java.io.FileOutputStream;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.PrintStream;
25import java.util.Enumeration;
26import java.util.Properties;
27import java.util.Vector;
28
29import org.apache.tools.ant.BuildException;
30import org.apache.tools.ant.BuildListener;
31import org.apache.tools.ant.BuildLogger;
32import org.apache.tools.ant.DefaultLogger;
33import org.apache.tools.ant.Diagnostics;
34import org.apache.tools.ant.ExitStatusException;
35import org.apache.tools.ant.Project;
36import org.apache.tools.ant.ProjectHelper;
37import org.apache.tools.ant.Target;
38import org.apache.tools.ant.input.DefaultInputHandler;
39import org.apache.tools.ant.input.InputHandler;
40import org.apache.tools.ant.launch.AntMain;
41import org.apache.tools.ant.util.JavaEnvUtils;
42import org.tp23.antinstaller.InstallerContext;
43import org.tp23.antinstaller.PropertiesFileRenderer;
44
45
46/**
47 * Command line entry point into Ant. This class is entered via the
48 * canonical `public static void main` entry point and reads the
49 * command line arguments. It then assembles and executes an Ant
50 * project.
51 * <p>
52 * If you integrating Ant into some other tool, this is not the class
53 * to use as an entry point. Please see the source code of this
54 * class to see how it manipulates the Ant project classes.
55 *
56* This file has been modified by Paul Hinds for Antinstaller and is not the same
57* as the one delivered with Ant 1.6
58* @version $Id: Main.java,v 1.5 2007/01/12 10:41:48 anothermwilson Exp $
59 */
60public class Main implements AntMain {
61
62    /** The default build file name. */
63    public static final String DEFAULT_BUILD_FILENAME = "build.xml";
64
65    /** Our current message output status. Follows Project.MSG_XXX. */
66    private int msgOutputLevel = Project.MSG_INFO;
67
68    /** File that we are using for configuration. */
69    private File buildFile; /* null */
70
71    /** Stream to use for logging. */
72    private static PrintStream out = System.out;
73
74    /** Stream that we are using for logging error messages. */
75    private static PrintStream err = System.err;
76
77    /** The build targets. */
78    private Vector targets = new Vector();
79
80    /** Set of properties that can be used by tasks. */
81    private Properties definedProps = new Properties();
82
83    /** Names of classes to add as listeners to project. */
84    private Vector listeners = new Vector(1);
85
86    /** File names of property files to load on startup. */
87    private Vector propertyFiles = new Vector(1);
88
89    /** Indicates whether this build is to support interactive input */
90    private boolean allowInput = true;
91
92    /** keep going mode */
93    private boolean keepGoingMode = false;
94
95    /**
96     * The Ant logger class. There may be only one logger. It will have
97     * the right to use the 'out' PrintStream. The class must implements the
98     * BuildLogger interface.
99     */
00    private String loggerClassname = null;
01
02    /**
03     * The Ant InputHandler class.  There may be only one input
04     * handler.
05     */
06    private String inputHandlerClassname = null;
07
08    /**
09     * Whether or not output to the log is to be unadorned.
10     */
11    private boolean emacsMode = false;
12
13    /**
14     * Whether or not this instance has successfully been
15     * constructed and is ready to run.
16     */
17    private boolean readyToRun = false;
18
19    /**
20     * Whether or not we should only parse and display the project help
21     * information.
22     */
23    private boolean projectHelp = false;
24
25    /**
26     * Whether or not a logfile is being used. This is used to
27     * check if the output streams must be closed.
28     */
29    private static boolean isLogFileUsed = false;
30
31    /**
32     * optional thread priority
33     */
34    private Integer threadPriority = null;
35    /**
36     * Installer Context
37     */
38    private InstallerContext ctx = null;
39
40    /**
41     * Prints the message of the Throwable if it (the message) is not
42     * <code>null</code>.
43     *
44     * @param t Throwable to print the message of.
45     *          Must not be <code>null</code>.
46     */
47    private static void printMessage(Throwable t) {
48        String message = t.getMessage();
49        if (message != null) {
50            System.err.println(message);
51        }
52    }
53
54    /**
55     * Creates a new instance of this class using the
56     * arguments specified, gives it any extra user properties which have been
57     * specified, and then runs the build using the classloader provided.
58     *
59     * @param args Command line arguments. Must not be <code>null</code>.
60     * @param additionalUserProperties Any extra properties to use in this
61     *        build. May be <code>null</code>, which is the equivalent to
62     *        passing in an empty set of properties.
63     * @param coreLoader Classloader used for core classes. May be
64     *        <code>null</code> in which case the system classloader is used.
65     */
66    public static void start(String[] args, Properties additionalUserProperties,
67                             ClassLoader coreLoader) {
68        throw new UnsupportedOperationException("Required by Ant interface but not used");
69        //Main m = new Main();
70        //m.startAnt(args, additionalUserProperties, coreLoader);
71    }
72
73    
74    public void startAnt(String[] args, Properties additionalUserProperties,
75                         ClassLoader coreLoader) {
76        throw new UnsupportedOperationException("Required by Ant interface but not used");
77        //startAnt(args, additionalUserProperties, coreLoader, null); // FindBugs does not like this
78    }
79    /**
80     * Start Ant
81     * @param args command line args
82     * @param additionalUserProperties properties to set beyond those that
83     *        may be specified on the args list
84     * @param coreLoader - not used
85     * @return the return code that was used for System.exit() in the original Ant 
86     *
87     * @since Ant 1.6
88     */
89    public int startAnt(String[] args, Properties additionalUserProperties,
90                         ClassLoader coreLoader, InstallerContext ctx) {
91         this.ctx = ctx;
92         out = ctx.getAntOutputRenderer().getOut();
93         err = ctx.getAntOutputRenderer().getErr();
94        try {
95            Diagnostics.validateVersion();
96            processArgs(args);
97        } catch (Throwable exc) {
98            handleLogfile();
99            printMessage(exc);
00            return 1;
01        }
02
03        if (additionalUserProperties != null) {
04            for (Enumeration e = additionalUserProperties.keys();
05                    e.hasMoreElements();) {
06                String key = (String) e.nextElement();
07                String property = additionalUserProperties.getProperty(key);
08                definedProps.put(key, property);
09            }
10        }
11
12        // expect the worst
13        int exitCode = 1;
14        try {
15            try {
16                runBuild(coreLoader);
17                exitCode = 0;
18            } catch (ExitStatusException ese) {
19                exitCode = ese.getStatus();
20                if (exitCode != 0) {
21                    throw ese;
22                }
23            }
24        } catch (BuildException be) {
25            if (err != System.err) {
26                printMessage(be);
27            }
28        } catch (Throwable exc) {
29            exc.printStackTrace();
30            printMessage(exc);
31        } finally {
32            handleLogfile();
33        }
34        //mod by Paul Hinds
35        return exitCode;
36        //System.exit(exitCode);
37    }
38
39    /**
40     * Close logfiles, if we have been writing to them.
41     *
42     * @since Ant 1.6
43     */
44    private static void handleLogfile() {
45        if (isLogFileUsed) {
46            if (out != null) {
47                try {
48                    out.close();
49                } catch (final Exception e) {
50                    //ignore
51                }
52            }
53            if (err != null) {
54                try {
55                    err.close();
56                } catch (final Exception e) {
57                    //ignore
58                }
59            }
60        }
61    }
62
63    /**
64     * Command line entry point. This method kicks off the building
65     * of a project object and executes a build using either a given
66     * target or the default target.
67     *
68     * @param args Command line arguments. Must not be <code>null</code>.
69     */
70//    public static void main(String[] args) {
71//        start(args, null, null);
72//    }
73//
74    /**
75     * Constructor used when creating Main for later arg processing
76     * and startup
77     */
78    public Main() {
79    }
80
81    /**
82     * Sole constructor, which parses and deals with command line
83     * arguments.
84     *
85     * @param args Command line arguments. Must not be <code>null</code>.
86     *
87     * @exception BuildException if the specified build file doesn't exist
88     *                           or is a directory.
89     *
90     * @deprecated
91     */
92    protected Main(String[] args) throws BuildException {
93        processArgs(args);
94    }
95
96    /**
97     * Process command line arguments.
98     * When ant is started from Launcher, the -lib argument does not get
99     * passed through to this routine.
00     *
01     * @param args the command line arguments.
02     *
03     * @since Ant 1.6
04     */
05    private void processArgs(String[] args) {
06        String searchForThis = null;
07        PrintStream logTo = null;
08
09        // cycle through given args
10
11        for (int i = 0; i < args.length; i++) {
12            String arg = args[i];
13
14            if (arg.equals("-help") || arg.equals("-h")) {
15                printUsage();
16                return;
17            } else if (arg.equals("-version")) {
18                printVersion();
19                return;
20            } else if (arg.equals("-diagnostics")) {
21                Diagnostics.doReport(System.out);
22                return;
23            } else if (arg.equals("-quiet") || arg.equals("-q")) {
24                msgOutputLevel = Project.MSG_WARN;
25            } else if (arg.equals("-verbose") || arg.equals("-v")) {
26                printVersion();
27                msgOutputLevel = Project.MSG_VERBOSE;
28            } else if (arg.equals("-debug") || arg.equals("-d")) {
29                printVersion();
30                msgOutputLevel = Project.MSG_DEBUG;
31            } else if (arg.equals("-noinput")) {
32                allowInput = false;
33            } else if (arg.equals("-logfile") || arg.equals("-l")) {
34                try {
35                    File logFile = new File(args[i + 1]);
36                    i++;
37                    logTo = new PrintStream(new FileOutputStream(logFile));
38                    isLogFileUsed = true;
39                } catch (IOException ioe) {
40                    String msg = "Cannot write on the specified log file. "
41                        + "Make sure the path exists and you have write "
42                        + "permissions.";
43                    throw new BuildException(msg);
44                } catch (ArrayIndexOutOfBoundsException aioobe) {
45                    String msg = "You must specify a log file when "
46                        + "using the -log argument";
47                    throw new BuildException(msg);
48                }
49            } else if (arg.equals("-buildfile") || arg.equals("-file")
50                       || arg.equals("-f")) {
51                try {
52                    buildFile = new File(args[i + 1].replace('/', File.separatorChar));
53                    i++;
54                } catch (ArrayIndexOutOfBoundsException aioobe) {
55                    String msg = "You must specify a buildfile when "
56                        + "using the -buildfile argument";
57                    throw new BuildException(msg);
58                }
59            } else if (arg.equals("-listener")) {
60                try {
61                    listeners.addElement(args[i + 1]);
62                    i++;
63                } catch (ArrayIndexOutOfBoundsException aioobe) {
64                    String msg = "You must specify a classname when "
65                        + "using the -listener argument";
66                    throw new BuildException(msg);
67                }
68            } else if (arg.startsWith("-D")) {
69
70                /* Interestingly enough, we get to here when a user
71                 * uses -Dname=value. However, in some cases, the OS
72                 * goes ahead and parses this out to args
73                 *   {"-Dname", "value"}
74                 * so instead of parsing on "=", we just make the "-D"
75                 * characters go away and skip one argument forward.
76                 *
77                 * I don't know how to predict when the JDK is going
78                 * to help or not, so we simply look for the equals sign.
79                 */
80
81                String name = arg.substring(2, arg.length());
82                String value = null;
83                int posEq = name.indexOf("=");
84                if (posEq > 0) {
85                    value = name.substring(posEq + 1);
86                    name = name.substring(0, posEq);
87                } else if (i < args.length - 1) {
88                    value = args[++i];
89                } else {
90                    throw new BuildException("Missing value for property "
91                                             + name);
92                }
93
94                definedProps.put(name, value);
95            } else if (arg.equals("-logger")) {
96                if (loggerClassname != null) {
97                    throw new BuildException("Only one logger class may "
98                        + " be specified.");
99                }
00                try {
01                    loggerClassname = args[++i];
02                } catch (ArrayIndexOutOfBoundsException aioobe) {
03                    throw new BuildException("You must specify a classname when"
04                                             + " using the -logger argument");
05                }
06            } else if (arg.equals("-inputhandler")) {
07                if (inputHandlerClassname != null) {
08                    throw new BuildException("Only one input handler class may "
09                                             + "be specified.");
10                }
11                try {
12                    inputHandlerClassname = args[++i];
13                } catch (ArrayIndexOutOfBoundsException aioobe) {
14                    throw new BuildException("You must specify a classname when"
15                                             + " using the -inputhandler"
16                                             + " argument");
17                }
18            } else if (arg.equals("-emacs") || arg.equals("-e")) {
19                emacsMode = true;
20            } else if (arg.equals("-projecthelp") || arg.equals("-p")) {
21                // set the flag to display the targets and quit
22                projectHelp = true;
23            } else if (arg.equals("-find") || arg.equals("-s")) {
24                // eat up next arg if present, default to build.xml
25                if (i < args.length - 1) {
26                    searchForThis = args[++i];
27                } else {
28                    searchForThis = DEFAULT_BUILD_FILENAME;
29                }
30            } else if (arg.startsWith("-propertyfile")) {
31                try {
32                    propertyFiles.addElement(args[i + 1]);
33                    i++;
34                } catch (ArrayIndexOutOfBoundsException aioobe) {
35                    String msg = "You must specify a property filename when "
36                        + "using the -propertyfile argument";
37                    throw new BuildException(msg);
38                }
39            } else if (arg.equals("-k") || arg.equals("-keep-going")) {
40                keepGoingMode = true;
41            } else if (arg.equals("-nice")) {
42                try {
43                    threadPriority=Integer.decode(args[i + 1]);
44                } catch (ArrayIndexOutOfBoundsException aioobe) {
45                    throw new BuildException(
46                            "You must supply a niceness value (1-10)"+
47                            " after the -nice option");
48                } catch (NumberFormatException e) {
49                    throw new BuildException("Unrecognized niceness value: " +
50                            args[i + 1]);
51                }
52                i++;
53                if(threadPriority.intValue()<Thread.MIN_PRIORITY ||
54                        threadPriority.intValue()>Thread.MAX_PRIORITY) {
55                    throw new BuildException(
56                            "Niceness value is out of the range 1-10");
57                }
58            } else if (arg.startsWith("-")) {
59                // we don't have any more args to recognize!
60                String msg = "Unknown argument: " + arg;
61                System.out.println(msg);
62                printUsage();
63                throw new BuildException("");
64            } else {
65                // if it's no other arg, it may be the target
66                targets.addElement(arg);
67            }
68        }
69
70        // if buildFile was not specified on the command line,
71        if (buildFile == null) {
72            // but -find then search for it
73            if (searchForThis != null) {
74                buildFile = findBuildFile(System.getProperty("user.dir"),
75                                          searchForThis);
76            } else {
77                buildFile = new File(DEFAULT_BUILD_FILENAME);
78            }
79        }
80
81        // make sure buildfile exists
82        if (!buildFile.exists()) {
83            System.out.println("Buildfile: " + buildFile + " does not exist!");
84            throw new BuildException("Build failed");
85        }
86
87        // make sure it's not a directory (this falls into the ultra
88        // paranoid lets check everything category
89
90        if (buildFile.isDirectory()) {
91            System.out.println("What? Buildfile: " + buildFile + " is a dir!");
92            throw new BuildException("Build failed");
93        }
94
95        // Load the property files specified by -propertyfile
96        for (int propertyFileIndex = 0;
97             propertyFileIndex < propertyFiles.size();
98             propertyFileIndex++) {
99            String filename
00                = (String) propertyFiles.elementAt(propertyFileIndex);
01            Properties props = new Properties();
02            FileInputStream fis = null;
03            try {
04                fis = new FileInputStream(filename);
05                props.load(fis);
06            } catch (IOException e) {
07                System.out.println("Could not load property file "
08                   + filename + ": " + e.getMessage());
09            } finally {
10                if (fis != null) {
11                    try {
12                        fis.close();
13                    } catch (IOException e) {
14                        // ignore
15                    }
16                }
17            }
18
19            // ensure that -D properties take precedence
20            Enumeration propertyNames = props.propertyNames();
21            while (propertyNames.hasMoreElements()) {
22                String name = (String) propertyNames.nextElement();
23                if (definedProps.getProperty(name) == null) {
24                    definedProps.put(name, props.getProperty(name));
25                }
26            }
27        }
28
29        if (msgOutputLevel >= Project.MSG_INFO) {
30            System.out.println("Buildfile: " + buildFile);
31        }
32
33        if (logTo != null) {
34            out = logTo;
35            err = logTo;
36            System.setOut(out);
37            System.setErr(err);
38        }
39        readyToRun = true;
40    }
41
42    /**
43     * Helper to get the parent file for a given file.
44     * <p>
45     * Added to simulate File.getParentFile() from JDK 1.2.
46     * @deprecated
47     *
48     * @param file   File to find parent of. Must not be <code>null</code>.
49     * @return       Parent file or null if none
50     */
51    private File getParentFile(File file) {
52        File parent = file.getParentFile();
53
54        if (parent != null && msgOutputLevel >= Project.MSG_VERBOSE) {
55            System.out.println("Searching in " + parent.getAbsolutePath());
56        }
57
58        return parent;
59    }
60
61    /**
62     * Search parent directories for the build file.
63     * <p>
64     * Takes the given target as a suffix to append to each
65     * parent directory in search of a build file.  Once the
66     * root of the file-system has been reached an exception
67     * is thrown.
68     *
69     * @param start  Leaf directory of search.
70     *               Must not be <code>null</code>.
71     * @param suffix  Suffix filename to look for in parents.
72     *                Must not be <code>null</code>.
73     *
74     * @return A handle to the build file if one is found
75     *
76     * @exception BuildException if no build file is found
77     */
78    private File findBuildFile(String start, String suffix)
79         throws BuildException {
80        if (msgOutputLevel >= Project.MSG_INFO) {
81            System.out.println("Searching for " + suffix + " ...");
82        }
83
84        File parent = new File(new File(start).getAbsolutePath());
85        File file = new File(parent, suffix);
86
87        // check if the target file exists in the current directory
88        while (!file.exists()) {
89            // change to parent directory
90            parent = getParentFile(parent);
91
92            // if parent is null, then we are at the root of the fs,
93            // complain that we can't find the build file.
94            if (parent == null) {
95                throw new BuildException("Could not locate a build file!");
96            }
97
98            // refresh our file handle
99            file = new File(parent, suffix);
00        }
01
02        return file;
03    }
04
05    /**
06     * Executes the build. If the constructor for this instance failed
07     * (e.g. returned after issuing a warning), this method returns
08     * immediately.
09     *
10     * @param coreLoader The classloader to use to find core classes.
11     *                   May be <code>null</code>, in which case the
12     *                   system classloader is used.
13     *
14     * @exception BuildException if the build fails
15     */
16    private void runBuild(ClassLoader coreLoader) throws BuildException {
17
18        if (!readyToRun) {
19            return;
20        }
21
22        final Project project = new Project();
23        project.setCoreLoader(coreLoader);
24
25        Throwable error = null;
26
27        try {
28            addBuildListeners(project);
29            addInputHandler(project);
30
31            PrintStream err = System.err;
32            PrintStream out = System.out;
33            InputStream in = System.in;
34
35            // use a system manager that prevents from System.exit()
36            // only in JDK > 1.1
37            SecurityManager oldsm = null;
38            if (!JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_0)
39                && !JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
40                oldsm = System.getSecurityManager();
41
42                //SecurityManager can not be installed here for backwards
43                //compatibility reasons (PD). Needs to be loaded prior to
44                //ant class if we are going to implement it.
45                //System.setSecurityManager(new NoExitSecurityManager());
46            }
47            try {
48                if (allowInput) {
49                    project.setDefaultInputStream(System.in);
50                }
51                //System.setIn(new DemuxInputStream(project));
52                //System.setOut(new PrintStream(new DemuxOutputStream(project, false)));
53                //System.setErr(new PrintStream(new DemuxOutputStream(project, true)));
54
55
56                if (!projectHelp) {
57                    project.fireBuildStarted();
58                }
59
60                // set the thread priorities
61                if (threadPriority != null) {
62                    try {
63                        project.log("Setting Ant's thread priority to "
64                                + threadPriority,Project.MSG_VERBOSE);
65                        Thread.currentThread().setPriority(threadPriority.intValue());
66                    } catch (SecurityException swallowed) {
67                        //we cannot set the priority here.
68                        project.log("A security manager refused to set the -nice value");
69                    }
70                }
71
72                project.init();
73                project.setUserProperty("ant.version", getAntVersion());
74
75                // set user-define properties
76                Enumeration e = definedProps.keys();
77                while (e.hasMoreElements()) {
78                    String arg = (String) e.nextElement();
79                    String value = (String) definedProps.get(arg);
80                    project.setUserProperty(arg, value);
81                }
82
83                project.setUserProperty("ant.file",
84                                        buildFile.getAbsolutePath());
85                project.setKeepGoingMode(keepGoingMode);
86
87                ProjectHelper.configureProject(project, buildFile);
88
89                project.setBasedir(definedProps.getProperty(PropertiesFileRenderer.FILE_ROOT_PROPERTY));
90
91
92                if (projectHelp) {
93                    printDescription(project);
94                    printTargets(project, msgOutputLevel > Project.MSG_INFO);
95                    return;
96                }
97
98                // make sure that we have a target to execute
99                if (targets.size() == 0) {
00                    if (project.getDefaultTarget() != null) {
01                        targets.addElement(project.getDefaultTarget());
02                    }
03                }
04                project.executeTargets(targets);
05            } finally {
06                // put back the original security manager
07                //The following will never eval to true. (PD)
08                if (oldsm != null) {
09                    System.setSecurityManager(oldsm);
10                }
11
12                System.setOut(out);
13                System.setErr(err);
14                System.setIn(in);
15            }
16        } catch (RuntimeException exc) {
17            error = exc;
18            throw exc;
19        } catch (Error err) {
20            error = err;
21            throw err;
22        } finally {
23            if (!projectHelp) {
24                project.fireBuildFinished(error);
25            } else if (error != null) {
26                project.log(error.toString(), Project.MSG_ERR);
27            }
28        }
29    }
30
31    /**
32     * Adds the listeners specified in the command line arguments,
33     * along with the default listener, to the specified project.
34     *
35     * @param project The project to add listeners to.
36     *                Must not be <code>null</code>.
37     */
38    protected void addBuildListeners(Project project) {
39
40        // Add the default listener
41        project.addBuildListener(createLogger());
42
43        //Add the AntInstaller Listener
44        if(ctx.getBuildListener()!=null){
45            project.addBuildListener(ctx.getBuildListener());
46        }
47
48        for (int i = 0; i < listeners.size(); i++) {
49            String className = (String) listeners.elementAt(i);
50            try {
51                BuildListener listener =
52                    (BuildListener) Class.forName(className).newInstance();
53                if (project != null) {
54                    project.setProjectReference(listener);
55                }
56                project.addBuildListener(listener);
57            } catch (Throwable exc) {
58                throw new BuildException("Unable to instantiate listener "
59                    + className, exc);
60            }
61        }
62    }
63
64    /**
65     * Creates the InputHandler and adds it to the project.
66     *
67     * @param project the project instance.
68     *
69     * @exception BuildException if a specified InputHandler
70     *                           implementation could not be loaded.
71     */
72    private void addInputHandler(Project project) throws BuildException {
73        InputHandler handler = null;
74        if (inputHandlerClassname == null) {
75            handler = new DefaultInputHandler();
76        } else {
77            try {
78                handler = (InputHandler)
79                    (Class.forName(inputHandlerClassname).newInstance());
80                if (project != null) {
81                    project.setProjectReference(handler);
82                }
83            } catch (ClassCastException e) {
84                String msg = "The specified input handler class "
85                    + inputHandlerClassname
86                    + " does not implement the InputHandler interface";
87                throw new BuildException(msg);
88            } catch (Exception e) {
89                String msg = "Unable to instantiate specified input handler "
90                    + "class " + inputHandlerClassname + " : "
91                    + e.getClass().getName();
92                throw new BuildException(msg);
93            }
94        }
95        project.setInputHandler(handler);
96    }
97
98    // XXX: (Jon Skeet) Any reason for writing a message and then using a bare
99    // RuntimeException rather than just using a BuildException here? Is it
00    // in case the message could end up being written to no loggers (as the
01    // loggers could have failed to be created due to this failure)?
02    /**
03     * Creates the default build logger for sending build events to the ant
04     * log.
05     *
06     * @return the logger instance for this build.
07     */
08    private BuildLogger createLogger() {
09        BuildLogger logger = null;
10        if (loggerClassname != null) {
11            try {
12                Class loggerClass = Class.forName(loggerClassname);
13                logger = (BuildLogger) (loggerClass.newInstance());
14            } catch (ClassCastException e) {
15                System.err.println("The specified logger class "
16                    + loggerClassname
17                    + " does not implement the BuildLogger interface");
18                throw new RuntimeException();
19            } catch (Exception e) {
20                System.err.println("Unable to instantiate specified logger "
21                    + "class " + loggerClassname + " : "
22                    + e.getClass().getName());
23                throw new RuntimeException();
24            }
25        } else {
26            logger = new DefaultLogger();
27        }
28
29        logger.setMessageOutputLevel(msgOutputLevel);
30        logger.setOutputPrintStream(out);
31        logger.setErrorPrintStream(err);
32        logger.setEmacsMode(emacsMode);
33
34        return logger;
35    }
36
37    /**
38     * Prints the usage information for this class to <code>System.out</code>.
39     */
40    private static void printUsage() {
41        String lSep = System.getProperty("line.separator");
42        StringBuffer msg = new StringBuffer();
43        msg.append("ant [options] [target [target2 [target3] ...]]" + lSep);
44        msg.append("Options: " + lSep);
45        msg.append("  -help, -h              print this message" + lSep);
46        msg.append("  -projecthelp, -p       print project help information" + lSep);
47        msg.append("  -version               print the version information and exit" + lSep);
48        msg.append("  -diagnostics           print information that might be helpful to" + lSep);
49        msg.append("                         diagnose or report problems." + lSep);
50        msg.append("  -quiet, -q             be extra quiet" + lSep);
51        msg.append("  -verbose, -v           be extra verbose" + lSep);
52        msg.append("  -debug, -d             print debugging information" + lSep);
53        msg.append("  -emacs, -e             produce logging information without adornments" + lSep);
54        msg.append("  -lib <path>            specifies a path to search for jars and classes" + lSep);
55        msg.append("  -logfile <file>        use given file for log" + lSep);
56        msg.append("    -l     <file>                ''" + lSep);
57        msg.append("  -logger <classname>    the class which is to perform logging" + lSep);
58        msg.append("  -listener <classname>  add an instance of class as a project listener" + lSep);
59        msg.append("  -noinput               do not allow interactive input" + lSep);
60        msg.append("  -buildfile <file>      use given buildfile" + lSep);
61        msg.append("    -file    <file>              ''" + lSep);
62        msg.append("    -f       <file>              ''" + lSep);
63        msg.append("  -D<property>=<value>   use value for given property" + lSep);
64        msg.append("  -keep-going, -k        execute all targets that do not depend" + lSep);
65        msg.append("                         on failed target(s)" + lSep);
66        msg.append("  -propertyfile <name>   load all properties from file with -D" + lSep);
67        msg.append("                         properties taking precedence" + lSep);
68        msg.append("  -inputhandler <class>  the class which will handle input requests" + lSep);
69        msg.append("  -find <file>           (s)earch for buildfile towards the root of" + lSep);
70        msg.append("    -s  <file>           the filesystem and use it" + lSep);
71        msg.append("  -nice  number          A niceness value for the main thread:" + lSep +
72                   "                         1 (lowest) to 10 (highest); 5 is the default" + lSep);
73        System.out.println(msg.toString());
74    }
75
76    /**
77     * Prints the Ant version information to <code>System.out</code>.
78     *
79     * @exception BuildException if the version information is unavailable
80     */
81    private static void printVersion() throws BuildException {
82        System.out.println(getAntVersion());
83    }
84
85    /**
86     * Cache of the Ant version information when it has been loaded.
87     */
88    private static String antVersion = null;
89
90    /**
91     * Returns the Ant version information, if available. Once the information
92     * has been loaded once, it's cached and returned from the cache on future
93     * calls.
94     *
95     * @return the Ant version information as a String
96     *         (always non-<code>null</code>)
97     *
98     * @exception BuildException if the version information is unavailable
99     */
00    public static synchronized String getAntVersion() throws BuildException {
01        if (antVersion == null) {
02            try {
03                Properties props = new Properties();
04                InputStream in =
05                    Main.class.getResourceAsStream("/org/apache/tools/ant/version.txt");
06                props.load(in);
07                in.close();
08
09                StringBuffer msg = new StringBuffer();
10                msg.append("Apache Ant version ");
11                msg.append(props.getProperty("VERSION"));
12                msg.append(" compiled on ");
13                msg.append(props.getProperty("DATE"));
14                antVersion = msg.toString();
15            } catch (IOException ioe) {
16                throw new BuildException("Could not load the version information:"
17                                         + ioe.getMessage());
18            } catch (NullPointerException npe) {
19                throw new BuildException("Could not load the version information.");
20            }
21        }
22        return antVersion;
23    }
24
25     /**
26      * Prints the description of a project (if there is one) to
27      * <code>System.out</code>.
28      *
29      * @param project The project to display a description of.
30      *                Must not be <code>null</code>.
31      */
32    private static void printDescription(Project project) {
33       if (project.getDescription() != null) {
34          project.log(project.getDescription());
35       }
36    }
37
38    /**
39     * Prints a list of all targets in the specified project to
40     * <code>System.out</code>, optionally including subtargets.
41     *
42     * @param project The project to display a description of.
43     *                Must not be <code>null</code>.
44     * @param printSubTargets Whether or not subtarget names should also be
45     *                        printed.
46     */
47    private static void printTargets(Project project, boolean printSubTargets) {
48        // find the target with the longest name
49        int maxLength = 0;
50        Enumeration ptargets = project.getTargets().elements();
51        String targetName;
52        String targetDescription;
53        Target currentTarget;
54        // split the targets in top-level and sub-targets depending
55        // on the presence of a description
56        Vector topNames = new Vector();
57        Vector topDescriptions = new Vector();
58        Vector subNames = new Vector();
59
60        while (ptargets.hasMoreElements()) {
61            currentTarget = (Target) ptargets.nextElement();
62            targetName = currentTarget.getName();
63            if (targetName.equals("")) {
64                continue;
65            }
66            targetDescription = currentTarget.getDescription();
67            // maintain a sorted list of targets
68            if (targetDescription == null) {
69                int pos = findTargetPosition(subNames, targetName);
70                subNames.insertElementAt(targetName, pos);
71            } else {
72                int pos = findTargetPosition(topNames, targetName);
73                topNames.insertElementAt(targetName, pos);
74                topDescriptions.insertElementAt(targetDescription, pos);
75                if (targetName.length() > maxLength) {
76                    maxLength = targetName.length();
77                }
78            }
79        }
80
81        printTargets(project, topNames, topDescriptions, "Main targets:",
82                     maxLength);
83        //if there were no main targets, we list all subtargets
84        //as it means nothing has a description
85        if (topNames.size() == 0) {
86            printSubTargets = true;
87        }
88        if (printSubTargets) {
89            printTargets(project, subNames, null, "Other targets:", 0);
90        }
91
92        String defaultTarget = project.getDefaultTarget();
93        if (defaultTarget != null && !"".equals(defaultTarget)) {
94            // shouldn't need to check but...
95            project.log("Default target: " + defaultTarget);
96        }
97    }
98
99    /**
00     * Searches for the correct place to insert a name into a list so as
01     * to keep the list sorted alphabetically.
02     *
03     * @param names The current list of names. Must not be <code>null</code>.
04     * @param name  The name to find a place for.
05     *              Must not be <code>null</code>.
06     *
07     * @return the correct place in the list for the given name
08     */
09    private static int findTargetPosition(Vector names, String name) {
10        int res = names.size();
11        for (int i = 0; i < names.size() && res == names.size(); i++) {
12            if (name.compareTo((String) names.elementAt(i)) < 0) {
13                res = i;
14            }
15        }
16        return res;
17    }
18
19    /**
20     * Writes a formatted list of target names to <code>System.out</code>
21     * with an optional description.
22     *
23     *
24     * @param project the project instance.
25     * @param names The names to be printed.
26     *              Must not be <code>null</code>.
27     * @param descriptions The associated target descriptions.
28     *                     May be <code>null</code>, in which case
29     *                     no descriptions are displayed.
30     *                     If non-<code>null</code>, this should have
31     *                     as many elements as <code>names</code>.
32     * @param heading The heading to display.
33     *                Should not be <code>null</code>.
34     * @param maxlen The maximum length of the names of the targets.
35     *               If descriptions are given, they are padded to this
36     *               position so they line up (so long as the names really
37     *               <i>are</i> shorter than this).
38     */
39    private static void printTargets(Project project, Vector names,
40                                     Vector descriptions, String heading,
41                                     int maxlen) {
42        // now, start printing the targets and their descriptions
43        String lSep = System.getProperty("line.separator");
44        // got a bit annoyed that I couldn't find a pad function
45        String spaces = "    ";
46        while (spaces.length() <= maxlen) {
47            spaces += spaces;
48        }
49        StringBuffer msg = new StringBuffer();
50        msg.append(heading);
51        msg.append(lSep);
52        msg.append(lSep);
53        for (int i = 0; i < names.size(); i++) {
54            msg.append(" ");
55            msg.append(names.elementAt(i));
56            if (descriptions != null) {
57                msg.append(spaces.substring(0, maxlen - ((String) names.elementAt(i)).length() + 2));
58                msg.append(descriptions.elementAt(i));
59            }
60            msg.append(lSep);
61        }
62        project.log(msg.toString());
63    }
64}
65