/* *******************************************************************
 * Copyright (c) 2001-2001 Xerox Corporation, 
 *               2002 Palo Alto Research Center, Incorporated (PARC)
 *               2003-2004 Contributors.
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Common Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/cpl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 *     Wes Isberg     2003-2004 changes
 * ******************************************************************/


package org.aspectj.tools.ant.taskdefs;

import java.io.*;
import java.util.*;

import org.apache.tools.ant.*;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.*;
import org.aspectj.bridge.*;
import org.aspectj.tools.ajc.Main;
import org.aspectj.tools.ajc.Main.MessagePrinter;
import org.aspectj.util.*;


/**
 * This runs the AspectJ 1.1 compiler, 
 * supporting all the command-line options.
 * In 1.1.1, ajc copies resources from input jars, 
 * but you can copy resources from the source directories
 * using sourceRootCopyFilter.
 * When not forking, things will be copied as needed 
 * for each iterative compile,
 * but when forking things are only copied at the 
 * completion of a successful compile.
 * <p>
 * See the development environment guide for 
 * usage documentation.
 * 
 * @since AspectJ 1.1, Ant 1.5
 */
public class AjcTask extends MatchingTask {
    /*
     * This task mainly converts ant specification for ajc,
     * verbosely ignoring improper input.
     * It also has some special features for non-obvious clients:
     * (1) Javac compiler adapter supported in 
     *    <code>setupAjc(AjcTask, Javac, File)</code>
     * and
     *    <code>readArguments(String[])</code>;
     * (2) testing is supported by
     * (a) permitting the same specification to be re-run
     *     with added flags (settings once made cannot be
     *     removed); and
     * (b) permitting recycling the task with 
     *     <code>reset()</code> (untested).
     * 
     * The parts that do more than convert ant specs are
     * (a) code for forking;
     * (b) code for copying resources.
     * 
     * If you maintain/upgrade this task, keep in mind:
     * (1) changes to the semantics of ajc (new options, new
     *     values permitted, etc.) will have to be reflected here. 
     * (2) the clients:
     * the iajc ant script, Javac compiler adapter,
     * maven clients of iajc, and testing code.
     */

    // XXX move static methods after static initializer
    /**
     * This method extracts javac arguments to ajc,
     * and add arguments to make ajc behave more like javac
     * in copying resources.
     * <p>
     * Pass ajc-specific options using compilerarg sub-element:
     * <pre>
     * &lt;javac srcdir="src">
     *     &lt;compilerarg compiler="..." line="-argfile src/args.lst"/>
     * &lt;javac>
     * </pre>
     * Some javac arguments are not supported in this component (yet):
     * <pre>
     * String memoryInitialSize;
     * boolean includeAntRuntime = true;
     * boolean includeJavaRuntime = false;
     * </pre>
     * Other javac arguments are not supported in ajc 1.1:
     * <pre>
     * boolean optimize;
     * String forkedExecutable;
     * FacadeTaskHelper facade;
     * boolean depend;
     * String debugLevel;
     * Path compileSourcepath;
     * </pre>
     * @param javac the Javac command to implement (not null)
     * @param ajc the AjcTask to adapt (not null)
     * @param destDir the File class destination directory (may be null)
     * @return null if no error, or String error otherwise
     */
    public String setupAjc(Javac javac) {        
        if (null == javac) {
            return "null javac";
        }
        AjcTask ajc = this;
        // no null checks b/c AjcTask handles null input gracefully
        ajc.setProject(javac.getProject());
        ajc.setLocation(javac.getLocation());
        ajc.setTaskName("javac-iajc");
        
        ajc.setDebug(javac.getDebug());
        ajc.setDeprecation(javac.getDeprecation());
        ajc.setFailonerror(javac.getFailonerror());
        final boolean fork = javac.isForkedJavac();
        ajc.setFork(fork);        
        if (fork) {
            ajc.setMaxmem(javac.getMemoryMaximumSize());
        }
        ajc.setNowarn(javac.getNowarn()); 
        ajc.setListFileArgs(javac.getListfiles());
        ajc.setVerbose(javac.getVerbose());               
        ajc.setTarget(javac.getTarget());        
        ajc.setSource(javac.getSource());        
        ajc.setEncoding(javac.getEncoding());
        File javacDestDir = javac.getDestdir();
        if (null != javacDestDir) {
            ajc.setDestdir(javacDestDir);
            // filter requires dest dir
            // mimic Javac task's behavior in copying resources,
            ajc.setSourceRootCopyFilter("**/CVS/*,**/*.java,**/*.aj");
        }        
        ajc.setBootclasspath(javac.getBootclasspath());
        ajc.setExtdirs(javac.getExtdirs());
        ajc.setClasspath(javac.getClasspath());        
        // ignore srcDir -- all files picked up in recalculated file list
//      ajc.setSrcDir(javac.getSrcdir());        
        ajc.addFiles(javac.getFileList());
        // arguments can override the filter, add to paths, override options
        ajc.readArguments(javac.getCurrentCompilerArgs());
        
        return null;
    }
    
   /**
     * Find aspectjtools.jar on the task or system classpath.
     * Accept <code>aspectj{-}tools{...}.jar</code>
     * mainly to support build systems using maven-style 
     * re-naming 
     * (e.g., <code>aspectj-tools-1.1.0.jar</code>.
     * Note that we search the task classpath first, 
     * though an entry on the system classpath would be loaded first,
     * because it seems more correct as the more specific one.
     * @return readable File for aspectjtools.jar, or null if not found.
     */            
    public static File findAspectjtoolsJar() {
        File result = null;
        ClassLoader loader = AjcTask.class.getClassLoader();
        if (loader instanceof AntClassLoader) {
            AntClassLoader taskLoader = (AntClassLoader) loader;
            String cp = taskLoader.getClasspath();
            String[] cps = LangUtil.splitClasspath(cp);
            for (int i = 0; (i < cps.length) && (null == result); i++) {
                result = isAspectjtoolsjar(cps[i]);
            }
        }
        if (null == result) {
            final Path classpath = Path.systemClasspath;
            final String[] paths = classpath.list();
            for (int i = 0; (i < paths.length) && (null == result); i++) {
                result = isAspectjtoolsjar(paths[i]);
            }
        }
        return (null == result? null : result.getAbsoluteFile());
    }
    
    /** @return File if readable jar with aspectj tools name, or null */
    private static File isAspectjtoolsjar(String path) {
        if (null == path) {
            return null;
        }
        final String prefix = "aspectj";        
        final String infix = "tools";        
        final String altInfix = "-tools";        
        final String suffix = ".jar";
        final int prefixLength = 7; // prefix.length();
        final int minLength = 16;
        // prefixLength + infix.length() + suffix.length();        
        if (!path.endsWith(suffix)) {
            return null;
        }
        int loc = path.lastIndexOf(prefix);
        if ((-1 != loc) && ((loc + minLength) <= path.length())) {
            String rest = path.substring(loc+prefixLength);
            if (-1 != rest.indexOf(File.pathSeparator)) {
                return null;
            }
            if (rest.startsWith(infix)
                || rest.startsWith(altInfix)) {
                File result = new File(path);
                if (result.canRead() && result.isFile()) {
                    return result;
                }
            }
        }        
        return null;
    }

    /** 
     * Maximum length (in chars) of command line 
     * before converting to an argfile when forking 
     */
    private static final int MAX_COMMANDLINE = 4096;
    
    private static final File DEFAULT_DESTDIR = new File(".") {
        public String toString() {
            return "(no destination dir specified)";
        }
    };
    
    /** do not throw BuildException on fail/abort message with usage */
    private static final String USAGE_SUBSTRING = "AspectJ-specific options";

    /** valid -X[...] options other than -Xlint variants */
    private static final List VALID_XOPTIONS;

	/** valid warning (-warn:[...]) variants */
    private static final List VALID_WARNINGS;
    
	/** valid debugging (-g:[...]) variants */
    private static final List VALID_DEBUG;

	/** 
	 * -Xlint variants (error, warning, ignore)
	 * @see org.aspectj.weaver.Lint 
	 */
    private static final List VALID_XLINT;

    public static final String COMMAND_EDITOR_NAME
        = AjcTask.class.getName() + ".COMMAND_EDITOR";

    static final String[] TARGET_INPUTS = new String [] 
    { "1.1", "1.2", "1.3", "1.4" };
    static final String[] SOURCE_INPUTS = new String [] 
    { "1.3", "1.4" };
    static final String[] COMPLIANCE_INPUTS = new String [] 
    { "-1.3", "-1.4" };

    private static final ICommandEditor COMMAND_EDITOR;
            
    static {
        String[] xs = new String[] 
            {   "serializableAspects", "incrementalFile", "lazyTjp"
            	//, "targetNearSource", "OcodeSize",
                 };
        VALID_XOPTIONS = Collections.unmodifiableList(Arrays.asList(xs));

        xs = new String[]
        	{"constructorName", "packageDefaultMethod", "deprecation",
        		"maskedCatchBlocks", "unusedLocals", "unusedArguments",
        		"unusedImports", "syntheticAccess", "assertIdentifier", "none" };
        VALID_WARNINGS = Collections.unmodifiableList(Arrays.asList(xs));

        xs = new String[] {"none", "lines", "vars", "source" };
        VALID_DEBUG = Collections.unmodifiableList(Arrays.asList(xs));
        
        
        xs = new String[] { "error", "warning", "ignore"};
        VALID_XLINT = Collections.unmodifiableList(Arrays.asList(xs));
    
        ICommandEditor editor = null;
        try {
            String editorClassName = System.getProperty(COMMAND_EDITOR_NAME);
            if (null != editorClassName) {
                ClassLoader cl = AjcTask.class.getClassLoader();
                Class editorClass = cl.loadClass(editorClassName);
                editor = (ICommandEditor) editorClass.newInstance();
            }
        } catch (Throwable t) {
            System.err.println("Warning: unable to load command editor");
            t.printStackTrace(System.err);
        }
        COMMAND_EDITOR = editor;
    }
	// ---------------------------- state and Ant interface thereto
    private boolean verbose;
    private boolean listFileArgs;
    private boolean failonerror;
    private boolean fork;
    private String maxMem;
	
	// ------- single entries dumped into cmd
    protected GuardedCommand cmd;
	
	// ------- lists resolved in addListArgs() at execute() time
    private Path srcdir;
    private Path injars;
    private Path inpath;
    private Path classpath;
    private Path bootclasspath;
    private Path forkclasspath;
    private Path extdirs;
    private Path aspectpath;
    private Path argfiles;
    private List ignored;
    private Path sourceRoots;
    private File xweaveDir;
    private String xdoneSignal;
    
    // ----- added by adapter - integrate better?
    private List /* File */ adapterFiles;
    private String[] adapterArguments;

    private IMessageHolder messageHolder;
    private ICommandEditor commandEditor;

    // -------- resource-copying
    /** true if copying injar non-.class files to the output jar */
    private boolean copyInjars;
    private boolean copyInpath;
    
    /** non-null if copying all source root files but the filtered ones */
    private String sourceRootCopyFilter;
    
    /** directory sink for classes */
    private File destDir;
    
    /** zip file sink for classes */
    private File outjar;
    
    /** track whether we've supplied any temp outjar */
    private boolean outjarFixedup;

    /** 
     * When possibly copying resources to the output jar,
     * pass ajc a fake output jar to copy from,
     * so we don't change the modification time of the output jar
     * when copying injars/inpath into the actual outjar.
     */
    private File tmpOutjar;

    private boolean executing;

    /** non-null only while executing in same vm */
    private Main main;
    
    /** true only when executing in other vm */
    private boolean executingInOtherVM;

    /** true if -incremental  */
    private boolean inIncrementalMode;

    /** true if -XincrementalFile (i.e, setTagFile)*/
    private boolean inIncrementalFileMode; 
    
    // also note MatchingTask grabs source files...
    
    public AjcTask() {
    	reset();
    }

	/** to use this same Task more than once (testing) */
    public void reset() { // XXX possible to reset MatchingTask?
        // need declare for "all fields initialized in ..."
        adapterArguments = null;
        adapterFiles = new ArrayList();
        argfiles = null;
        executing = false;
        aspectpath = null;
        bootclasspath = null;
        classpath = null;
        cmd = new GuardedCommand();
        copyInjars = false;
        copyInpath = false;
        destDir = DEFAULT_DESTDIR;
        executing = false;
        executingInOtherVM = false;
        extdirs = null;
        failonerror = true;  // non-standard default
        forkclasspath = null;
        inIncrementalMode = false;
        inIncrementalFileMode = false;
        ignored = new ArrayList();
        injars = null;
        inpath = null;
        listFileArgs = false;
        maxMem = null;
        messageHolder = null;
        outjar = null;
        sourceRootCopyFilter = null;
        sourceRoots = null;
        srcdir = null;
        tmpOutjar = null;
        verbose = false;
        xweaveDir = null;
        xdoneSignal = null;
    }

    protected void ignore(String ignored) {
        this.ignored.add(ignored + " at " + getLocation());
    }
    
    //---------------------- option values

    // used by entries with internal commas
    protected String validCommaList(String list, List valid, String label) {
    	return validCommaList(list, valid, label, valid.size());
    }
    
    protected String validCommaList(String list, List valid, String label, int max) {
    	StringBuffer result = new StringBuffer();
    	StringTokenizer st = new StringTokenizer(list, ",");
		int num = 0;
    	while (st.hasMoreTokens()) {
			String token = st.nextToken().trim();
			num++;
			if (num > max) {
				ignore("too many entries for -" 
					+ label 
					+ ": " 
					+ token); 
				break;
			}
			if (!valid.contains(token)) {
				ignore("bad commaList entry for -" 
					+ label 
					+ ": " 
					+ token); 
			} else {
				if (0 < result.length()) {
					result.append(",");
				}
				result.append(token);
			}
    	}
    	return (0 == result.length() ? null : result.toString());
    }
    
    public void setIncremental(boolean incremental) {  
        cmd.addFlag("-incremental", incremental);
        inIncrementalMode = incremental;
    }

    public void setHelp(boolean help) {  
        cmd.addFlag("-help", help);
    }

    public void setVersion(boolean version) {  
    	cmd.addFlag("-version", version);
    }

    public void setXNoweave(boolean noweave) {  
        cmd.addFlag("-XnoWeave", noweave);
    }

    public void setXReweavable(boolean reweavable) {
    	cmd.addFlag("-Xreweavable",reweavable);
    }

    public void setNowarn(boolean nowarn) {  
        cmd.addFlag("-nowarn", nowarn);
    }

    public void setDeprecation(boolean deprecation) {  
        cmd.addFlag("-deprecation", deprecation);
    }

    public void setWarn(String warnings) {
    	warnings = validCommaList(warnings, VALID_WARNINGS, "warn");
        cmd.addFlag("-warn:" + warnings, (null != warnings));
    }

    public void setDebug(boolean debug) {
        cmd.addFlag("-g", debug);
    }
    
    public void setDebugLevel(String level) {
    	level = validCommaList(level, VALID_DEBUG, "g");
        cmd.addFlag("-g:" + level, (null != level));
    }

    public void setEmacssym(boolean emacssym) {
        cmd.addFlag("-emacssym", emacssym);
    }

	/** 
	 * -Xlint - set default level of -Xlint messages to warning
	 * (same as </code>-Xlint:warning</code>)
	 */
	public void setXlintwarnings(boolean xlintwarnings) {
        cmd.addFlag("-Xlint", xlintwarnings);
	}
	
	/** -Xlint:{error|warning|info} - set default level for -Xlint messages
	 * @param xlint the String with one of error, warning, ignored 
	 */
    public void setXlint(String xlint) {
    	xlint = validCommaList(xlint, VALID_XLINT, "Xlint", 1);
        cmd.addFlag("-Xlint:" + xlint, (null != xlint));
    }

	/** 
	 * -Xlintfile {lint.properties} - enable or disable specific forms 
	 * of -Xlint messages based on a lint properties file
	 *  (default is 
	 * <code>org/aspectj/weaver/XLintDefault.properties</code>)
	 * @param xlintFile the File with lint properties
	 */
    public void setXlintfile(File xlintFile) { 
        cmd.addFlagged("-Xlintfile", xlintFile.getAbsolutePath());
    }

    public void setPreserveAllLocals(boolean preserveAllLocals) {  
        cmd.addFlag("-preserveAllLocals", preserveAllLocals);
    }

    public void setNoImportError(boolean noImportError) {  
        cmd.addFlag("-warn:-unusedImport", noImportError);
    }

    public void setEncoding(String encoding) {   
        cmd.addFlagged("-encoding", encoding);
    }

    public void setLog(File file) {
        cmd.addFlagged("-log", file.getAbsolutePath());        
    }
    
    public void setProceedOnError(boolean proceedOnError) {  
        cmd.addFlag("-proceedOnError", proceedOnError);
    }

    public void setVerbose(boolean verbose) {  
        cmd.addFlag("-verbose", verbose);
        this.verbose = verbose;
    }
    
    public void setListFileArgs(boolean listFileArgs) { 
        this.listFileArgs = listFileArgs;
    }

    public void setReferenceInfo(boolean referenceInfo) {  
        cmd.addFlag("-referenceInfo", referenceInfo);
    }

    public void setProgress(boolean progress) {  
        cmd.addFlag("-progress", progress);
    }

    public void setTime(boolean time) {  
        cmd.addFlag("-time", time);
    }

    public void setNoExit(boolean noExit) {  
        cmd.addFlag("-noExit", noExit);
    }

    public void setFailonerror(boolean failonerror) {  
        this.failonerror = failonerror;
    }

    /**
     * @return true if fork was set
     */
    public boolean isForked() {
        return fork;
    }
    
    public void setFork(boolean fork) {  
        this.fork = fork;
    }
    
    public void setMaxmem(String maxMem) {
        this.maxMem = maxMem;
    }

	// ----------------
    public void setTagFile(File file) {
        inIncrementalMode = true;
        cmd.addFlagged(Main.CommandController.TAG_FILE_OPTION,
	        file.getAbsolutePath());
        inIncrementalFileMode = true;
    }
    
    public void setOutjar(File file) {
        if (DEFAULT_DESTDIR != destDir) {
            String e = "specifying both output jar ("
                + file 
                + ") and destination dir ("
                + destDir
                + ")";
            throw new BuildException(e);
        }
        outjar = file;
        outjarFixedup = false;
        tmpOutjar = null;
    }

    public void setDestdir(File dir) {
        if (null != outjar) {
            String e = "specifying both output jar ("
                + outjar 
                + ") and destination dir ("
                + dir
                + ")";
            throw new BuildException(e);
        }
        cmd.addFlagged("-d", dir.getAbsolutePath());
        destDir = dir;        
    }
    
    /**
     * @param input a String in TARGET_INPUTS
     */
    public void setTarget(String input) {
        String ignore = cmd.addOption("-target", TARGET_INPUTS, input);
        if (null != ignore) {
            ignore(ignore);
        }
    }
    
    /** 
     * Language compliance level.
     * If not set explicitly, eclipse default holds.
     * @param input a String in COMPLIANCE_INPUTS
     */
    public void setCompliance(String input) {
        String ignore = cmd.addOption(null, COMPLIANCE_INPUTS, input);
        if (null != ignore) {
            ignore(ignore);
        }
    }
    
    /** 
     * Source compliance level.
     * If not set explicitly, eclipse default holds.
     * @param input a String in SOURCE_INPUTS
     */
    public void setSource(String input) {
        String ignore = cmd.addOption("-source", SOURCE_INPUTS, input);
        if (null != ignore) {
            ignore(ignore);
        }
    }
    /**
     * Flag to copy all non-.class contents of injars
     * to outjar after compile completes.
     * Requires both injars and outjar.
     * @param doCopy
     */
    public void setCopyInjars(boolean doCopy){
        ignore("copyInJars");
        log("copyInjars not required since 1.1.1.\n", Project.MSG_WARN);
        //this.copyInjars = doCopy;
    }

    
    /**
     * Option to copy all files from
     * all source root directories
     * except those specified here.
     * If this is specified and sourceroots are specified,
     * then this will copy all files except 
     * those specified in the filter pattern.
     * Requires sourceroots.
     * 
     * @param filter a String acceptable as an excludes
     *        filter for an Ant Zip fileset.
     */
    public void setSourceRootCopyFilter(String filter){
        this.sourceRootCopyFilter = filter;
    }

    public void setX(String input) {  // ajc-only eajc-also docDone
        StringTokenizer tokens = new StringTokenizer(input, ",", false);
        while (tokens.hasMoreTokens()) {
            String token = tokens.nextToken().trim();
            if (1 < token.length()) {
                if (VALID_XOPTIONS.contains(token)) {
                    cmd.addFlag("-X" + token, true); 
                } else {
                    ignore("-X" + token);
                }
            }
        }
    }

    public void setXDoneSignal(String doneSignal) {
        this.xdoneSignal = doneSignal;
    }

    /** direct API for testing */
    public void setMessageHolder(IMessageHolder holder) {
        this.messageHolder = holder;
    }
    
    /** 
     * Setup custom message handling.
     * @param className the String fully-qualified-name of a class
     *          reachable from this object's class loader,
     *          implementing IMessageHolder, and 
     *          having a public no-argument constructor.
     * @throws BuildException if unable to create instance of className
     */
    public void setMessageHolderClass(String className) {
        try {
            Class mclass = Class.forName(className);
            IMessageHolder holder = (IMessageHolder) mclass.newInstance();
            setMessageHolder(holder);
        } catch (Throwable t) {
            String m = "unable to instantiate message holder: " + className;
            throw new BuildException(m, t);
        }
    }
    
    /** direct API for testing */
    public void setCommandEditor(ICommandEditor editor) {
        this.commandEditor = editor;
    }
    
    /**
     * Setup command-line filter.
     * To do this staticly, define the environment variable
     * <code>org.aspectj.tools.ant.taskdefs.AjcTask.COMMAND_EDITOR</code>
     * with the <code>className</code> parameter.
     * @param className the String fully-qualified-name of a class
     *          reachable from this object's class loader,
     *          implementing ICommandEditor, and 
     *          having a public no-argument constructor.
     * @throws BuildException if unable to create instance of className
     */
    public void setCommandEditorClass(String className) { // skip Ant interface?
        try {
            Class mclass = Class.forName(className);
            setCommandEditor((ICommandEditor) mclass.newInstance());
        } catch (Throwable t) {
            String m = "unable to instantiate command editor: " + className;
            throw new BuildException(m, t);
        }
    }
    //---------------------- Path lists

    /**
     * Add path elements to source path and return result.
     * Elements are added even if they do not exist.
     * @param source the Path to add to - may be null
     * @param toAdd the Path to add - may be null
     * @return the (never-null) Path that results
     */
    protected Path incPath(Path source, Path toAdd) {
        if (null == source) {
            source = new Path(project); 
        }
        if (null != toAdd) {
            source.append(toAdd);
        }
        return source;
    }

    public void setSourcerootsref(Reference ref) {
        createSourceRoots().setRefid(ref);
    }
    
    public void setSourceRoots(Path roots) {
        sourceRoots = incPath(sourceRoots, roots);
    }

    public Path createSourceRoots() {
        if (sourceRoots == null) {
            sourceRoots = new Path(project);
        }
        return sourceRoots.createPath();
    }        
	    
    public void setXWeaveDir(File file) {
        if ((null != file) && file.isDirectory()
            && file.canRead()) {
            xweaveDir = file;
        }
    }       

    public void setInjarsref(Reference ref) {
        createInjars().setRefid(ref);
    }
    
    public void setInpathref(Reference ref) {
    	createInpath().setRefid(ref);
    }
    
    public void setInjars(Path path) {
        injars = incPath(injars, path);
    }
    
    public void setInpath(Path path) {
    	inpath = incPath(inpath,path);
    }

    public Path createInjars() {
        if (injars == null) {
            injars = new Path(project);
        }
        return injars.createPath();
    }        
    
    public Path createInpath() {
    	if (inpath == null) {
    		inpath = new Path(project);
    	}
    	return inpath.createPath();
    }
    
    public void setClasspath(Path path) {
        classpath = incPath(classpath, path);
    }

    public void setClasspathref(Reference classpathref) {
        createClasspath().setRefid(classpathref);
    }
        
    public Path createClasspath() {
        if (classpath == null) {
            classpath = new Path(project);
        }
        return classpath.createPath();
    }        

    public void setBootclasspath(Path path) {
        bootclasspath = incPath(bootclasspath, path);  
    }
    
    public void setBootclasspathref(Reference bootclasspathref) {
        createBootclasspath().setRefid(bootclasspathref);
    }
    
    public Path createBootclasspath() {
        if (bootclasspath == null) {
            bootclasspath = new Path(project);
        }
        return bootclasspath.createPath();
    }        
    
    public void setForkclasspath(Path path) {
        forkclasspath = incPath(forkclasspath, path);  
    }
    
    public void setForkclasspathref(Reference forkclasspathref) {
        createForkclasspath().setRefid(forkclasspathref);
    }
    
    public Path createForkclasspath() {
        if (forkclasspath == null) {
            forkclasspath = new Path(project);
        }
        return forkclasspath.createPath();
    }        
    
    public void setExtdirs(Path path) {
        extdirs = incPath(extdirs, path);
    }

    public void setExtdirsref(Reference ref) {
        createExtdirs().setRefid(ref);
    }
        
    public Path createExtdirs() {
        if (extdirs == null) {
            extdirs = new Path(project);
        }
        return extdirs.createPath();
    }        
   

    public void setAspectpathref(Reference ref) {
        createAspectpath().setRefid(ref);
    }

    public void setAspectpath(Path path) {
        aspectpath = incPath(aspectpath, path);
    }

    public Path createAspectpath() {
        if (aspectpath == null) {
            aspectpath = new Path(project);
        }
        return aspectpath.createPath();
    }        

    public void setSrcDir(Path path) {
        srcdir = incPath(srcdir, path);
    }

    public Path createSrc() {
        return createSrcdir();
    }

    public Path createSrcdir() {
        if (srcdir == null) {
            srcdir = new Path(project);
        }
        return srcdir.createPath();
    }
    
    /** @return true if in incremental mode (command-line or file) */
    public boolean isInIncrementalMode() {
        return inIncrementalMode;
    }

    /** @return true if in incremental file mode */
    public boolean isInIncrementalFileMode() {
        return inIncrementalFileMode;
    }

    public void setArgfilesref(Reference ref) {
        createArgfiles().setRefid(ref);
    }
    
    public void setArgfiles(Path path) { // ajc-only eajc-also docDone
        argfiles = incPath(argfiles, path);
    }

    public Path createArgfiles() {
        if (argfiles == null) {
            argfiles = new Path(project);
        }
        return argfiles.createPath();
    }
                
    // ------------------------------ run
  
    /**
     * Compile using ajc per settings.
     * @exception BuildException if the compilation has problems
     *             or if there were compiler errors and failonerror is true.
     */
    public void execute() throws BuildException {
        if (executing) {
            throw new IllegalStateException("already executing");
        } else {
            executing = true;
        }
        setupOptions();
        verifyOptions();
        try {
            String[] args = makeCommand();
            if (verbose || listFileArgs) { // XXX if listFileArgs, only do that
                log("ajc " + Arrays.asList(args), Project.MSG_VERBOSE);
            }
            if (!fork) {
                executeInSameVM(args);
            } else { // when forking, Adapter handles failonerror
                executeInOtherVM(args);
            }
        } catch (BuildException e) {
            throw e;
        } catch (Throwable x) {
        	System.err.println(Main.renderExceptionForUser(x));        	
            throw new BuildException("IGNORE -- See " 
            	+ LangUtil.unqualifiedClassName(x) 
            	+ " rendered to System.err");
        } finally {
            executing = false;
            if (null != tmpOutjar) {
                tmpOutjar.delete();
            }
        }        
    }

    /** 
     * Halt processing.
     * This tells main in the same vm to quit.
     * It fails when running in forked mode.
     * @return true if not in forked mode
     *         and main has quit or been told to quit
     */
    public boolean quit() {
        if (executingInOtherVM) {
            return false;
        }
        Main me = main;
        if (null != me) {
            me.quit();
        }
        return true;
    }

    // package-private for testing
    String[] makeCommand() {
        ArrayList result = new ArrayList();
        if (0 < ignored.size()) {
            for (Iterator iter = ignored.iterator(); iter.hasNext();) {
                log("ignored: " + iter.next(), Project.MSG_INFO);                   
            }
        }
        // when copying resources, use temp jar for class output
        // then copy temp jar contents and resources to output jar
        if ((null != outjar) && !outjarFixedup) {
            if (copyInjars || copyInpath || (null != sourceRootCopyFilter)) {
                String path = outjar.getAbsolutePath();
                int len = FileUtil.zipSuffixLength(path);
                if (len < 1) {
                    log("not copying resources - weird outjar: " + path);
                } else {
                    path = path.substring(0, path.length()-len) + ".tmp.jar";
                    tmpOutjar = new File(path);
                }
            }
            if (null == tmpOutjar) {                
                cmd.addFlagged("-outjar", outjar.getAbsolutePath());        
            } else {
                cmd.addFlagged("-outjar", tmpOutjar.getAbsolutePath());        
            }
            outjarFixedup = true;
        }

        result.addAll(cmd.extractArguments());        
        addListArgs(result);

        String[] command = (String[]) result.toArray(new String[0]);
        if (null != commandEditor) {
            command = commandEditor.editCommand(command);
        } else if (null != COMMAND_EDITOR) {
            command = COMMAND_EDITOR.editCommand(command);
        }
        return command;
    }   

    /**
     * Create any pseudo-options required to implement
     * some of the macro options
     * @throws BuildException if options conflict
     */
    protected void setupOptions() {
        if (null != xweaveDir) {
           if (DEFAULT_DESTDIR != destDir) {
               throw new BuildException("weaveDir forces destdir"); 
           }
           if (null != outjar) {
               throw new BuildException("weaveDir forces outjar"); 
           }
           if (null != injars) {
               throw new BuildException("weaveDir incompatible with injars now"); 
           }
           if (null != inpath) {
               throw new BuildException("weaveDir incompatible with inpath now"); 
           }
           
           File injar = zipDirectory(xweaveDir);
           setInjars(new Path(getProject(), injar.getAbsolutePath()));
           setDestdir(xweaveDir);
        }
    }
    
    protected File zipDirectory(File dir) {
        File tempDir = new File(".");
        try {
            tempDir = File.createTempFile("AjcTest", ".tmp");
            tempDir.mkdirs();
            tempDir.deleteOnExit(); // XXX remove zip explicitly..
        } catch (IOException e) {
            // ignore
        }
//        File result = new File(tempDir, 
        String filename = "AjcTask-" 
            + System.currentTimeMillis() 
            + ".zip";
        File result = new File(filename);
        Zip zip = new Zip();
        zip.setProject(getProject());
        zip.setDestFile(result);
        zip.setTaskName(getTaskName() + " - zip");
        FileSet fileset = new FileSet();
        fileset.setDir(dir);
        zip.addFileset(fileset);
        zip.execute();
        Delete delete = new Delete();
        delete.setProject(getProject());
        delete.setTaskName(getTaskName() + " - delete");
        delete.setDir(dir);
        delete.execute();
        Mkdir mkdir = new Mkdir();
        mkdir.setProject(getProject());
        mkdir.setTaskName(getTaskName() + " - mkdir");
        mkdir.setDir(dir);
        mkdir.execute();
        return result;
    }
    
    /**
     * @throw BuildException if options conflict
     */
    protected void verifyOptions() {
        StringBuffer sb = new StringBuffer();
        if (fork && isInIncrementalMode() && !isInIncrementalFileMode()) {
            sb.append("can fork incremental only using tag file.\n");
        }
        if ((null != sourceRootCopyFilter) && (null == outjar) 
        	&& (DEFAULT_DESTDIR == destDir)) {        	
            final String REQ = " requires dest dir or output jar.\n";
            sb.append("sourceRootCopyFilter");
            sb.append(REQ);
        }
        if (0 < sb.length()) {
            throw new BuildException(sb.toString());
        }
    }

    /**
     * Run the compile in the same VM by
     * loading the compiler (Main), 
     * setting up any message holders,
     * doing the compile,
     * and converting abort/failure and error messages
     * to BuildException, as appropriate.
     * @throws BuildException if abort or failure messages
     *         or if errors and failonerror.
     * 
     */
    protected void executeInSameVM(String[] args) {
        if (null != maxMem) {
            log("maxMem ignored unless forked: " + maxMem, Project.MSG_WARN);
        }
        IMessageHolder holder = messageHolder;
        int numPreviousErrors;
        if (null == holder) {
            MessageHandler mhandler = new MessageHandler(true);
            final IMessageHandler delegate 
                = verbose ? MessagePrinter.VERBOSE: MessagePrinter.TERSE;
            mhandler.setInterceptor(delegate);
            if (!verbose) {
                mhandler.ignore(IMessage.INFO);
            }
            holder = mhandler;
            numPreviousErrors = 0;
        } else {
            numPreviousErrors = holder.numMessages(IMessage.ERROR, true);
        }
        {
            Main newmain = new Main();
            newmain.setHolder(holder);
            newmain.setCompletionRunner(new Runnable() {
                public void run() {
                    doCompletionTasks();
                }
            });
            if (null != main) {
                MessageUtil.fail(holder, "still running prior main");
                return;
            }
            main = newmain;          
        }
        try {
            main.runMain(args, false);
        } finally {
            main = null;
        }
        if (failonerror) {
            int errs = holder.numMessages(IMessage.ERROR, false);
            errs -= numPreviousErrors;
            if (0 < errs) {
                String m =  errs + " errors";
                MessageUtil.print(System.err, holder, "", MessageUtil.MESSAGE_ALL, MessageUtil.PICK_ERROR, true);
                throw new BuildException(m); 
            }
        } 
        // Throw BuildException if there are any fail or abort
        // messages.
        // The BuildException message text has a list of class names
        // for the exceptions found in the messages, or the
        // number of fail/abort messages found if there were
        // no exceptions for any of the fail/abort messages.
        // The interceptor message handler should have already
        // printed the messages, including any stack traces.
        // HACK: this ignores the Usage message
        {
            IMessage[] fails = holder.getMessages(IMessage.FAIL, true);
            if (!LangUtil.isEmpty(fails)) {
                StringBuffer sb = new StringBuffer();
                String prefix = "fail due to ";
                int numThrown = 0;
                for (int i = 0; i < fails.length; i++) {
                    String message = fails[i].getMessage();
                    if (LangUtil.isEmpty(message)) {
                        message = "<no message>";
                    } else if (-1 != message.indexOf(USAGE_SUBSTRING)) {
                        continue;
                    }
                    Throwable t = fails[i].getThrown();
                    if (null != t) {
                        numThrown++;
                        sb.append(prefix);
                        sb.append(LangUtil.unqualifiedClassName(t.getClass()));
                        String thrownMessage = t.getMessage();
                        if (!LangUtil.isEmpty(thrownMessage)) {
                            sb.append(" \"" + thrownMessage + "\"");
                        }
                    } 
                    sb.append("\"" + message + "\"");
                    prefix = ", ";
                }
                if (0 < sb.length()) {
                    sb.append(" (" + numThrown + " exceptions)");
                    throw new BuildException(sb.toString());
                }
            }
        }
    }
    
    /**
     * Execute in a separate VM.
     * Differences from normal same-VM execution:
     * <ul>
     * <li>ignores any message holder {class} set</li>
     * <li>No resource-copying between interative runs</li>
     * <li>failonerror fails when process interface fails 
     *     to return negative values</li>
     * </ul>
     * @param args String[] of the complete compiler command to execute
     * 
     * @see DefaultCompilerAdapter#executeExternalCompile(String[], int)
     * @throws BuildException if ajc aborts (negative value)
     *         or if failonerror and there were compile errors.
     */
    protected void executeInOtherVM(String[] args) {
        if (null != messageHolder) {
            log("message holder ignored when forking: "
                + messageHolder.getClass().getName(), Project.MSG_WARN);
        }
        CommandlineJava javaCmd = new CommandlineJava();
        javaCmd.setClassname(org.aspectj.tools.ajc.Main.class.getName());
        
        final Path vmClasspath = javaCmd.createClasspath(getProject());
        {
            File aspectjtools = null;
            int vmClasspathSize = vmClasspath.size();
            if ((null != forkclasspath) 
                && (0 != forkclasspath.size())) {
                vmClasspath.addExisting(forkclasspath);
            } else {                
                aspectjtools = findAspectjtoolsJar();
                if (null != aspectjtools) {
                    vmClasspath.createPathElement().setLocation(aspectjtools);
                }
            }
            int newVmClasspathSize = vmClasspath.size();
            if (vmClasspathSize == newVmClasspathSize) {
                String m = "unable to find aspectjtools to fork - ";
                if (null != aspectjtools) {
                    m += "tried " + aspectjtools.toString();
                } else if (null != forkclasspath) {
                    m += "tried " + forkclasspath.toString();
                } else {
                    m += "define forkclasspath or put aspectjtools on classpath";
                }
                throw new BuildException(m);
            }
        }
        if (null != maxMem) {
            javaCmd.setMaxmemory(maxMem);
        }
        File tempFile = null;
        int numArgs = args.length;
        args = GuardedCommand.limitTo(args, MAX_COMMANDLINE, getLocation());
        if (args.length != numArgs) {
            tempFile = new File(args[1]);
        }
        try {
            String[] javaArgs = javaCmd.getCommandline();
            String[] both = new String[javaArgs.length + args.length];
            System.arraycopy(javaArgs,0,both,0,javaArgs.length);
            System.arraycopy(args,0,both,javaArgs.length,args.length);
            // try to use javaw instead on windows
            if (both[0].endsWith("java.exe")) {
                String path = both[0];
                path = path.substring(0, path.length()-4);
                path = path + "w.exe";
                File javaw = new File(path);
                if (javaw.canRead() && javaw.isFile()) {
                    both[0] = path;
                }
            }
            if (verbose) { // XXX also when ant is verbose...
                log("forking " + Arrays.asList(both));
            }
            int result = execInOtherVM(both);
            if (0 > result) {
                throw new BuildException("failure[" + result + "] running ajc");
            } else if (failonerror && (0 < result)) {
                throw new BuildException("compile errors: " + result);                
            }
            // when forking, do completion only at end and when successful
            doCompletionTasks();
        } finally {
            if (null != tempFile) {
                tempFile.delete();
            }
        }
    }
    
    /**
     * Execute in another process using the same JDK
     * and the base directory of the project. XXX correct?
     * @param args
     * @return
     */
    protected int execInOtherVM(String[] args) {
        try {
            Project project = getProject();
            LogStreamHandler handler = new LogStreamHandler(this,
                                 Project.MSG_INFO, Project.MSG_WARN);
            Execute exe = new Execute(handler);
            exe.setAntRun(project);
            exe.setWorkingDirectory(project.getBaseDir());
            exe.setCommandline(args);
            try {
                if (executingInOtherVM) {
                    String s = "already running in other vm?";
                    throw new BuildException(s, location);
                }
                executingInOtherVM = true;
                exe.execute();
            } finally {
                executingInOtherVM = false;
            }
            return exe.getExitValue();
        } catch (IOException e) {
            String m = "Error executing command " + Arrays.asList(args);
            throw new BuildException(m, e, location);
        }
    }

    // ------------------------------ setup and reporting
    /** @return null if path null or empty, String rendition otherwise */
    protected static void addFlaggedPath(String flag, Path path, List list) {
        if (!LangUtil.isEmpty(flag) 
            && ((null != path) && (0 < path.size()))) {
            list.add(flag);
            list.add(path.toString());
        }
    }
    
    /** 
     * Add to list any path or plural arguments.
     */
	protected void addListArgs(List list) throws BuildException {
        addFlaggedPath("-classpath", classpath, list);       
        addFlaggedPath("-bootclasspath", bootclasspath, list);
        addFlaggedPath("-extdirs", extdirs, list);
        addFlaggedPath("-aspectpath", aspectpath, list);
        addFlaggedPath("-injars", injars, list);
        addFlaggedPath("-inpath", inpath, list);
        addFlaggedPath("-sourceroots", sourceRoots, list);
        
        if (argfiles != null) {
            String[] files = argfiles.list();
            for (int i = 0; i < files.length; i++) {
                File argfile = project.resolveFile(files[i]);
                if (check(argfile, files[i], false, location)) {
                    list.add("-argfile");
                    list.add(argfile.getAbsolutePath());
                }
            }
        }
        if (srcdir != null) {
            // todo: ignore any srcdir if any argfiles and no explicit includes
            String[] dirs = srcdir.list();
            for (int i = 0; i < dirs.length; i++) {
                File dir = project.resolveFile(dirs[i]);
                check(dir, dirs[i], true, location);
                // relies on compiler to prune non-source files
                String[] files = getDirectoryScanner(dir).getIncludedFiles();
                for (int j = 0; j < files.length; j++) {
                    File file = new File(dir, files[j]);
                    if (FileUtil.hasSourceSuffix(file)) {
                        list.add(file.getAbsolutePath());
                    }
                }
            }
        }
        if (0 < adapterFiles.size()) {
            for (Iterator iter = adapterFiles.iterator(); iter.hasNext();) {
                File file = (File) iter.next();
                if (file.canRead() && FileUtil.hasSourceSuffix(file)) {
                    list.add(file.getAbsolutePath());
                } else {
                    log("skipping file: " + file, Project.MSG_WARN);
                }
            }
        }
	}    

    /** 
     * Throw BuildException unless file is valid.
     * @param file the File to check
     * @param name the symbolic name to print on error
     * @param isDir if true, verify file is a directory
     * @param loc the Location used to create sensible BuildException
     * @return
     * @throws BuildException unless file valid
     */
    protected final boolean check(File file, String name,
                                  boolean isDir, Location loc) {
        loc = loc != null ? loc : location;
        if (file == null) {
            throw new BuildException(name + " is null!", loc);
        }
        if (!file.exists()) {
            throw new BuildException(file + " doesn't exist!", loc);
        }
        if (isDir ^ file.isDirectory()) {
            String e = file + " should" + (isDir ? "" : "n't")  +
                " be a directory!";
            throw new BuildException(e, loc);
        }
        return true;
    }
    
    /** 
     * Called when compile or incremental compile is completing,
     * this completes the output jar or directory
     * by copying resources if requested.
     * Note: this is a callback run synchronously by the compiler.
     * That means exceptions thrown here are caught by Main.run(..)
     * and passed to the message handler.
     */
    protected void doCompletionTasks() {
        if (!executing) {
            throw new IllegalStateException("should be executing");
        }
        if (null != outjar) {
            completeOutjar();
        } else {
            completeDestdir();
        }
        if (null != xdoneSignal) {
            MessageUtil.info(messageHolder, xdoneSignal);
        }
    }
    
    /** 
     * Complete the destination directory
     * by copying resources from the source root directories
     * (if the filter is specified)
     * and non-.class files from the input jars 
     * (if XCopyInjars is enabled).
     */
    private void completeDestdir() {
        if (!copyInjars && (null == sourceRootCopyFilter)) {
            return;
        } else if ((destDir == DEFAULT_DESTDIR)
                    || !destDir.canWrite()) {
            String s = "unable to copy resources to destDir: " + destDir;
            throw new BuildException(s);
        }
        final Project project = getProject();
        if (copyInjars) { // XXXX remove as unused since 1.1.1
            if (null != inpath) {
                log("copyInjars does not support inpath.\n", Project.MSG_WARN);
            }
            String taskName = getTaskName() + " - unzip";
            String[] paths = injars.list();            
            if (!LangUtil.isEmpty(paths)) {
                PatternSet patternSet = new PatternSet();
                patternSet.setProject(project);        
                patternSet.setIncludes("**/*");
                patternSet.setExcludes("**/*.class");  
                for (int i = 0; i < paths.length; i++) {
                    Expand unzip = new Expand();
                    unzip.setProject(project);
                    unzip.setTaskName(taskName);
                    unzip.setDest(destDir);
                    unzip.setSrc(new File(paths[i]));
                    unzip.addPatternset(patternSet);
                    unzip.execute();
                }
            }
        }
        if ((null != sourceRootCopyFilter) && (null != sourceRoots)) {
            String[] paths = sourceRoots.list();
            if (!LangUtil.isEmpty(paths)) {
                Copy copy = new Copy();
                copy.setProject(project);
                copy.setTodir(destDir);
                for (int i = 0; i < paths.length; i++) {
                    FileSet fileSet = new FileSet();
                    fileSet.setDir(new File(paths[i]));
                    fileSet.setIncludes("**/*");
                    fileSet.setExcludes(sourceRootCopyFilter);  
                    copy.addFileset(fileSet);
                }
                copy.execute();
            }
        }        
    }
    
    /** 
     * Complete the output jar
     * by copying resources from the source root directories
     * if the filter is specified.
     * and non-.class files from the input jars if enabled.
     */
    private void completeOutjar() {
        if (((null == tmpOutjar) || !tmpOutjar.canRead()) 
            || (!copyInjars && (null == sourceRootCopyFilter))) {
            return;
        }
        Zip zip = new Zip();
        Project project = getProject();
        zip.setProject(project);        
        zip.setTaskName(getTaskName() + " - zip");
        zip.setDestFile(outjar);
        ZipFileSet zipfileset = new ZipFileSet();
        zipfileset.setProject(project);        
        zipfileset.setSrc(tmpOutjar);
        zipfileset.setIncludes("**/*.class");
        zip.addZipfileset(zipfileset);
        if (copyInjars) {
            String[] paths = injars.list();
            if (!LangUtil.isEmpty(paths)) {
                for (int i = 0; i < paths.length; i++) {
                    File jarFile = new File(paths[i]);
                    zipfileset = new ZipFileSet();
                    zipfileset.setProject(project);
                    zipfileset.setSrc(jarFile);
                    zipfileset.setIncludes("**/*");
                    zipfileset.setExcludes("**/*.class");  
                    zip.addZipfileset(zipfileset);
                }
            }
        }
        if ((null != sourceRootCopyFilter) && (null != sourceRoots)) {
            String[] paths = sourceRoots.list();
            if (!LangUtil.isEmpty(paths)) {
                for (int i = 0; i < paths.length; i++) {
                    File srcRoot = new File(paths[i]);
                    FileSet fileset = new FileSet();
                    fileset.setProject(project);
                    fileset.setDir(srcRoot);
                    fileset.setIncludes("**/*");
                    fileset.setExcludes(sourceRootCopyFilter);  
                    zip.addFileset(fileset);
                }
            }
        }        
        zip.execute();
    }
    
    // -------------------------- compiler adapter interface extras

    /**
     * Add specified source files.
     */
    void addFiles(File[] paths) {
        for (int i = 0; i < paths.length; i++) {
            addFile(paths[i]);
        }
    }
    /**
     * Add specified source file.
     */
    void addFile(File path) {
        if (null != path) {
            adapterFiles.add(path);
        }
    }

    /**
     * Read arguments in as if from a command line, 
     * mainly to support compiler adapter compilerarg subelement. 
     * 
     * @param args the String[] of arguments to read
     */
    public void readArguments(String[] args) { // XXX slow, stupid, unmaintainable
        if ((null == args) || (0 == args.length)) {
            return;
        }
        /** String[] wrapper with increment, error reporting */
        class Args {
            final String[] args;
            int index = 0;
            Args(String[] args) {
                this.args = args; // not null or empty
            }
            boolean hasNext() {
                return index < args.length;
            }
            String next() {
                String err = null;
                if (!hasNext()) {
                    err = "need arg for flag " + args[args.length-1];
                } else {
                    String s = args[index++];
                    if (null == s) {
                        err = "null value";                                            
                    } else {
                        s = s.trim();
                        if (0 == s.trim().length()) {
                            err = "no value";                                            
                        } else {
                            return s;
                        }
                    }
                }
                err += " at [" + index + "] of " + Arrays.asList(args);
                throw new BuildException(err);
            }
        } // class Args

        Args in = new Args(args);
        String flag;
        while (in.hasNext()) {
            flag = in.next();
            if ("-1.3".equals(flag)) {
                setCompliance(flag);
            } else if ("-1.4".equals(flag)) {
            	setCompliance(flag);
//            } else if ("-1.5".equals(flag)) {
//                setCompliance("1.5");
            } else if ("-argfile".equals(flag)) {
                setArgfiles(new Path(project, in.next()));
            } else if ("-aspectpath".equals(flag)) {
                setAspectpath(new Path(project, in.next()));
            } else if ("-classpath".equals(flag)) {
                setClasspath(new Path(project, in.next()));
			} else if ("-extdirs".equals(flag)) {
				setExtdirs(new Path(project, in.next()));
            } else if ("-Xcopyinjars".equals(flag)) {
                setCopyInjars(true); // ignored - will be flagged by setter
            } else if ("-g".equals(flag)) {
                setDebug(true);
            } else if (flag.startsWith("-g:")) {
                setDebugLevel(flag.substring(2));
            } else if ("-deprecation".equals(flag)) {
                setDeprecation(true);
            } else if ("-d".equals(flag)) {
                setDestdir(new File(in.next()));
            } else if ("-emacssym".equals(flag)) {
                setEmacssym(true);
            } else if ("-encoding".equals(flag)) {
                setEncoding(in.next());
            } else if ("-Xfailonerror".equals(flag)) {
                setFailonerror(true);
            } else if ("-fork".equals(flag)) {
                setFork(true);
            } else if ("-forkclasspath".equals(flag)) {
                setForkclasspath(new Path(project, in.next()));
            } else if ("-help".equals(flag)) {
                setHelp(true);
            } else if ("-incremental".equals(flag)) {
                setIncremental(true);
            } else if ("-injars".equals(flag)) {
                setInjars(new Path(project, in.next()));
            } else if ("-inpath".equals(flag)) {
            	setInpath(new Path(project,in.next()));
            } else if ("-Xlistfileargs".equals(flag)) {
                setListFileArgs(true);
            } else if ("-Xmaxmem".equals(flag)) {
                setMaxmem(in.next());
            } else if ("-Xmessageholderclass".equals(flag)) {
                setMessageHolderClass(in.next());
            } else if ("-noexit".equals(flag)) {
                setNoExit(true);
            } else if ("-noimport".equals(flag)) {
                setNoExit(true);
            } else if ("-noExit".equals(flag)) {
                setNoExit(true);
            } else if ("-noImportError".equals(flag)) {
                setNoImportError(true);
            } else if ("-noWarn".equals(flag)) {
                setNowarn(true);
            } else if ("-noexit".equals(flag)) {
                setNoExit(true);
            } else if ("-outjar".equals(flag)) {
                setOutjar(new File(in.next()));
            } else if ("-preserveAllLocals".equals(flag)) {
                setPreserveAllLocals(true);
            } else if ("-proceedOnError".equals(flag)) {
                setProceedOnError(true);
            } else if ("-progress".equals(flag)) {
                setProgress(true);
            } else if ("-referenceInfo".equals(flag)) {
                setReferenceInfo(true);
            } else if ("-source".equals(flag)) {
                setSource(in.next());
            } else if ("-Xsourcerootcopyfilter".equals(flag)) {
                setSourceRootCopyFilter(in.next());
            } else if ("-sourceroots".equals(flag)) {
                setSourceRoots(new Path(project, in.next()));
            } else if ("-Xsrcdir".equals(flag)) {
                setSrcDir(new Path(project, in.next()));
            } else if ("-Xtagfile".equals(flag)) {
                setTagFile(new File(in.next()));
            } else if ("-target".equals(flag)) {
                setTarget(in.next());
            } else if ("-time".equals(flag)) {
                setTime(true);
            } else if ("-time".equals(flag)) {
                setTime(true);
            } else if ("-verbose".equals(flag)) {
                setVerbose(true);
            } else if ("-version".equals(flag)) {
                setVersion(true);
            } else if ("-warn".equals(flag)) {
                setWarn(in.next());
            } else if (flag.startsWith("-warn:")) {
                setWarn(flag.substring(6));
            } else if ("-Xlint".equals(flag)) {
                setXlintwarnings(true);
            } else if (flag.startsWith("-Xlint:")) {
                setXlint(flag.substring(7));
            } else if ("-Xlintfile".equals(flag)) {
                setXlintfile(new File(in.next()));
            } else if ("-Xnoweave".equals(flag)) {
                setXNoweave(true);
			} else if ("-Xreweavable".equals(flag)) {
				setXReweavable(true);
            } else if (flag.startsWith("@")) {
                File file = new File(flag.substring(1));
                if (file.canRead()) {
                    setArgfiles(new Path(project, file.getPath()));
                } else {
                    ignore(flag);            
                }
            } else {
                File file = new File(flag);
                if (file.isFile() 
                	&& file.canRead() 
                	&& FileUtil.hasSourceSuffix(file)) {
                    addFile(file);
                } else {
                    ignore(flag);
                }
            }
        }
    }

/**
 * Commandline wrapper that 
 * only permits addition of non-empty values
 * and converts to argfile form if necessary.
 */
public static class GuardedCommand {
    Commandline command;
    //int size;

    static boolean isEmpty(String s) {
        return ((null == s) || (0 == s.trim().length()));
    }

    GuardedCommand() {
        command = new Commandline();
    }

    void addFlag(String flag, boolean doAdd) {
        if (doAdd && !isEmpty(flag)) {
            command.createArgument().setValue(flag);
            //size += 1 + flag.length();
        }
    }
    
    /** @return null if added or ignoreString otherwise */
    String addOption(String prefix, String[] validOptions, String input) {
        if (isEmpty(input)) {
            return null;
        }
        for (int i = 0; i < validOptions.length; i++) {
			if (input.equals(validOptions[i])) {
                if (isEmpty(prefix)) {
                    addFlag(input, true);
                } else {
                    addFlagged(prefix, input);
                }
                return null;
			}
		}
        return (null == prefix ? input : prefix + " " + input);
    }
    
    void addFlagged(String flag, String argument) {
        if (!isEmpty(flag) && !isEmpty(argument)) {
            command.addArguments(new String[] {flag, argument});
            //size += 1 + flag.length() + argument.length();
        }
    }
    
//    private void addFile(File file) {
//        if (null != file) {
//            String path = file.getAbsolutePath();
//            addFlag(path, true);
//        }
//    }
    
    List extractArguments() {
        ArrayList result = new ArrayList();
        String[] cmds = command.getArguments();
        if (!LangUtil.isEmpty(cmds)) {
            result.addAll(Arrays.asList(cmds));
        }
        return result;
    }

     /**
     * Adjust args for size if necessary by creating
     * an argument file, which should be deleted by the client
     * after the compiler run has completed.
     * @param max the int maximum length of the command line (in char)
     * @return the temp File for the arguments (if generated), 
     *         for deletion when done.
     * @throws IllegalArgumentException if max is negative
     */
    static String[] limitTo(String[] args, int max, 
        Location location) {       
        if (max < 0) {
            throw new IllegalArgumentException("negative max: " + max);
        }
        // sigh - have to count anyway for now
        int size = 0;
        for (int i = 0; (i < args.length) && (size < max); i++) {
            size += 1 + (null == args[i] ? 0 : args[i].length());    
        }
        if (size <= max) {
            return args;
        }
        File tmpFile = null;
        PrintWriter out = null;
        // adapted from DefaultCompilerAdapter.executeExternalCompile
        try {
            String userDirName = System.getProperty("user.dir");
            File userDir = new File(userDirName);
            tmpFile = File.createTempFile("argfile", "", userDir);
            out = new PrintWriter(new FileWriter(tmpFile));
            for (int i = 0; i < args.length; i++) {
                out.println(args[i]);
            }
            out.flush();
            return new String[] {"-argfile", tmpFile.getAbsolutePath()};
        } catch (IOException e) {
            throw new BuildException("Error creating temporary file", 
                                     e, location);
        } finally {
            if (out != null) {
                try {out.close();} catch (Throwable t) {}
            }
        }
    }     
}
}

