From ed96631f11efc4895a90a68f86f0e75eef8e9e2d Mon Sep 17 00:00:00 2001
From: wisberg <wisberg>
Date: Wed, 29 Oct 2003 08:56:38 +0000
Subject: revamped option handling: n-element options, collision/conflict
 detection/resolution via forcing as an attribute, option families.  More
 compiler options actually handled, and error messages for unhandled options.

---
 .../testing/harness/bridge/AbstractRunSpec.java    |   63 +
 .../testing/harness/bridge/CompilerRun.java        | 1510 +++++++++++++-------
 .../aspectj/testing/harness/bridge/Globals.java    |  114 +-
 .../org/aspectj/testing/util/options/Option.java   |  707 +++++++++
 .../org/aspectj/testing/util/options/Options.java  |  152 ++
 .../org/aspectj/testing/util/options/Values.java   |  714 +++++++++
 6 files changed, 2711 insertions(+), 549 deletions(-)
 create mode 100644 testing/src/org/aspectj/testing/util/options/Option.java
 create mode 100644 testing/src/org/aspectj/testing/util/options/Options.java
 create mode 100644 testing/src/org/aspectj/testing/util/options/Values.java

(limited to 'testing/src/org')

diff --git a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
index 8339a25ad..b1e3051e1 100644
--- a/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
+++ b/testing/src/org/aspectj/testing/harness/bridge/AbstractRunSpec.java
@@ -30,7 +30,10 @@ import org.aspectj.bridge.ISourceLocation;
 import org.aspectj.bridge.MessageHandler;
 import org.aspectj.bridge.MessageUtil;
 import org.aspectj.testing.run.IRunIterator;
+import org.aspectj.testing.util.*;
 import org.aspectj.testing.util.BridgeUtil;
+import org.aspectj.testing.util.options.*;
+import org.aspectj.testing.util.options.Option.InvalidInputException;
 import org.aspectj.testing.xml.IXmlWritable;
 import org.aspectj.testing.xml.SoftMessage;
 import org.aspectj.testing.xml.XMLWriter;
@@ -955,6 +958,66 @@ abstract public class AbstractRunSpec implements IRunSpec { // XXX use MessageHa
             verbose = toCopy.verbose;
         }
         
+        /** 
+         * Return any parent option accepted by validOptions,
+         * optionally removing the parent option.
+         * @param validOptions String[] of options to extract
+         * @param remove if true, then remove any parent option matched
+         * @return String[] containing any validOptions[i] in parentOptions
+         *          
+         */
+        public Values extractOptions(
+            Options validOptions, 
+            boolean remove,
+            StringBuffer errors) {
+            Values result = Values.EMPTY;
+            if (null == errors) {
+                errors = new StringBuffer();
+            }
+            if (null == validOptions) {
+                errors.append("null options");
+                return result;                
+            }
+            if (LangUtil.isEmpty(parentOptions)) {
+                return result;
+            }
+            boolean haveOption = false;
+            String[] parents = (String[]) parentOptions.toArray(new String[0]);
+            try {
+                result = validOptions.acceptInput(parents);
+            } catch (InvalidInputException e) {
+                errors.append(e.getFullMessage());
+                return result;
+            }
+            if (remove) {
+                Option.Value[] values = result.asArray();
+                for (int i = 0; i < values.length; i++) {
+                    Option.Value value = values[i];
+                    if (null == value) {
+                        continue;
+                    }
+                    final int max = i + value.option.numArguments();
+                    if (max > i) {
+                        if (max >= parents.length) {
+                            errors.append("expecting more args for "
+                                + value.option
+                                + " at ["
+                                + i
+                                + "]: "
+                                + Arrays.asList(parents));
+                            return result;
+                        }
+                        // XXX verify
+                        for (int j = i;  j < max ; j++) {
+                            parentOptions.remove(parents[j]);
+                        }
+                        i = max-1;
+                    }
+                }
+            }
+            return result;
+        }
+
         /** 
          * Return any parent option which has one of validOptions as a prefix,
          * optionally absorbing (removing) the parent option.
diff --git a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java
index 172f0de74..0f0385ac2 100644
--- a/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java
+++ b/testing/src/org/aspectj/testing/harness/bridge/CompilerRun.java
@@ -1,5 +1,6 @@
 /* *******************************************************************
- * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
+ * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC),
+ *               2003 Contributors.
  * All rights reserved. 
  * This program and the accompanying materials are made available 
  * under the terms of the Common Public License v1.0 
@@ -8,34 +9,22 @@
  *  
  * Contributors: 
  *     Xerox/PARC     initial implementation 
+ *     Wes Isberg     2003 updates
  * ******************************************************************/
 
 package org.aspectj.testing.harness.bridge;
 
-import org.aspectj.bridge.ICommand;
-import org.aspectj.bridge.IMessage;
-import org.aspectj.bridge.IMessageHandler;
-import org.aspectj.bridge.MessageUtil;
-import org.aspectj.bridge.ReflectionFactory;
+import java.io.*;
+import java.util.*;
+
+import org.aspectj.bridge.*;
 import org.aspectj.testing.ajde.CompileCommand;
-import org.aspectj.testing.run.IRunIterator;
-import org.aspectj.testing.run.IRunStatus;
-import org.aspectj.testing.run.WrappedRunIterator;
+import org.aspectj.testing.run.*;
 import org.aspectj.testing.taskdefs.AjcTaskCompileCommand;
-import org.aspectj.testing.xml.SoftMessage;
-import org.aspectj.testing.xml.XMLWriter;
-import org.aspectj.util.FileUtil;
-import org.aspectj.util.LangUtil;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-import java.util.ListIterator;
+import org.aspectj.testing.util.options.*;
+import org.aspectj.testing.util.options.Option.*;
+import org.aspectj.testing.xml.*;
+import org.aspectj.util.*;
 
 /**
  * Run the compiler once.
@@ -62,40 +51,37 @@ import java.util.ListIterator;
  * </ul>
  */
 public class CompilerRun implements IAjcRun {
-    static final String BUILDER_COMPILER = 
-        "org.aspectj.ajdt.internal.core.builder.Builder.Command";
-    static final String AJDE_COMPILER = CompileCommand.class.getName();
-    static final String AJCTASK_COMPILER 
-        = AjcTaskCompileCommand.class.getName();
-//    static final String JAVAC_COMPILER 
-//        = JavacCompileCommand.class.getName();
-
-	static final String[] RA_String = new String[0];
-    
+    //    static final String JAVAC_COMPILER 
+    //        = JavacCompileCommand.class.getName();
+
+    static final String[] RA_String = new String[0];
+
     static final String[] JAR_SUFFIXES = new String[] { ".jar", ".zip" };
 
-    static final String[] SOURCE_SUFFIXES 
-        = (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]);
-        
+    static final String[] SOURCE_SUFFIXES =
+        (String[]) FileUtil.SOURCE_SUFFIXES.toArray(new String[0]);
+
     /** specifications, set on construction */
     Spec spec;
 
     //------------ calculated during setup
-    /** get shared stuff during setup */       
+    /** get shared stuff during setup */
     Sandbox sandbox;
-    
+
     /** 
      * During run, these String are passed as the source and arg files to compile.
      * The list is set up in setupAjcRun(..), when arg files are prefixed with "@".
      */
-    final List /*String*/ arguments;
-    
+    final List /*String*/
+    arguments;
+
     /** 
      * During run, these String are collapsed and passed as the injar option.
      * The list is set up in setupAjcRun(..).
      */
-    final List /*String*/ injars;
-   
+    final List /*String*/
+    injars;
+
     private CompilerRun(Spec spec) {
         if (null == spec) {
             throw new IllegalArgumentException("null spec");
@@ -103,8 +89,8 @@ public class CompilerRun implements IAjcRun {
         this.spec = spec;
         arguments = new ArrayList();
         injars = new ArrayList();
-    }           
-    
+    }
+
     /** 
      * This checks that the spec is reasonable and does setup:
      * <ul>
@@ -124,72 +110,98 @@ public class CompilerRun implements IAjcRun {
      * for incremental tests.   Note that (as of this writing) the
      * compiler only handles source roots for incremental tests.
      * @param classesDir the File
-	 * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File)
+     * @see org.aspectj.testing.harness.bridge.AjcTest.IAjcRun#setup(File, File)
      * @throws AbortException containing IOException or IllegalArgumentException
      *          if the staging operations fail
-	 */
-	public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
+     */
+    public boolean setupAjcRun(Sandbox sandbox, Validator validator) {
 
         if (!validator.nullcheck(spec.getOptionsArray(), "localOptions")
             || !validator.nullcheck(sandbox, "sandbox")
             || !validator.nullcheck(spec.compiler, "compilerName")
             || !validator.canRead(Globals.F_aspectjrt_jar, "aspectjrt.jar")
-            || !validator.canRead(Globals.F_testingclient_jar, "testing-client.jar")
-            ) {
+            || !validator.canRead(
+                Globals.F_testingclient_jar,
+                "testing-client.jar")) {
             return false;
         }
-         
+
         this.sandbox = sandbox;
-        
+
         String rdir = spec.testSrcDirOffset;
         File testBaseSrcDir;
         if ((null == rdir) || (0 == rdir.length())) {
             testBaseSrcDir = sandbox.testBaseDir;
         } else {
-            testBaseSrcDir = new File(sandbox.testBaseDir, rdir); // XXX what if rdir is two levels deep?
-            if (!validator.canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) {
+            testBaseSrcDir = new File(sandbox.testBaseDir, rdir);
+            // XXX what if rdir is two levels deep?
+            if (!validator
+                .canReadDir(testBaseSrcDir, "sandbox.testBaseSrcDir")) {
                 return false;
             }
         }
         sandbox.setTestBaseSrcDir(testBaseSrcDir, this);
-        
-        
+
         // Sources come as relative paths - check read, copy if staging.
         // This renders paths absolute before run(RunStatusI) is called.
         // For a compile run to support relative paths + source base,
         // change so the run calculates the paths (differently when staging)
 
-        final String[] injarPaths; 
+        final String[] injarPaths;
         final String[] srcPaths;
-        { 
+        {
             final String[] paths = spec.getPathsArray();
-            srcPaths = LangUtil.endsWith(paths, CompilerRun.SOURCE_SUFFIXES, true);
-            injarPaths = LangUtil.endsWith(paths, CompilerRun.JAR_SUFFIXES, true);
-        } 
+            srcPaths =
+                LangUtil.endsWith(
+                    paths,
+                    CompilerRun.SOURCE_SUFFIXES,
+                    true);
+            injarPaths =
+                LangUtil.endsWith(paths, CompilerRun.JAR_SUFFIXES, true);
+        }
         // validate readable for sources
         if (!spec.badInput) {
             if (!validator.canRead(testBaseSrcDir, srcPaths, "sources")
                 || !validator.canRead(testBaseSrcDir, injarPaths, "injars")
-                || !validator.canRead(testBaseSrcDir, spec.argfiles, "argfiles")
-                || !validator.canRead(testBaseSrcDir, spec.classpath, "classpath")
-                || !validator.canRead(testBaseSrcDir, spec.aspectpath, "aspectpath")
-                || !validator.canRead(testBaseSrcDir, spec.sourceroots, "sourceroots")
-                ) {
+                || !validator.canRead(
+                    testBaseSrcDir,
+                    spec.argfiles,
+                    "argfiles")
+                || !validator.canRead(
+                    testBaseSrcDir,
+                    spec.classpath,
+                    "classpath")
+                || !validator.canRead(
+                    testBaseSrcDir,
+                    spec.aspectpath,
+                    "aspectpath")
+                || !validator.canRead(
+                    testBaseSrcDir,
+                    spec.sourceroots,
+                    "sourceroots")) {
                 return false;
             }
         }
-        
-        int numSources = srcPaths.length + injarPaths.length 
-            + spec.argfiles.length + spec.sourceroots.length;
+
+        int numSources =
+            srcPaths.length
+                + injarPaths.length
+                + spec.argfiles.length
+                + spec.sourceroots.length;
         if (!spec.badInput && (numSources < 1)) {
-            validator.fail("no input jars, arg files, or source files or roots");
-        	return false;
-        } 
-        
-        final File[] argFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles);
-        final File[] injarFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths);
-        final File[] aspectFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath);
-        final File[] classFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.classpath);
+            validator.fail(
+                "no input jars, arg files, or source files or roots");
+            return false;
+        }
+
+        final File[] argFiles =
+            FileUtil.getBaseDirFiles(testBaseSrcDir, spec.argfiles);
+        final File[] injarFiles =
+            FileUtil.getBaseDirFiles(testBaseSrcDir, injarPaths);
+        final File[] aspectFiles =
+            FileUtil.getBaseDirFiles(testBaseSrcDir, spec.aspectpath);
+        final File[] classFiles =
+            FileUtil.getBaseDirFiles(testBaseSrcDir, spec.classpath);
         // hmm - duplicates validation above, verifying getBaseDirFiles?
         if (!spec.badInput) {
             if (!validator.canRead(argFiles, "argFiles")
@@ -203,34 +215,53 @@ public class CompilerRun implements IAjcRun {
         final File[] srcFiles;
         File[] sourcerootFiles = new File[0];
         // source text files are copied when staging incremental tests
-        if (!spec.isStaging()) { // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) {
-            srcFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, srcPaths, CompilerRun.SOURCE_SUFFIXES);
+        if (!spec.isStaging()) {
+            // XXX why this? was always? || (testBaseSrcDir != sandbox.stagingDir))) {
+            srcFiles =
+                FileUtil.getBaseDirFiles(
+                    testBaseSrcDir,
+                    srcPaths,
+                    CompilerRun.SOURCE_SUFFIXES);
             if (!LangUtil.isEmpty(spec.sourceroots)) {
-                sourcerootFiles = FileUtil.getBaseDirFiles(testBaseSrcDir, spec.sourceroots, null);
+                sourcerootFiles =
+                    FileUtil.getBaseDirFiles(
+                        testBaseSrcDir,
+                        spec.sourceroots,
+                        null);
             }
         } else { // staging - copy files
             if (spec.badInput) {
-                validator.info("badInput ignored - files checked when staging");
+                validator.info(
+                    "badInput ignored - files checked when staging");
             }
             try {
                 // copy all files, then remove tagged ones
                 // XXX make copyFiles support a filter?
-                srcFiles = FileUtil.copyFiles(testBaseSrcDir, srcPaths, sandbox.stagingDir);
+                srcFiles =
+                    FileUtil.copyFiles(
+                        testBaseSrcDir,
+                        srcPaths,
+                        sandbox.stagingDir);
                 if (!LangUtil.isEmpty(spec.sourceroots)) {
-                    sourcerootFiles = FileUtil.copyFiles(testBaseSrcDir, spec.sourceroots, sandbox.stagingDir);
+                    sourcerootFiles =
+                        FileUtil.copyFiles(
+                            testBaseSrcDir,
+                            spec.sourceroots,
+                            sandbox.stagingDir);
                     // delete incremental files in sourceroot after copying // XXX inefficient
                     FileFilter pickIncFiles = new FileFilter() {
-                        // an incremental file has an extra "." in name
-                        // most .java files don't, because they are named after
-                        // the principle type they contain, and simple type names
-                        // have no dots.
-                        public boolean accept(File file) {
-                            if (file.isDirectory()) { // continue recursion
+                            // an incremental file has an extra "." in name
+        // most .java files don't, because they are named after
+        // the principle type they contain, and simple type names
+        // have no dots.
+    public boolean accept(File file) {
+                            if (file.isDirectory()) {
+                                // continue recursion
                                 return true;
                             }
                             String path = file.getPath();
                             // only source files are relevant to staging
-                            if (!FileUtil.hasSourceSuffix(path)) { 
+                            if (!FileUtil.hasSourceSuffix(path)) {
                                 return false;
                             }
                             int first = path.indexOf(".");
@@ -239,13 +270,18 @@ public class CompilerRun implements IAjcRun {
                         }
                     };
                     for (int i = 0; i < sourcerootFiles.length; i++) {
-                        FileUtil.deleteContents(sourcerootFiles[i], pickIncFiles, false);
+                        FileUtil.deleteContents(
+                            sourcerootFiles[i],
+                            pickIncFiles,
+                            false);
                     }
                     if (0 < sourcerootFiles.length) {
-                        FileUtil.sleepPastFinalModifiedTime(sourcerootFiles);
+                        FileUtil.sleepPastFinalModifiedTime(
+                            sourcerootFiles);
                     }
                 }
-                File[] files = FileUtil.getBaseDirFiles(sandbox.stagingDir, srcPaths); 
+                File[] files =
+                    FileUtil.getBaseDirFiles(sandbox.stagingDir, srcPaths);
                 if (0 < files.length) {
                     FileUtil.sleepPastFinalModifiedTime(files);
                 }
@@ -257,7 +293,8 @@ public class CompilerRun implements IAjcRun {
                 return false;
             }
         }
-        if (!spec.badInput && !validator.canRead(srcFiles, "copied paths")) {
+        if (!spec.badInput
+            && !validator.canRead(srcFiles, "copied paths")) {
             return false;
         }
         arguments.clear();
@@ -279,12 +316,13 @@ public class CompilerRun implements IAjcRun {
                 arguments.add("@" + ra[j]);
             }
             if (!spec.badInput && spec.isStaging) {
-                validator.fail("warning: files listed in argfiles not staged");
-            }               
+                validator.fail(
+                    "warning: files listed in argfiles not staged");
+            }
         }
 
         // save classpath and aspectpath in sandbox for this and other clients
-        final boolean checkReadable = !spec.badInput; 
+        final boolean checkReadable = !spec.badInput;
         int size = spec.includeClassesDir ? 3 : 2;
         File[] cp = new File[size + classFiles.length];
         System.arraycopy(classFiles, 0, cp, 0, classFiles.length);
@@ -299,7 +337,7 @@ public class CompilerRun implements IAjcRun {
         if (0 < aspectFiles.length) {
             sandbox.setAspectpath(aspectFiles, checkReadable, this);
         }
-        
+
         // set bootclasspath if set for forking
         AbstractRunSpec.Fork fork = spec.getFork();
         String bootclasspath = fork.getJavaBootclasspath();
@@ -308,7 +346,7 @@ public class CompilerRun implements IAjcRun {
         }
         return true;
     }
-    
+
     /**
      * Setup result evaluation and command line, run, and evaluate result.
      * <li>setup an AjcMessageHandler using the expected messages from
@@ -325,20 +363,23 @@ public class CompilerRun implements IAjcRun {
      * <li>After running, report AjcMessageHandler results to the status parameter.
      *     If the AjcMessageHandler reports a failure, then send info messages
      *     for the Spec, TestSetup, and command line.<li>
-	 * @see org.aspectj.testing.run.IRun#run(IRunStatus)
-	 */
-	public boolean run(IRunStatus status) {
+     * @see org.aspectj.testing.run.IRun#run(IRunStatus)
+     */
+    public boolean run(IRunStatus status) {
         if (null == spec.testSetup) {
-            MessageUtil.abort(status, "no test setup - adoptParentValues not called");
+            MessageUtil.abort(
+                status,
+                "no test setup - adoptParentValues not called");
             return false;
         } else if (!spec.testSetup.result) {
             MessageUtil.abort(status, spec.testSetup.failureReason);
             return false;
         }
-        boolean ignoreWarnings = (spec.testSetup.ignoreWarningsSet
+        boolean ignoreWarnings =
+            (spec.testSetup.ignoreWarningsSet
                 && spec.testSetup.ignoreWarnings);
-        AjcMessageHandler handler 
-            = new AjcMessageHandler(spec.getMessages());
+        AjcMessageHandler handler =
+            new AjcMessageHandler(spec.getMessages());
         handler.init();
         boolean handlerResult = false;
         boolean result = false;
@@ -349,9 +390,12 @@ public class CompilerRun implements IAjcRun {
             argList.add("-d");
             String outputDirPath = sandbox.classesDir.getAbsolutePath();
             try { // worth it to try for canonical?
-                outputDirPath = sandbox.classesDir.getCanonicalPath();        
+                outputDirPath = sandbox.classesDir.getCanonicalPath();
             } catch (IOException e) {
-                MessageUtil.abort(status, "canonical " + sandbox.classesDir, e);
+                MessageUtil.abort(
+                    status,
+                    "canonical " + sandbox.classesDir,
+                    e);
             }
             argList.add(outputDirPath);
 
@@ -365,7 +409,7 @@ public class CompilerRun implements IAjcRun {
                 argList.add("-bootclasspath");
                 argList.add(path);
             }
-            
+
             path = sandbox.aspectpathToString(this);
             if (!LangUtil.isEmpty(path)) {
                 argList.add("-aspectpath");
@@ -374,54 +418,81 @@ public class CompilerRun implements IAjcRun {
 
             if (0 < injars.size()) {
                 argList.add("-injars");
-                argList.add(FileUtil.flatten((String[]) injars.toArray(new String[0]), null));
+                argList.add(
+                    FileUtil.flatten(
+                        (String[]) injars.toArray(new String[0]),
+                        null));
             }
-            
+
             // put specified arguments last, for better badInput tests
             argList.addAll(setupResult.commandOptions);
 
             // add both java/aspectj and argfiles
             argList.addAll(arguments);
-            
+
             // XXX hack - seek on request as a side effect. reimplement as listener 
             if (null != setupResult.seek) {
                 String slopPrefix = Spec.SEEK_MESSAGE_PREFIX + " slop - ";
-                PrintStream slop = MessageUtil.handlerPrintStream(
-                    status, 
-                    IMessage.INFO, 
-                    System.err, 
-                    slopPrefix);
-                List found = FileUtil.lineSeek(setupResult.seek, arguments, false, slop);
+                PrintStream slop =
+                    MessageUtil.handlerPrintStream(
+                        status,
+                        IMessage.INFO,
+                        System.err,
+                        slopPrefix);
+                List found =
+                    FileUtil.lineSeek(
+                        setupResult.seek,
+                        arguments,
+                        false,
+                        slop);
                 if (!LangUtil.isEmpty(found)) {
-                    for (Iterator iter = found.iterator(); iter.hasNext();) {
-                        MessageUtil.info(status, Spec.SEEK_MESSAGE_PREFIX + iter.next());
-					}
+                    for (Iterator iter = found.iterator();
+                        iter.hasNext();
+                        ) {
+                        MessageUtil.info(
+                            status,
+                            Spec.SEEK_MESSAGE_PREFIX + iter.next());
+                    }
                 }
             }
-            ICommand compiler = spec.reuseCompiler 
-                ? sandbox.getCommand(this) // throws IllegalStateException if null
-                : ReflectionFactory.makeCommand(setupResult.compilerName, status);            
+            ICommand compiler = spec.reuseCompiler
+                // throws IllegalStateException if null
+    ? sandbox.getCommand(this)
+    : ReflectionFactory.makeCommand(setupResult.compilerName, status);
             DirChanges dirChanges = null;
             if (null == compiler) {
-                MessageUtil.fail(status, "unable to make compiler " + setupResult.compilerName);
+                MessageUtil.fail(
+                    status,
+                    "unable to make compiler " + setupResult.compilerName);
                 return false;
             } else {
                 if (setupResult.compilerName != Spec.DEFAULT_COMPILER) {
-                    MessageUtil.info(status, "compiler: " + setupResult.compilerName);
+                    MessageUtil.info(
+                        status,
+                        "compiler: " + setupResult.compilerName);
                 }
                 if (status.aborted()) {
-                    MessageUtil.debug(status, "aborted, but compiler valid?: " + compiler);
+                    MessageUtil.debug(
+                        status,
+                        "aborted, but compiler valid?: " + compiler);
                 } else {
                     // same DirChanges handling for JavaRun, CompilerRun, IncCompilerRun 
                     // XXX around advice or template method/class
                     if (!LangUtil.isEmpty(spec.dirChanges)) {
-                        LangUtil.throwIaxIfFalse(1 == spec.dirChanges.size(), "expecting 0..1 dirChanges");
-                        dirChanges = new DirChanges((DirChanges.Spec) spec.dirChanges.get(0));
-                        if (!dirChanges.start(status, sandbox.classesDir)) {
+                        LangUtil.throwIaxIfFalse(
+                            1 == spec.dirChanges.size(),
+                            "expecting 0..1 dirChanges");
+                        dirChanges =
+                            new DirChanges(
+                                (DirChanges.Spec) spec.dirChanges.get(0));
+                        if (!dirChanges
+                            .start(status, sandbox.classesDir)) {
                             return false; // setup failed
                         }
                     }
-                    MessageUtil.info(status, compiler + "(" + argList + ")");
+                    MessageUtil.info(
+                        status,
+                        compiler + "(" + argList + ")");
                     sandbox.setCommand(compiler, this);
                     String[] args = (String[]) argList.toArray(RA_String);
                     commandResult = compiler.runCommand(args, handler);
@@ -431,11 +502,12 @@ public class CompilerRun implements IAjcRun {
             if (!handlerResult) {
                 return false;
             } else {
-                result = (commandResult == handler.expectingCommandTrue()); 
-                if (! result) {
-                    String m = commandResult 
-                        ? "compile did not fail as expected"
-                        : "compile failed unexpectedly";                        
+                result = (commandResult == handler.expectingCommandTrue());
+                if (!result) {
+                    String m =
+                        commandResult
+                            ? "compile did not fail as expected"
+                            : "compile failed unexpectedly";
                     MessageUtil.fail(status, m);
                 } else if (null != dirChanges) {
                     result = dirChanges.end(status, sandbox.testBaseDir);
@@ -450,111 +522,163 @@ public class CompilerRun implements IAjcRun {
                     MessageUtil.info(handler, "" + setupResult);
                 }
             }
-            handler.report(status); // XXX weak - actual messages not reported in real-time, no fast-fail
-        }             
+            handler.report(status);
+            // XXX weak - actual messages not reported in real-time, no fast-fail
+        }
     }
-    
 
     public String toString() {
         return "CompilerRun(" + spec + ")";
     }
-    
+
     /** 
      * Initializer/factory for CompilerRun
      * any path or file is relative to this test base dir
      */
     public static class Spec extends AbstractRunSpec {
         public static final String XMLNAME = "compile";
+        public static final String DEFAULT_COMPILER =
+            ReflectionFactory.ECLIPSE;
         static final String SEEK_PREFIX = "-seek:";
         static final String SEEK_MESSAGE_PREFIX = "found: ";
-        
-        /** can't specify these as options */
-        private static final String[] INVALID_OPTIONS = new String[]
-            { "-workingdir", "-argfile", "-sourceroots", "-outjar"}; 
-            // when updating these, update tests/harness/selectionTest.xml
-        
-
-        /** no support in the javac for these otherwise-valid options */
-        private static final String[] INVALID_JAVAC_OPTIONS = new String[]
-            { "-lenient", "-strict", "-usejavac", "-preprocess",
-              "-XOcodeSize", "-XSerializable", "-XaddSafePrefix",
-              "-XtargetNearSource",
-              "-incremental", "-Xlint", "-aspectpath",
-              "workingdir", "-argfile", "-sourceroots", "-outjar",
-               };
-               
-        /** no support in the eclipse-based compiler for these otherwise-valid options */
-        private static final String[] INVALID_ECLIPSE_OPTIONS = new String[]
-            { "-lenient", "-strict", "-usejavac", "-preprocess",
-              "-XOcodeSize", "-XSerializable", "-XaddSafePrefix",
-              "-XtargetNearSource" };
-
-        /** options supported by the harness */
-        private static final String[] VALID_OPTIONS = new String[]
-            {
-                SEEK_PREFIX,
-                // eajc does not support -usejavac, -preprocess
-                // testFlag() handles -ajc, -eclipse, -javac, -ajdeCompiler, -ignoreWarnings 
-                "-usejavac", "-preprocess",          
-                "-Xlint",  "-lenient", "-strict", 
-                "-source14", "-verbose", "-emacssym", 
-                "-ajc", "-eclipse", "-ajdeCompiler", "-ajctaskCompiler", "-javac",
-                "-ignoreWarnings", "-1.3", "-1.4", "-1.5",
-                // XXX consider adding [!^]ajdeCompiler
-                "!usejavac", "!preprocess",          
-                "!Xlint",  "!lenient", "!strict", 
-                "!source14", "!verbose", "!emacssym", 
-                "!ajc", "!eclipse", 
-
-                "^usejavac", "^preprocess",          
-                "^Xlint",  "^lenient", "^strict", 
-                "^source14", "^verbose", "^emacssym", 
-                "^ajc", "^eclipse"
-            };
-        public static final String DEFAULT_COMPILER 
-            = ReflectionFactory.ECLIPSE;
+
+        private static final CRSOptions CRSOPTIONS = new CRSOptions();
+        /**
+          * Retitle description to title, paths to files, do comment,
+          * staging, badInput,
+          * do dirChanges, and print no chidren. 
+          */
+        private static final AbstractRunSpec.XMLNames NAMES =
+            new AbstractRunSpec.XMLNames(
+                AbstractRunSpec.XMLNames.DEFAULT,
+                "title",
+                null,
+                null,
+                null,
+                "files",
+                null,
+                null,
+                null,
+                false,
+                false,
+                true);
 
         /**
-         * Retitle description to title, paths to files, do comment,
-         * staging, badInput,
-         * do dirChanges, and print no chidren. 
+         * If the source version warrants, add a -bootclasspath
+         * entry to the list of arguments to add. 
+         * This will fail and return an error String if the 
+         * required library is not found.
+         * @param sourceVersion the String (if any) describing the -source option
+         *        (expecting one of [null, "1.3", "1.4", "1.5"].
+         * @param compilerName the String name of the target compiler
+         * @param toAdd the ArrayList to add -bootclasspath to
+         * @return the String describing any errors, or null if no errors
          */
-        private static final XMLNames NAMES = new XMLNames(XMLNames.DEFAULT,
-            "title", null, null, null, "files", null, null, null, false, false, true);
-        
+        private static String updateBootclasspathForSourceVersion(
+            String sourceVersion,
+            String compilerName,
+            ArrayList toAdd) {
+            if (null == sourceVersion) {
+                return null;
+            }
+            if (3 != sourceVersion.length()) {
+                throw new IllegalArgumentException(
+                    "bad version: " + sourceVersion);
+            }
+            if (null == toAdd) {
+                throw new IllegalArgumentException("null toAdd");
+            }
+            int version = sourceVersion.charAt(2) - '0';
+            switch (version) {
+                case (3) :
+                    if (LangUtil.supportsJava("1.4")) {
+                        if (!FileUtil.canReadFile(Globals.J2SE13_RTJAR)) {
+                            return "no 1.3 libraries to handle -source 1.3";
+                        }
+                        toAdd.add("-bootclasspath");
+                        toAdd.add(Globals.J2SE13_RTJAR.getAbsolutePath());
+                    }
+                    break;
+                case (4) :
+                    if (!LangUtil.supportsJava("1.4")) {
+                        if (ReflectionFactory
+                            .ECLIPSE
+                            .equals(compilerName)) {
+                            return "run eclipse under 1.4 to handle -source 1.4";
+                        }
+                        if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) {
+                            return "no 1.4 libraries to handle -source 1.4";
+                        }
+                        toAdd.add("-bootclasspath");
+                        toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath());
+                    }
+                    break;
+                case (5) :
+                    return "1.5 not supported in CompilerRun";
+                case (0) :
+                    // ignore - no version specified
+                    break;
+                default :
+                    throw new Error("unexpected version: " + version);
+            }
+            return null;
+        }
+
+        static CRSOptions testAccessToCRSOptions() {
+            return CRSOPTIONS;
+        }
+
+        static Options testAccessToOptions() {
+            return CRSOPTIONS.getOptions();
+        }
+
+        private static String[] copy(String[] input) {
+            if (null == input) {
+                return null;
+            }
+            String[] result = new String[input.length];
+            System.arraycopy(input, 0, result, 0, input.length);
+            return result;
+        }
+
+        /** @return true if javac is available on the classpath */
+        private static boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java
+            Class compilerClass = null;
+            try {
+                compilerClass = Class.forName("com.sun.tools.javac.Main");
+            } catch (ClassNotFoundException ce1) {
+                try {
+                    compilerClass = Class.forName("sun.tools.javac.Main");
+                } catch (ClassNotFoundException ce2) {
+                }
+            }
+            return (null != compilerClass);
+        }
+
         protected String compiler;
-        
+
         // use same command - see also IncCompiler.Spec.fresh
         protected boolean reuseCompiler;
         protected boolean includeClassesDir;
-        
+
         protected TestSetup testSetup;
-        
+
         protected String[] argfiles = new String[0];
         protected String[] aspectpath = new String[0];
         protected String[] classpath = new String[0]; // XXX unused
         protected String[] sourceroots = new String[0];
-        
+
         /** src path = {suiteParentDir}/{testBaseDirOffset}/{testSrcDirOffset}/{path} */
         protected String testSrcDirOffset;
-        
+
         public Spec() {
             super(XMLNAME);
             setXMLNames(NAMES);
             compiler = DEFAULT_COMPILER;
         }
-        
-        private static String[] copy(String[] input) {
-            if (null == input) {
-                return null;
-            }
-            String[] result = new String[input.length];
-            System.arraycopy(input, 0, result, 0, input.length);
-            return result;
-        }
 
-        protected void initClone(Spec spec) 
-                throws CloneNotSupportedException {
+        protected void initClone(Spec spec)
+            throws CloneNotSupportedException {
             super.initClone(spec);
             spec.argfiles = copy(argfiles);
             spec.aspectpath = copy(aspectpath);
@@ -569,11 +693,11 @@ public class CompilerRun implements IAjcRun {
             }
             spec.testSrcDirOffset = testSrcDirOffset;
         }
-        
+
         public Object clone() throws CloneNotSupportedException {
             Spec result = new Spec();
             initClone(result);
-            return result;    
+            return result;
         }
 
         public void setIncludeClassesDir(boolean include) {
@@ -582,17 +706,17 @@ public class CompilerRun implements IAjcRun {
         public void setReuseCompiler(boolean reuse) {
             this.reuseCompiler = reuse;
         }
-        
+
         public void setCompiler(String compilerName) {
             this.compiler = compilerName;
-        }        
+        }
 
         public void setTestSrcDirOffset(String s) {
             if (null != s) {
                 testSrcDirOffset = s;
             }
         }
-        
+
         /** override to set dirToken to Sandbox.CLASSES and default suffix to ".class" */
         public void addDirChanges(DirChanges.Spec spec) {
             if (null == spec) {
@@ -602,7 +726,7 @@ public class CompilerRun implements IAjcRun {
             spec.setDefaultSuffix(".class");
             super.addDirChanges(spec);
         }
-        
+
         public String toLongString() {
             return getPrintName() + "(" + super.containedSummary() + ")";
         }
@@ -611,11 +735,11 @@ public class CompilerRun implements IAjcRun {
             return getPrintName() + "(" + super.containedSummary() + ")";
         }
 
-        /** bean mapping for writers */        
+        /** bean mapping for writers */
         public void setFiles(String paths) {
             addPaths(paths);
         }
-        
+
         /**
          * Add to default classpath
          * (which includes aspectjrt.jar and testing-client.jar).
@@ -632,7 +756,7 @@ public class CompilerRun implements IAjcRun {
          * Set source roots, deleting any old ones
          * @param files comma-delimited list of directories
          *  - ignored if null or empty
-         */        
+         */
         public void setSourceroots(String dirs) {
             if (!LangUtil.isEmpty(dirs)) {
                 sourceroots = XMLWriter.unflattenList(dirs);
@@ -643,7 +767,7 @@ public class CompilerRun implements IAjcRun {
          * Set aspectpath, deleting any old ones
          * @param files comma-delimited list of aspect jars - ignored if null or
          * empty
-         */        
+         */
         public void setAspectpath(String files) {
             if (!LangUtil.isEmpty(files)) {
                 aspectpath = XMLWriter.unflattenList(files);
@@ -653,7 +777,7 @@ public class CompilerRun implements IAjcRun {
         /** 
          * Set argfiles, deleting any old ones
          * @param files comma-delimited list of argfiles - ignored if null or empty
-         */        
+         */
         public void setArgfiles(String files) {
             if (!LangUtil.isEmpty(files)) {
                 argfiles = XMLWriter.unflattenList(files);
@@ -668,20 +792,22 @@ public class CompilerRun implements IAjcRun {
             }
             return (String[]) LangUtil.copy(argfiles);
         }
-        
+
         /**
          * This implementation skips if:
          * <ul>
          * <li>incremental test, but using ajc (not eclipse)</li>
          * <li>usejavac, but javac is not available on the classpath</li>
          * <li>eclipse, but -usejavac or -preprocess test</li>
-         * <li>-source14, but running under 1.2 (XXX design)</li>
+         * <li>-source 1.4, but running under 1.2 (XXX design)</li>
          * <li>local/global option conflicts (-lenient/-strict)</li>
          * <li>semantic conflicts (e.g., -lenient/-strict)</li>
          * </ul>
          * @return false if this wants to be skipped, true otherwise
          */
-        protected boolean doAdoptParentValues(RT parentRuntime, IMessageHandler handler) {
+        protected boolean doAdoptParentValues(
+            RT parentRuntime,
+            IMessageHandler handler) {
             if (!super.doAdoptParentValues(parentRuntime, handler)) {
                 return false;
             }
@@ -690,33 +816,35 @@ public class CompilerRun implements IAjcRun {
                 skipMessage(handler, testSetup.failureReason);
             }
             return testSetup.result;
-        }  
-        
+        }
 
-	    private String getShortCompilerName() {
-			String cname = compiler;
+        private String getShortCompilerName() {
+            String compilerClassName = compiler;
             if (null != testSetup) {
-                cname = testSetup.compilerName;
-            }
-			if (null != cname) {
-			    int loc = cname.lastIndexOf(".");
-			    if (-1 != loc) {
-			        cname = cname.substring(loc+1);
-			    }
-			}
-			return cname;
-		}
-        
+                compilerClassName = testSetup.compilerName;
+            }
+            if (null != compilerClassName) {
+                int loc = compilerClassName.lastIndexOf(".");
+                if (-1 != loc) {
+                    compilerClassName =
+                        compilerClassName.substring(loc + 1);
+                }
+            }
+            return compilerClassName;
+        }
+
         /** @return a CompilerRun with this as spec if setup completes successfully. */
-		public IRunIterator makeRunIterator(Sandbox sandbox, Validator validator) {
-			CompilerRun run = new CompilerRun(this);
+        public IRunIterator makeRunIterator(
+            Sandbox sandbox,
+            Validator validator) {
+            CompilerRun run = new CompilerRun(this);
             if (run.setupAjcRun(sandbox, validator)) {
                 // XXX need name for compilerRun
                 return new WrappedRunIterator(this, run);
             }
             return null;
-		}
-        
+        }
+
         protected String getPrintName() {
             return "CompilerRun.Spec " + getShortCompilerName();
         }
@@ -750,9 +878,6 @@ public class CompilerRun implements IAjcRun {
          * <li>lenient, strict</li>
          * </ul>
          * <p>
-         * The -source 1.4 flag should always be specified as -source14, 
-         * as this will otherwise fail to process it correctly.  
-         * This converts it back to -source 1.4.
          * <p>
          * This also interprets any relevant System properties,
          * e.g., from <code>JavaRun.BOOTCLASSPATH_KEY</code>.
@@ -773,298 +898,346 @@ public class CompilerRun implements IAjcRun {
          * and commandOptions is not null, and any test flags (ignore warning,
          * compiler) are reflected in the TestSetup.
          * If this fails, then TestSetup.result is false,
-         * and a TestSetup.failreason is set.  
+         * and a TestSetup.failreason is set. 
+         * This means the test is skipped. 
          * @return TestSetup with results 
          *          (TestSetup result=false if the run should not continue)
          */
         protected TestSetup setupArgs(IMessageHandler handler) {
             // warning: HarnessSelectionTest checks for specific error wording
-            ArrayList argList = new ArrayList();
-            argList.addAll(getOptionsList());
             final Spec spec = this;
-            TestSetup result = new TestSetup();
-            if (argList.contains("-source")) {
-                result.failureReason = "use -source14 for -source 1.4: " + argList;
-                return result;                
-            }
+            final TestSetup result = new TestSetup();
             result.compilerName = spec.compiler;
-            String[] globalOptions = spec.runtime.extractOptions(Spec.VALID_OPTIONS, true);
-            if ((null != globalOptions) && (globalOptions.length > 0)) {
-                // --- fold in globals, removing conflicts, etc.
-                for (int i = 0; i < globalOptions.length; i++) {
-                    String globalArg = globalOptions[i];
-                    if ((null == globalArg) || (2 > globalArg.length())) {
-                        continue;
-                    } else if (globalArg.startsWith(SEEK_PREFIX)) { 
-                        result.seek = globalArg.substring(SEEK_PREFIX.length());
-                        continue;
-                    } else if ("-source".equals(globalArg)) {
-                        result.failureReason = "use -source14 for -source 1.4 [" + i + "]";
-                        return result;                
-                    }
-                    char first = globalArg.charAt(0);
-                    globalArg = globalArg.substring(1);
-                    boolean globalForceOn   = (first == '!');
-                    boolean globalForceOff  = (first == '^');
-                    boolean globalSet       = (first == '-');
-                    if (!globalSet && !globalForceOn && !globalForceOff) {
-                        MessageUtil.info(handler, "ignoring bad global: " + globalOptions[i]);
-                        continue;
-                    }
-                    int argIndex = indexOf(globalArg, argList);
-                    if (-1 == argIndex) { // no apparent conflict - look for eclipse/ajc conflicts XXX unresolved
-                        boolean ajcGlobal = true;
-                        if ("ajc".equals(globalArg)) {
-                            argIndex = indexOf("eclipse", argList);
-                        } else if ("eclipse".equals(globalArg)) {
-                            argIndex = indexOf("ajc", argList);                            
-                            ajcGlobal = false;
-                        }
-                        if (-1 != argIndex) {   // resolve eclipse/ajc conflict
-                            String arg = ((String) argList.get(argIndex));
-                            char argFirst = arg.charAt(0);
-                            argList.remove(arg);      // replace with resolved variant...
-                            char ajcFirst;
-                            char eclipseFirst;
-                            if (ajcGlobal) {
-                                ajcFirst = first;
-                                eclipseFirst = argFirst;
-                            } else {
-                                ajcFirst = argFirst;
-                                eclipseFirst = first;
-                            }
-                            if ('!' == eclipseFirst) {
-                                if ('!' == ajcFirst) {
-                                    result.failureReason = "conflict between !eclipse and !ajc";
-                                    return result;
-                                } else {
-                                    argList.add("-eclipse");
-                                }
-                            } else if (('!' == ajcFirst)  || ('^' == eclipseFirst)) {
-                                argList.add("-ajc");
-                            } else if ('^' == ajcFirst) {
-                                argList.add("-eclipse");
-                            } else if (('-' != ajcFirst) || ('-' != eclipseFirst)) {
-                                result.failureReason = "harness logic error resolving "
-                                    + arg + " and global " + globalArg;
-                                return result;
-                            } else if (ajcGlobal) {
-                                argList.add("-ajc");
-                            } else {
-                                argList.add("-eclipse");
-                            }
-                            continue; // resolved 
-                        }
-                    }
-                    
-                    if (-1 == argIndex) { // no dup, so no conflict
-                        if (!globalForceOff) {
-                            argList.add("-" + globalArg);
-                        }
-                    } else { // have conflict - resolve
-                        String arg = (String) argList.get(argIndex);
-                        first = arg.charAt(0);
-                        boolean localForceOn   = (first == '!');
-                        boolean localForceOff  = (first == '^');
-                        boolean localSet       = (first == '-');
-                        if (!localSet && !localForceOn && !localForceOff) {
-                            result.failureReason = "only handling [-^!]{arg}: " + arg;
-                            return result;
-                        }
-                        if ((localForceOn && globalForceOff)
-                            || (localForceOff && globalForceOn)) {
-                            result.failureReason = "force conflict between arg=" 
-                                + arg + " and global=" + globalOptions[i];
-                            return result;
-                        } 
-                        if (globalForceOn) {
-                            if (localForceOn) { // localSet is already correct, localForceOff was conflict
-                                argList.remove(arg);      // no !funkiness
-                                argList.add("-" + globalArg);
-                            }
-                        } else if (globalSet) {
-                            if (localSet) {
-                                // do nothing - already correct
-                            } else if (localForceOn) {
-                                argList.remove(arg);      // no !funkiness
-                                argList.add("-" + globalArg);
-                            }
-                        } else if (globalForceOff) {
-                            argList.remove(arg);
-                        } else {
-                            throw new Error("illegal arg state?? : " + arg);
-                            //MessageUtil.info(handler, "illegal arg state??: " + arg);
-                        }
-                    }
-                }
+            // urk - s.b. null, but expected
+            Values values = gatherValues(result);
+
+            if ((null == values) || (null != result.failureReason)) {
+                return checkResult(result);
             }
+
             // send info messages about
             // forced staging when -incremental
             // or staging but no -incremental flag
-            int incLoc = argList.indexOf("-incremental");
+            Option.Family getFamily =
+                CRSOPTIONS.crsIncrementalOption.getFamily();
+            final boolean haveIncrementalFlag =
+                (null != values.firstInFamily(getFamily));
+
             if (spec.isStaging()) {
-                if (-1 == incLoc) { // staging and no flag
-                    MessageUtil.info(handler, "staging but no -incremental flag");
+                if (!haveIncrementalFlag) {
+                    MessageUtil.info(
+                        handler,
+                        "staging but no -incremental flag");
                 }
-            } else if (-1 != incLoc) { // flagged but not staging - stage
+            } else if (haveIncrementalFlag) {
                 spec.setStaging(true);
                 MessageUtil.info(handler, "-incremental forcing staging");
             }
-            // remove funky prefixes from remainder, fixup two-part flags
-            // and interpret special flags
-            boolean source14 = false;
-            ArrayList toAdd = new ArrayList();
-            for (ListIterator iter = argList.listIterator(); iter.hasNext();) {
-                String arg = (String) iter.next();
-                if (testFlag(arg, result)) {
-                    iter.remove();
-                    continue;
-                }
-                char c = arg.charAt(0);
-                String rest = arg.substring(1);
-                if (c == '^') {
-                    iter.remove();
-                    continue;
-                }
-                if (c == '!') {
-                    iter.remove();
-                    if (!("source14".equals(rest))) {
-                        toAdd.add("-" + rest);
-                    } else {
-                        source14 = true;
-                    }
-                    rest = null;
-                } else if ("source14".equals(rest)) {
-                    iter.remove();
-                    source14 = true;
-                }
+
+            if (hasInvalidOptions(values, result)) {
+                return checkResult(result);
             }
-            if (source14) {
-                // must run under 1.4 VM or (if ajc) set up bootclasspath
-                if (!LangUtil.supportsJava("1.4")) {
-                    if (ReflectionFactory.ECLIPSE.equals(result.compilerName)) {
-                        result.failureReason 
-                            = "eclipse must run under 1.4 to implement -source 1.4";
-                        return result;                        
-                    }
-                    
-                    if (!FileUtil.canReadFile(Globals.J2SE14_RTJAR)) {
-                        result.failureReason 
-                            = "unable to get 1.4 libraries to implement -source 1.4";
-                        return result;
-                    }
-                    toAdd.add("-bootclasspath");
-                    toAdd.add(Globals.J2SE14_RTJAR.getAbsolutePath());
-                }
-                toAdd.add("-source");
-                toAdd.add("1.4");
-            }
-            argList.addAll(toAdd);
-    
-            // finally, check for semantic conflicts
-            String[] badOptions = LangUtil.selectOptions(argList, Spec.INVALID_OPTIONS);
-            if (!LangUtil.isEmpty(badOptions)) {
-                result.failureReason = "no support for (normally-valid) options "
-                     + Arrays.asList(badOptions);
-            } else if (argList.contains("-lenient") && argList.contains("-strict")) {
-                result.failureReason = "semantic conflict -lenient | -strict";
-            } else if (ReflectionFactory.OLD_AJC.equals(result.compilerName)) {
-                if (spec.isStaging) {
-                    result.failureReason = "OLD_AJC does not do incremental compiles";
-                } else if (argList.contains("-usejavac") && !haveJavac()) {
-                    result.failureReason = "-usejavac but no javac on classpath";
-                } else {
-                    result.result = true;
-                }
-            } else if (ReflectionFactory.ECLIPSE.equals(result.compilerName)
-                || AJDE_COMPILER.equals(result.compilerName)
-                || AJCTASK_COMPILER.equals(result.compilerName)
-                || BUILDER_COMPILER.equals(result.compilerName)) {
-                badOptions = LangUtil.selectOptions(argList, Spec.INVALID_ECLIPSE_OPTIONS);
-                if (!LangUtil.isEmpty(badOptions)) {                    
-                    result.failureReason = "no support in eclipse-based compiler"
-                        + " for (normally-valid) options "+ Arrays.asList(badOptions);
-                } else {
-                    result.result = true;
+
+            // set compiler in result
+            getFamily = CRSOPTIONS.ajccompilerOption.getFamily();
+            Option.Value compiler = values.firstInFamily(getFamily);
+            if (null != compiler) {
+                result.compilerName 
+                    = CRSOPTIONS.compilerClassName(compiler.option);
+                if (null == result.compilerName) {
+                    result.failureReason =
+                        "unable to get class name for " + compiler;
+                    return checkResult(result);
                 }
-//            } else if (JAVAC_COMPILER.equals(result.compilerName)) {
-//                // XXX vet
-//                badOptions = LangUtil.selectOptions(argList, Spec.INVALID_JAVAC_OPTIONS);
-//                if (!LangUtil.isEmpty(badOptions)) {                    
-//                    result.failureReason = "no support in javac"
-//                        + " for (normally-valid) options " + Arrays.asList(badOptions);
-//                } else {
-//                    result.result = true;
-//                }
-            } else {
-                result.failureReason = "unrecognized compiler: " + result.compilerName;
             }
-            if (result.result) {
-                result.commandOptions = argList;
+            String compilerName =
+                (null == result.compilerName
+                    ? spec.compiler
+                    : result.compilerName);
+            // check compiler semantics
+            if (hasCompilerSpecErrors(compilerName, values, result)) {
+                return checkResult(result);
+            }
+            // add toadd and finish result
+            ArrayList args = new ArrayList();
+            String[] rendered = values.render();
+            if (!LangUtil.isEmpty(rendered)) {
+                args.addAll(Arrays.asList(rendered));
             }
-            return result; 
+            // update bootclasspath
+            getFamily = CRSOPTIONS.crsSourceOption.getFamily();
+            Option.Value source = values.firstInFamily(getFamily);
+            if (null != source) {
+                String sourceVersion = source.unflatten()[1];
+                ArrayList toAdd = new ArrayList();
+                String err =
+                    updateBootclasspathForSourceVersion(
+                        sourceVersion,
+                        spec.compiler,
+                        toAdd);
+                args.addAll(toAdd);
+            }
+            result.commandOptions = args;
+            result.result = true;
+            return checkResult(result);
         }
 
-		/** @return true if javac is available on the classpath */
-		private boolean haveJavac() { // XXX copy/paste from JavaCWrapper.java
-            Class compilerClass = null;
-            try {
-                compilerClass = Class.forName("com.sun.tools.javac.Main");
-            } catch (ClassNotFoundException ce1) {
-                try {
-                    compilerClass = Class.forName("sun.tools.javac.Main");
-                } catch (ClassNotFoundException ce2) {
+        /**
+         * Ensure exit invariant:
+         * <code>result.result == (null == result.failureReason)
+         *       == (null != result.commandOptions)</code>
+         * @param result the TestSetup to verify
+         * @return result
+         * @throws Error if invariant is not true
+         */
+        TestSetup checkResult(TestSetup result) {
+            String err = null;
+            if (null == result) {
+                err = "null result";
+            } else if (result.result != (null == result.failureReason)) {
+                err =
+                    result.result
+                        ? "expected no failure: " + result.failureReason
+                        : "fail for no reason";
+            } else if (result.result != (null != result.commandOptions)) {
+                err =
+                    result.result
+                        ? "expected command options"
+                        : "unexpected command options";
+            }
+            if (null != err) {
+                throw new Error(err);
+            }
+            return result;
+        }
+
+        boolean hasInvalidOptions(Values values, TestSetup result) {
+            // not supporting 1.0 options any more
+            for (Iterator iter = CRSOPTIONS.invalidOptions.iterator();
+                iter.hasNext();
+                ) {
+                Option option = (Option) iter.next();
+                if (null != values.firstOption(option)) {
+                    result.failureReason =
+                        "invalid option in harness: " + option;
+                    return true;
                 }
             }
-            return (null != compilerClass);
-		}
+            return false;
+        }
 
-        
-        /**
-         * Handle flags that are interpreted by the test rather than the
-         * underlying command.  These flags are to be removed from the
-         * arg list.
-         *  @return true if this is a flag to remove from the arg list
-         */
-        protected boolean testFlag(String arg, TestSetup result) {
-            if ("-ajdeCompiler".equals(arg)) {
-                result.compilerName = AJDE_COMPILER;
-                return true;
-            } else if ("-builderCompiler".equals(arg)) {
-                result.compilerName = BUILDER_COMPILER;
-                return true;
-            } else if ("-ajctaskCompiler".equals(arg)) {
-                result.compilerName = AJCTASK_COMPILER;
-                return true;
-//            } else if ("-javac".equals(arg)) {
-//                result.compilerName = JAVAC_COMPILER;
-//                return true;
-            } else if ("-eclipse".equals(arg) || "!eclipse".equals(arg) || "^ajc".equals(arg)) {
-                result.compilerName = ReflectionFactory.ECLIPSE;
-                return true;
-            } else if ("-ajc".equals(arg) || "!ajc".equals(arg) || "^eclipse".equals(arg)) {
-                result.compilerName = ReflectionFactory.OLD_AJC;
+        boolean hasCompilerSpecErrors(
+            String compilerName,
+            Values values,
+            TestSetup result) {
+            /*
+             * Describe any semantic conflicts between options.
+             * This skips:
+             * - old 1.0 options, including lenient v. strict
+             * - old ajc options, include !incremental and usejavac w/o javac
+             * - invalid eclipse options (mostly ajc) 
+             * @param compilerName the String name of the target compiler
+             * @return a String describing any conflicts, or null if none
+             */
+            if (!(ReflectionFactory.ECLIPSE.equals(compilerName)
+                || ReflectionFactory.OLD_AJC.equals(compilerName)
+                || CRSOPTIONS.AJDE_COMPILER.equals(compilerName)
+                || CRSOPTIONS.AJCTASK_COMPILER.equals(compilerName) 
+                )) {
+                //|| BUILDER_COMPILER.equals(compilerName))
+                result.failureReason =
+                    "unrecognized compiler: " + compilerName;
                 return true;
-            } else if ("-ignoreWarnings".equals(arg)) {
-                result.ignoreWarnings = true;
-                result.ignoreWarningsSet = true;
+            }
+            // not supporting ajc right now
+            if (null
+                != values.firstOption(CRSOPTIONS.ajccompilerOption)) {
+                result.failureReason = "ajc not supported";
                 return true;
             }
+            // not supporting 1.0 options any more
+            for (Iterator iter = CRSOPTIONS.ajc10Options.iterator();
+                iter.hasNext();
+                ) {
+                Option option = (Option) iter.next();
+                if (null != values.firstOption(option)) {
+                    result.failureReason = "old ajc 1.0 option: " + option;
+                    return true;
+                }
+            }
+
             return false;
         }
-        
+
+        protected Values gatherValues(TestSetup result) {
+            final Spec spec = this;
+            // ---- local option values
+            final Values localValues;
+            final Options options = CRSOPTIONS.getOptions();
+            try {
+                String[] input = getOptionsArray();
+                // this handles reading options, 
+                // flattening two-String options, etc.
+                localValues = options.acceptInput(input);
+                // all local values should be picked up
+                String err = Options.missedMatchError(input, localValues);
+                if (!LangUtil.isEmpty(err)) {
+                    result.failureReason = err;
+                    return null;
+                }
+            } catch (InvalidInputException e) {
+                result.failureReason = e.getFullMessage();
+                return null;
+            }
+
+            // ---- global option values
+            StringBuffer errs = new StringBuffer();
+            final Values globalValues =
+                spec.runtime.extractOptions(options, true, errs);
+            if (errs.length() > 0) {
+                result.failureReason = errs.toString();
+                return null;
+            }
+            final Values combined =
+                Values.wrapValues(
+                    new Values[] { localValues, globalValues });
+
+            String err = combined.resolve();
+            if (null != err) {
+                result.failureReason = err;
+                return null;
+            }
+
+            return handleTestArgs(combined, result);
+        }
+
+        //            final int len = globalValues.length() + localValues.length();
+        //            final Option.Value[] combinedValues = new Option.Value[len];
+        //            System.arraycopy(
+        //                globalValues,
+        //                0,
+        //                combinedValues,
+        //                0,
+        //                globalValues.length());
+        //            System.arraycopy(
+        //                localValues,
+        //                0,
+        //                combinedValues,
+        //                globalValues.length(),
+        //                localValues.length());
+        //
+        //            result.compilerName = spec.compiler;
+        //            if (0 < combinedValues.length) {
+        //                // this handles option forcing, etc.
+        //                String err = Options.resolve(combinedValues);
+        //                if (null != err) {
+        //                    result.failureReason = err;
+        //                    return null;
+        //                }
+        //                if (!handleTestArgs(combinedValues, result)) {
+        //                    return null;
+        //                }
+        //            }
+        //            return Values.wrapValues(combinedValues);
+        //        }
+        /**
+         * This interprets and nullifies values for the test.
+         * @param values the Option.Value[] being processed
+         * @param result the TestSetup to modify
+         * @return false if error (caller should return), true otherwise
+         */
+        Values handleTestArgs(Values values, final TestSetup result) {
+            final Option.Family compilerFamily =
+                CRSOPTIONS.ajccompilerOption.getFamily();
+            Values.Selector selector = new Values.Selector() {
+                protected boolean accept(Option.Value value) {
+                    if (null == value) {
+                        return false;
+                    }
+                    Option option = value.option;
+                    if (compilerFamily.sameFamily(option.getFamily())) {
+                        if (value.prefix.isSet()) {
+                            String compilerClass
+                                = CRSOPTIONS.compilerClassName(option);
+                            if (null == compilerClass) {
+                                result.failureReason =
+                                    "unrecognized compiler: " + value;
+                                throw Values.Selector.STOP;
+                            }
+                            if (!CRSOPTIONS.compilerIsLoadable(option)) {
+                                result.failureReason =
+                                    "unable to load compiler: " + option;
+                                throw Values.Selector.STOP;
+                            }
+                            result.compilerName = compilerClass;
+                        }
+                        return true;
+                    } else if (
+                        CRSOPTIONS.crsIgnoreWarnings.sameOptionIdentifier(
+                            option)) {
+                        result.ignoreWarnings = value.prefix.isSet();
+                        result.ignoreWarningsSet = true;
+                        return true;
+                    }
+                    return false;
+                }
+            };
+            return values.nullify(selector);
+        }
+
+        //        /**
+        //         * This interprets and nullifies values for the test.
+        //         * @param values the Option.Value[] being processed
+        //         * @param result the TestSetup to modify
+        //         * @return false if error (caller should return), true otherwise
+        //         */
+        //        boolean handleTestArgs(Option.Value[] values, TestSetup result) {
+        //            if (!LangUtil.isEmpty(values)) {
+        //                for (int i = 0; i < values.length; i++) {
+        //                    Option.Value value = values[i];
+        //                    if (null == value) {
+        //                        continue;
+        //                    }
+        //                    Option option = value.option;
+        //                    if (option.sameOptionFamily(ECLIPSE_OPTION)) {
+        //                        if (!value.prefix.isSet()) {
+        //                            values[i] = null;
+        //                            continue;
+        //                        }
+        //                        String compilerClass =
+        //                            (String) COMPILER_OPTION_TO_CLASSNAME.get(
+        //                                option);
+        //                        if (null == compilerClass) {
+        //                            result.failureReason =
+        //                                "unrecognized compiler: " + value;
+        //                            return false;
+        //                        }
+        //                        result.compilerName = compilerClass;
+        //                        values[i] = null;
+        //                    } else if (
+        //                        option.sameOptionFamily(crsIgnoreWarnings)) {
+        //                        result.ignoreWarnings = value.prefix.isSet();
+        //                        result.ignoreWarningsSet = true;
+        //                        values[i] = null;
+        //                    }
+        //                }
+        //            }
+        //            return true;
+        //        }
+
         // XXX need keys, cache...
         /** @return index of global in argList, ignoring first char */
         protected int indexOf(String global, ArrayList argList) {
             int max = argList.size();
             for (int i = 0; i < max; i++) {
-                 if (global.equals(((String) argList.get(i)).substring(1))) {
+                if (global
+                    .equals(((String) argList.get(i)).substring(1))) {
                     return i;
-                 }
+                }
             }
             return -1;
         }
-        
+
         /** 
          * Write this out as a compile element as defined in
          * AjcSpecXmlReader.DOCTYPE.
@@ -1087,13 +1260,19 @@ public class CompilerRun implements IAjcRun {
                 out.printAttribute("includeClassesDir", "true");
             }
             if (!LangUtil.isEmpty(argfiles)) {
-                out.printAttribute("argfiles", XMLWriter.flattenFiles(argfiles));
+                out.printAttribute(
+                    "argfiles",
+                    XMLWriter.flattenFiles(argfiles));
             }
             if (!LangUtil.isEmpty(aspectpath)) {
-                out.printAttribute("aspectpath", XMLWriter.flattenFiles(argfiles));
+                out.printAttribute(
+                    "aspectpath",
+                    XMLWriter.flattenFiles(argfiles));
             }
             if (!LangUtil.isEmpty(sourceroots)) {
-                out.printAttribute("sourceroots", XMLWriter.flattenFiles(argfiles));
+                out.printAttribute(
+                    "sourceroots",
+                    XMLWriter.flattenFiles(argfiles));
             }
             out.endAttributes();
             if (!LangUtil.isEmpty(dirChanges)) {
@@ -1102,7 +1281,7 @@ public class CompilerRun implements IAjcRun {
             SoftMessage.writeXml(out, getMessages());
             out.endElement(xmlElementName);
         }
-        
+
         /** 
          * Encapsulate the directives that can be set using
          * global arguments supplied in {@link Spec.getOptions()}.
@@ -1116,22 +1295,22 @@ public class CompilerRun implements IAjcRun {
              * to ignore warnings in its result evaluation
              */
             boolean ignoreWarningsSet;
-            
+
             /** if telling AjcMessageHandler, what we tell it */
             boolean ignoreWarnings;
-            
+
             /** false if setup failed */
             boolean result;
-            
+
             /** if setup failed, this has the reason why */
             String failureReason;
-            
+
             /** beyond running test, also seek text in sources */
             String seek;
-            
+
             /** if setup completed, this has the combined global/local options */
             ArrayList commandOptions;
-            
+
             public Object clone() {
                 TestSetup testSetup = new TestSetup();
                 testSetup.compilerName = compilerName;
@@ -1149,13 +1328,326 @@ public class CompilerRun implements IAjcRun {
             public String toString() {
                 return "TestSetup("
                     + (null == compilerName ? "" : compilerName + " ")
-                    + (!ignoreWarningsSet ?  "" 
+                    + (!ignoreWarningsSet
+                        ? ""
                         : (ignoreWarnings ? "" : "do not ")
                             + "ignore warnings ")
                     + (result ? "" : "setup failed")
                     + ")";
             }
         }
+
+        /**
+         * Options-related stuff in the spec.
+         */
+        static class CRSOptions {
+            //    static final String BUILDER_COMPILER =
+            //        "org.aspectj.ajdt.internal.core.builder.Builder.Command";
+            static final String AJDE_COMPILER =
+                CompileCommand.class.getName();
+
+            static final String AJCTASK_COMPILER =
+                AjcTaskCompileCommand.class.getName();
+
+            private final Map compilerOptionToLoadable = new TreeMap();
+            /*
+            * The options field in a compiler test permits some arbitrary
+            * command-line options to be set.  It does not permit things
+            * like classpath,  aspectpath, files, etc. which are set
+            * using other fields in the test specification, so the options
+            * permitted are a subset of those permitted on the command-line.
+            * 
+            * Global options specified on the harness command-line are
+            * adopted for the compiler command-line if they are permitted
+            * in the options field.  That means we have to detect each
+            * permitted option, rather than just letting all through
+            * for the compiler.
+            * 
+            * Conversely, some options are targeted not at the compiler,
+            * but at the test itself (e.g., to ignore warnings, or to 
+            * select a compiler.
+            * 
+            * The harness can run many compilers, and they differ in
+            * which options are permitted.  You can specify a compiler
+            * as an option (e.g., -eclipse).  So the set of constraints
+            * on the list of permitted options can differ from test to test.
+            * 
+            * The following code sets up the lists of permitted options
+            * and breaks out subsets for different compiler-variant checks.
+            * Most options are created but not named, but some options
+            * are named to detect corresponding values for further 
+            * processing.  e.g., the compiler options are saved so 
+            * we can do per-compiler option verification.
+            *
+            */
+            private final Options crsOptions;
+            private final Family compilerFamily;
+            private final Option crsIncrementalOption;
+            private final Option crsSourceOption;
+            // these are options handled/absorbed by CompilerRun
+            private final Option crsIgnoreWarnings;
+            private final Option eclipseOption;
+            private final Option buildercompilerOption;
+            private final Option ajdecompilerOption;
+            private final Option javacOption;
+            private final Option ajctaskcompilerOption;
+            private final Option ajccompilerOption;
+            private final Map compilerOptionToClassname;
+            private final Set compilerOptions;
+            // compiler verification - permit but flag ajc 1.0 options
+            private final List ajc10Options;
+            private final List invalidOptions;
+
+            private CRSOptions() {
+                crsOptions = new Options(true);
+                Option.Factory factory = new Option.Factory("CompilerRun");
+                // compiler options go in map            
+                eclipseOption =
+                    factory.create(
+                        "eclipse",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+                compilerFamily = eclipseOption.getFamily();
+                buildercompilerOption =
+                    factory.create(
+                        "builderCompiler",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+                ajctaskcompilerOption =
+                    factory.create(
+                        "ajctaskCompiler",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+                ajdecompilerOption =
+                    factory.create(
+                        "ajdeCompiler",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+                ajccompilerOption =
+                    factory.create(
+                        "ajc",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+                javacOption =
+                    factory.create(
+                        "javac",
+                        "compiler",
+                        Option.FORCE_PREFIXES,
+                        false);
+
+                Map map = new TreeMap();
+                map.put(eclipseOption, ReflectionFactory.ECLIPSE);
+                //map.put(BUILDERCOMPILER_OPTION, BUILDER_COMPILER);
+                map.put(
+                    ajctaskcompilerOption,
+                    AJCTASK_COMPILER);
+                map.put(ajdecompilerOption, AJDE_COMPILER);
+                map.put(ajccompilerOption, ReflectionFactory.OLD_AJC);
+                //map.put(JAVAC_OPTION, "XXX javac option not supported");
+                compilerOptionToClassname =
+                    Collections.unmodifiableMap(map);
+
+                compilerOptions =
+                    Collections.unmodifiableSet(
+                        compilerOptionToClassname.keySet());
+                // options not permitted in the harness
+                List list = new ArrayList();
+                list.add(factory.create("workingdir"));
+                list.add(factory.create("argfile"));
+                list.add(factory.create("sourceroots"));
+                list.add(factory.create("outjar"));
+                invalidOptions = Collections.unmodifiableList(list);
+
+                // other options added directly
+                crsIncrementalOption = factory.create("incremental");
+
+                crsIgnoreWarnings = factory.create("ignoreWarnings");
+
+                crsSourceOption =
+                    factory
+                        .create(
+                            "source",
+                            "source",
+                            Option.FORCE_PREFIXES,
+                            false,
+                            new String[][] { new String[] { "1.3", "1.4" }
+                });
+
+                // ajc 1.0 options
+                // workingdir above in invalid options
+                list = new ArrayList();
+                list.add(factory.create("usejavac"));
+                list.add(factory.create("preprocess"));
+                list.add(factory.create("nocomment"));
+                list.add(factory.create("porting"));
+                list.add(factory.create("XOcodeSize"));
+                list.add(factory.create("XTargetNearSource"));
+                list.add(factory.create("XaddSafePrefix"));
+                list.add(
+                    factory.create(
+                        "lenient",
+                        "lenient",
+                        Option.FORCE_PREFIXES,
+                        false));
+                list.add(
+                    factory.create(
+                        "strict",
+                        "lenient",
+                        Option.FORCE_PREFIXES,
+                        false));
+                ajc10Options = Collections.unmodifiableList(list);
+
+                // -warn:.. and -g/-g:.. are not exclusive
+                if (!(factory.setupFamily("debug", true)
+                    && factory.setupFamily("warning", true))) {
+                    System.err.println("CompilerRun debug/warning fail!");
+                }
+                Option[] options =
+                    new Option[] {
+                        crsIncrementalOption,
+                        crsIgnoreWarnings,
+                        crsSourceOption,
+                        factory.create(
+                            "Xlint",
+                            "XLint",
+                            Option.FORCE_PREFIXES,
+                            true),
+                        factory.create("verbose"),
+                        factory.create("emacssym"),
+                        factory.create("referenceInfo"),
+                        factory.create("nowarn"),
+                        factory.create("deprecation"),
+                        factory.create("noImportError"),
+                        factory.create("proceedOnError"),
+                        factory.create("preserveAllLocals"),
+                        factory.create(
+                            "warn",
+                            "warning",
+                            Option.STANDARD_PREFIXES,
+                            true),
+                        factory.create(
+                            "g",
+                            "debug",
+                            Option.STANDARD_PREFIXES,
+                            false),
+                        factory.create(
+                            "g:",
+                            "debug",
+                            Option.STANDARD_PREFIXES,
+                            true),
+                        factory.create(
+                            "1.3",
+                            "compliance",
+                            Option.FORCE_PREFIXES,
+                            false),
+                        factory.create(
+                            "1.4",
+                            "compliance",
+                            Option.FORCE_PREFIXES,
+                            false),
+                        factory
+                            .create(
+                                "target",
+                                "target",
+                                Option.FORCE_PREFIXES,
+                                false,
+                                new String[][] { new String[] {
+                                    "1.1",
+                                    "1.2" }}),
+                        factory.create("XnoInline"),
+                        factory.create("XnoWeave"),
+                        factory.create("XserializableAspects")
+                    };
+                for (int i = 0; i < options.length; i++) {
+                    crsOptions.addOption(options[i]);
+                }
+                for (Iterator iter = compilerOptions.iterator();
+                    iter.hasNext();
+                    ) {
+                    crsOptions.addOption((Option) iter.next());
+                }
+                // these are recognized but records with them are skipped
+                for (Iterator iter = ajc10Options.iterator();
+                    iter.hasNext();
+                    ) {
+                    crsOptions.addOption((Option) iter.next());
+                }
+                crsOptions.freeze();
+            }
+
+            Options getOptions() {
+                return crsOptions;
+            }
+            
+            /** 
+             * @return unmodifiable Set of options sharing the
+             * family "compiler".
+             */
+            Set compilerOptions() {
+                return compilerOptions;
+            }
+
+            /**
+             * @param option the compiler Option to get name for
+             * @return null if option is null or not a compiler option,
+             *     or the fully-qualified classname of the ICommand
+             *     implementing the compiler.
+             */
+            String compilerClassName(Option option) {
+                if ((null == option)
+                    || (!compilerFamily.sameFamily(option.getFamily()))) {
+                    return null;
+                }
+                return (String) compilerOptionToClassname.get(option);
+            }
+
+            /**
+             * Check that the compiler class associated with a compiler
+             * option can be loaded.  This check only happens once;
+             * the result is cached (by compilerOption key) for later calls.
+             * @param compilerOption the Option (family compiler) to check
+             * @return true if compiler class for this option can be loaded
+             */
+            boolean compilerIsLoadable(Option compilerOption) {
+                LangUtil.throwIaxIfNull(compilerOption, "compilerName");
+                synchronized (compilerOptionToLoadable) {
+                    Boolean result =
+                        (Boolean) compilerOptionToLoadable.get(
+                            compilerOption);
+                    if (null == result) {
+                        MessageHandler sink = new MessageHandler();
+                        String compilerClassname =
+                            (String) compilerOptionToClassname.get(
+                                compilerOption);
+                        if (null == compilerClassname) {
+                            result = Boolean.FALSE;
+                        } else {
+                            ICommand c =
+                                ReflectionFactory.makeCommand(
+                                    compilerClassname,
+                                    sink);
+
+                            if ((null == c)
+                                || sink.hasAnyMessage(
+                                    IMessage.ERROR,
+                                    true)) {
+                                result = Boolean.FALSE;
+                            } else {
+                                result = Boolean.TRUE;
+                            }
+                        }
+                        compilerOptionToLoadable.put(
+                            compilerOption,
+                            result);
+                    }
+                    return result.booleanValue();
+                }
+            }
+        } // CompilerRun.Spec.CRSOptions
     } // CompilerRun.Spec
-} // CompilerRun
- 
\ No newline at end of file
+} // CompilerRun
\ No newline at end of file
diff --git a/testing/src/org/aspectj/testing/harness/bridge/Globals.java b/testing/src/org/aspectj/testing/harness/bridge/Globals.java
index 9b0b7736c..e4060d50a 100644
--- a/testing/src/org/aspectj/testing/harness/bridge/Globals.java
+++ b/testing/src/org/aspectj/testing/harness/bridge/Globals.java
@@ -12,7 +12,7 @@
  *     Xerox/PARC     initial implementation 
  *     Wes Isberg     removed unused globals.
  * ******************************************************************/
- 
+
 package org.aspectj.testing.harness.bridge;
 
 import java.io.File;
@@ -23,71 +23,105 @@ import org.aspectj.util.LangUtil;
 /**
  */
 public class Globals {
-	public static final String  FORK_NAME = "harness.fork";  // XXX in testing-drivers/../package.htm
+    public static final String FORK_NAME = "harness.fork";
+    // XXX in testing-drivers/../package.htm
     /** name/key of the System property to set to define library dir */
     public static final String LIBDIR_NAME = "harness.libdir";
 
     /** name/key of the System property to set to define J2SE_HOME dir */
     public static final String J2SE14_RTJAR_NAME = "j2se14.rtjar";
+    public static final String J2SE13_RTJAR_NAME = "j2se13.rtjar";
 
     /** assumed relative location of a library with required jars */
-    public static final String LIBDIR = getSystemProperty(LIBDIR_NAME, "../lib/test");
+    public static final String LIBDIR =
+        getSystemProperty(LIBDIR_NAME, "../lib/test");
 
     /** Path to J2SE_HOME */
-    public static final File J2SE14_RTJAR; // XXX used only by 1.0 compiler tests - deprecate?
+    public static final File J2SE14_RTJAR;
+    // XXX used only by 1.0 compiler tests - deprecate?
+    public static final File J2SE13_RTJAR;
 
     /** array of parameter types for main(String[]) */
-    public static final Class[] MAIN_PARM_TYPES = new Class[] {String[].class};    
-    public static final String S_testingclient_jar  = LIBDIR + "/testing-client.jar";
-    public static final String S_aspectjrt_jar      = LIBDIR + "/aspectjrt.jar";
-    public static final File F_testingclient_jar    = new File(S_testingclient_jar);
-    public static final File F_aspectjrt_jar        = new File(S_aspectjrt_jar);
+    public static final Class[] MAIN_PARM_TYPES =
+        new Class[] { String[].class };
+    public static final String S_testingclient_jar =
+        LIBDIR + "/testing-client.jar";
+    public static final String S_aspectjrt_jar = LIBDIR + "/aspectjrt.jar";
+    public static final File F_testingclient_jar =
+        new File(S_testingclient_jar);
+    public static final File F_aspectjrt_jar = new File(S_aspectjrt_jar);
     public static final boolean globalsValid;
-    
+
     static {
+        J2SE13_RTJAR =
+            getRtJarFor(J2SE13_RTJAR_NAME, "c:/home/apps/jdk13");
+        J2SE14_RTJAR =
+            getRtJarFor(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14");
+
+        String forkSpec = getSystemProperty(FORK_NAME, null);
+        globalsValid =
+            (FileUtil.canReadFile(F_testingclient_jar)
+                && FileUtil.canReadFile(F_aspectjrt_jar)
+                && FileUtil.canReadFile(J2SE13_RTJAR)
+                && FileUtil.canReadFile(J2SE14_RTJAR));
+    }
+
+    private static File getRtJarFor(
+        String propertyName,
+        String defaultLocation) {
         File j2seJar = null;
-        String path = getSystemProperty(J2SE14_RTJAR_NAME, "c:/home/apps/jdk14");
-        File j2seHome = new File(path);
-        if (j2seHome.exists() && j2seHome.isDirectory()) {
-            File rtjar = new File(j2seHome.getAbsolutePath() + "/jre/lib/rt.jar");
-            if (rtjar.canRead() && rtjar.isFile()) {
-                j2seJar = rtjar;
+        try {
+            String path = getSystemProperty(propertyName, defaultLocation);
+            File file = new File(path);
+            if (file.exists()) {
+                File rtjar = null;
+                if (path.endsWith("rt.jar")) {
+                    rtjar = file;
+                } else if (file.isDirectory()) {
+                    path = file.getAbsolutePath() + "/jre/lib/rt.jar";
+                    rtjar = new File(path);                            
+                }
+                if (rtjar.canRead() && rtjar.isFile()) {
+                    j2seJar = rtjar;
+                }
+            }
+        } catch (Throwable t) { // avoid at all costs during static init
+            try {
+                t.printStackTrace(System.err);
+            } catch (Throwable x) {
+                // unable to log, better to just fail...
             }
         }
-        J2SE14_RTJAR = j2seJar;
-    	globalsValid = 
-    		(FileUtil.canReadFile(F_testingclient_jar)
-    		  && FileUtil.canReadFile(F_aspectjrt_jar)
-              && FileUtil.canReadFile(J2SE14_RTJAR)
-            );
+        return j2seJar;
     }
 
-	/**
-	 * 
-	 * @return null if not found, or 
+    /**
+     * 
+     * @return null if not found, or 
      *          String with class path for compiler to load J2SE 1.4 classes from.
-	 */
-	public static String get14Bootclasspath() {
+     */
+    public static String get14Bootclasspath() {
         return null;
-	}
+    }
 
-	/**
-	 * Get System property completely safely.
-	 * @param propertyName the String name of the property to get
-	 * @param defaultValue the String default value to return value is null or empty
-	 * @return String value or defaultValue if not available.
-	 */
-	static String getSystemProperty(
-		String propertyName,
-		String defaultValue) {
+    /**
+     * Get System property completely safely.
+     * @param propertyName the String name of the property to get
+     * @param defaultValue the String default value to return value is null or empty
+     * @return String value or defaultValue if not available.
+     */
+    static String getSystemProperty(
+        String propertyName,
+        String defaultValue) {
         String result = defaultValue;
         try {
             String value = System.getProperty(propertyName);
             if (!LangUtil.isEmpty(value)) {
                 result = value;
             }
-        } catch (Throwable t) {}
-        return result;      
-	}
+        } catch (Throwable t) {
+        }
+        return result;
+    }
 
 }
diff --git a/testing/src/org/aspectj/testing/util/options/Option.java b/testing/src/org/aspectj/testing/util/options/Option.java
new file mode 100644
index 000000000..85917922e
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/options/Option.java
@@ -0,0 +1,707 @@
+/* *******************************************************************
+ * Copyright (c) 2003 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: 
+ *     Wes Isberg     initial implementation 
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * Immutable schema for an input (command-line) option.
+ * The schema contains the expected name/label,
+ * the family (for comparison purposes), 
+ * and permitted prefixes.
+ * This has operations to accept input values and compare options.
+ * Options cannot be created directly; for that, use an 
+ * <code>Option.Factory</code>, since it enforces uniqueness
+ * within the families and options created by the factory.
+ * <p>
+ * Option is used with related nested classes to implement relations:
+ * <ul>
+ * <li>Option.Factory produces Option</li>
+ * <li>An Option has a set of Option.Prefixes, 
+ *     which are variants of Option.Prefix 
+ *     valid for the option (e.g., on/set, force-off, and force-on)</li>
+ * <li>Option evaluates input, produces Option.Value</li>
+ * <li>Related instances of Option share an Option.Family, 
+ *     which enforce option exclusion, etc.</li>
+ * </ul>
+ * The classes are nested as "friends" in order to hide private
+ * members (esp. constructors) that can be used within relations.
+ * Most of these classes are immutable.
+ */
+public class Option implements Comparable {
+    public static final Prefix ON = new Prefix("-", "on", true, false);
+    public static final Prefix NONE = new Prefix("", "none", true, false);
+    public static final Prefix FORCE_ON =
+                                new Prefix("!", "force-on", true, true);
+
+    public static final Prefix FORCE_OFF =
+                                new Prefix("^", "force-off", false, true);
+    public static final Prefixes LITERAL_PREFIXES =
+        new Prefixes(new Prefix[] { NONE });
+    public static final Prefixes STANDARD_PREFIXES =
+        new Prefixes(new Prefix[] { ON });
+    public static final Prefixes FORCE_PREFIXES =
+        new Prefixes(new Prefix[] { ON, FORCE_ON, FORCE_OFF });
+
+    /** family is the key for comparing two options */
+    private final Family family;
+
+    /** label expected for the option */
+    private final String name;
+
+    /** unique identifier for option */
+    private final String optionIdentifier;
+
+    /** prefixes permitted for the option in input */
+    private final Prefixes permittedPrefixes;
+
+    /** if true, then match on input that has extra suffix beyond prefix and name */
+    private final boolean acceptSuffixedInput;
+
+    /** 
+     * If true, no collision if there are multiple values 
+     * that share the same family but not the same literal value 
+     */
+    private final boolean permitMultipleValues;
+
+    /** int number of arguments expected after the option itself */
+    private final int numArguments;
+
+    /**
+     * If numArguments > 0, each element has a list of arguments
+     * permitted at that index from the initial matching value.
+     * Elements are not null.
+     */
+    private final String[][] permittedArguments;
+
+    private final int nameLength;
+
+    /*
+     * Create a standard named boolean option,
+     * permitting force-on and force-off.
+     * @param name the String name of the option, e.g., "1.3" for "-1.3"
+     * @param family
+     * @param permittedPrefixes
+     * @param acceptSuffixedInput
+     * @param permittedArguments
+     */
+    public Option(
+        String name,
+        Family family,
+        Prefixes permittedPrefixes,
+        boolean acceptSuffixedInput,
+        String[][] permittedArguments) {
+        LangUtil.throwIaxIfNull(name, "name");
+        LangUtil.throwIaxIfNull(family, "family");
+        LangUtil.throwIaxIfNull(permittedPrefixes, "permittedPrefixes");
+        this.name = name;
+        this.nameLength = name.length();
+        this.family = family;
+        this.permittedPrefixes = permittedPrefixes;
+        this.acceptSuffixedInput = acceptSuffixedInput;
+        this.permitMultipleValues = false;
+        if (LangUtil.isEmpty(permittedArguments)) {
+            permittedArguments = new String[][] { };
+            // nominal, unused
+        } else {
+            String[][] temp = new String[permittedArguments.length][];
+            for (int i = 0; i < temp.length; i++) {
+                String[] toCopy = permittedArguments[i];
+                LangUtil.throwIaxIfNull(toCopy, "no permitted args");
+                final int len = toCopy.length;
+                String[] variants = new String[len];
+                System.arraycopy(toCopy, 0, variants, 0, len);
+                temp[i] = variants;
+            }
+            permittedArguments = temp;
+        }
+        this.permittedArguments = permittedArguments;
+        numArguments = permittedArguments.length;
+        optionIdentifier = family.familyName + "." + name;
+    }
+
+    public int compareTo(Object other) {
+        Option option = (Option) other;
+        int diff = family.compareTo(option.family);
+        if (0 == diff) {
+            diff = name.compareTo(option.name);
+        }
+        return diff;
+    }
+
+    public Family getFamily() {
+        return family;
+    }
+
+    public boolean permitMultipleValues() {
+        return permitMultipleValues;
+    }
+
+    /**
+     * @return int number of elements in this option,
+     *   e.g., 0 for -g or 1 for -source 1.4
+     */
+    public int numArguments() {
+        return numArguments;
+    }
+
+    /**
+     * If this value String represents a valid input for this option,
+     * then create and return the associated Value.
+     * 
+     * @param value the Value created, or null if invalid
+     * @return Value if this value is permitted by this option
+     */
+    public Value acceptValue(String value) {
+        Prefix prefix = hasPrefix(value);
+        if (null != prefix) {
+            if (value.startsWith(name, prefix.length())) {
+                value = value.substring(prefix.length());
+                if (value.length() == nameLength) {
+                    return new Value(value, prefix, this);
+                } else if (acceptSuffixedInput) {
+                    return new Value(value, prefix, this);
+                } else {
+                    return rejectingSuffixedInput(value);
+                }
+            }
+        }
+        return null;
+    }
+
+    /** @return true if we have same option family */
+    public boolean sameOptionFamily(Option other) {
+        return ((null != other) && other.family.equals(family));
+    }
+
+    /** @return true if we have same option family and name */
+    public boolean sameOptionIdentifier(Option other) {
+        return (sameOptionFamily(other) && name.equals(other.name));
+    }
+
+    public String toString() {
+        return name;
+    }
+
+    /** 
+     * Called when ignoreSuffix is off but we got value with suffix.
+     */
+    protected Value rejectingSuffixedInput(String value) {
+        return null;
+    }
+
+    /** 
+     * Verify that the input is permitted at this position.
+     * @param input the String input to check for validity
+     * @param position the int proposed position (0-based) 
+     *   for the input (position 0 is for first argument)
+     * @return null if this input is valid at this position,
+     *         or a String error message otherwise.
+     */
+    String validArgument(String input, int position) {
+        if (null == input) {
+            return "null input";
+        }
+        // assert numArguments == permittedInput.length
+        if ((position < 0) || (position >= numArguments)) {
+            return "no input permitted at " + position;
+        }
+        String[] permitted = permittedArguments[position];
+        for (int i = 0; i < permitted.length; i++) {
+            if (input.equals(permitted[i])) {
+                return null;
+            }
+        }
+        return input + " not permitted, expecting one of "
+            + Arrays.asList(permitted);
+    }
+
+    String getName() {
+        return name;
+    }
+    Object getKey() {
+        return family;
+    }
+
+    private String optionIdentifier() {
+        return optionIdentifier;
+    }
+
+    private Prefix hasPrefix(String value) {
+        for (Iterator iter = permittedPrefixes.iterator();
+            iter.hasNext();
+            ) {
+            Prefix prefix = (Prefix) iter.next();
+            if (-1 != prefix.prefixLength(value)) {
+                return prefix;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * An option family identifies a set of related options that
+     * might share no literal specification.
+     * E.g., the compiler family of options might include
+     * -ajc and -eclipse, and the debugInfo family of options
+     * might include -g and -g:vars.
+     * Option families may permit or avoid option collisions.
+     * <p>
+     * For subclasses to permit some collisions and not others,
+     * they should set permitMultipleFamilyValues to false 
+     * and implement <code>doCollision(Option, Option)</code>.
+     * <p>
+     * This relies on Factory to ensure that familyName is
+     * a unique identifier for the factory.
+     */
+    public static class Family implements Comparable {
+
+        /** unique String identifier for this family */
+        private final String familyName;
+
+        /** if true, then report no collisions */
+        private final boolean permitMultipleFamilyValues;
+
+        protected Family(
+            String familyName,
+            boolean permitMultipleFamilyValues) {
+            this.familyName = familyName;
+            this.permitMultipleFamilyValues = permitMultipleFamilyValues;
+        }
+
+        public int compareTo(Object arg0) {
+            Family family = (Family) arg0;
+            return familyName.compareTo(family.familyName);
+        }
+
+        public boolean sameFamily(Family family) {
+            return (
+                (null != family) && familyName.equals(family.familyName));
+        }
+
+        boolean permitMultipleFamilyValues() {
+            return permitMultipleFamilyValues;
+        }
+
+        /**
+         * Options collide if they share the same family
+         * but are not the same,
+         * and multiple values are not permitted by the family.
+         * @param lhs the Option to compare with rhs
+         * @param rhs the Option to compare with lhs
+         * @return true if the two options collide, false otherwise
+         * @throws IllegalArgumentException if the input differ
+         *         and share the same family, but this isn't it.
+         */
+        public final boolean collision(Option lhs, Option rhs) {
+            if ((lhs == rhs) || (null == lhs) || (null == rhs)) {
+                return false;
+            }
+            Family lhsFamily = lhs.getFamily();
+            Family rhsFamily = rhs.getFamily();
+            if (!(lhsFamily.sameFamily(rhsFamily))) {
+                return false;
+            }
+            if (lhs.sameOptionIdentifier(rhs)) {
+                return false;
+            }
+            if (this != lhsFamily) {
+                String s =
+                    "expected family " + this +" got family " + lhsFamily;
+                throw new IllegalArgumentException(s);
+            }
+            return doCollision(lhs, rhs);
+        }
+
+        /**
+         * Subclasses implement this to resolve collisions on
+         * a case-by-case basis.  Input are guaranteed to be 
+         * non-null, different, and to share this family.
+         * This implementation returns
+         * <code>!permitMultipleFamilyValues</code>.
+         * 
+         * @param lhs the Option to compare
+         * @param rhs the other Option to compare
+         * @return true if there is a collision.
+         */
+        protected boolean doCollision(Option lhs, Option rhs) {
+            return !permitMultipleFamilyValues;
+        }
+    }
+
+    /** 
+     * A factory enforces a namespace on options.
+     * All options produced from a given factory are unique,
+     * as are all families.
+     * Once an option or family is created, it cannot be changed.
+     * To have a family permit multiple values
+     * (i.e., ignore collisions), set up the family before any
+     * associated options are created using
+     * <code>setupFamily(String, boolean)</code>.
+     */
+    public static class Factory {
+        private final String factoryName;
+
+        /** enforce uniqueness of family */
+        private final Map familyNameToFamily = new TreeMap();
+
+        /** enforce uniqueness of options */
+        private final ArrayList names = new ArrayList();
+
+        public Factory(String factoryName) {
+            this.factoryName = factoryName;
+        }
+
+        /**
+         * Ensure that the family with this name has the 
+         * specified permission.  If the family does not exist,
+         * it is created.  If it does, the permission is checked.
+         * If this returns false, there is no way to change the
+         * family permission.
+         * @param name the String identifier for the family
+         * @param permitMultipleValues the boolean permission whether to
+         *  allow multiple values in this family
+         * @return true if family exists with this name and permission
+         */
+        public boolean setupFamily(
+            String name,
+            boolean permitMultipleValues) {
+            LangUtil.throwIaxIfNull(name, "name");
+            Family family;
+            synchronized (familyNameToFamily) {
+                family = (Family) familyNameToFamily.get(name);
+                if (null == family) {
+                    family = new Family(name, permitMultipleValues);
+                    familyNameToFamily.put(name, family);
+                } else if (
+                    permitMultipleValues
+                        != family.permitMultipleFamilyValues) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Register a family with this factory.
+         * @return null if the family was successfully registered,
+         *        or a String error otherwise
+         */
+        public String registerFamily(Family family) {
+            if (null == family) {
+                return "null family";
+            }
+            synchronized (familyNameToFamily) {
+                Family knownFamily =
+                    (Family) familyNameToFamily.get(family.familyName);
+                if (null == knownFamily) {
+                    familyNameToFamily.put(family.familyName, family);
+                } else if (!knownFamily.equals(family)) {
+                    return "different family registered, have "
+                        + knownFamily
+                        + " registering "
+                        + family;
+                }
+            }
+
+            return null;
+        }
+
+        public Option create(String name) {
+            return create(name, name, FORCE_PREFIXES, false);
+        }
+
+        public Option create(
+            String name,
+            String family,
+            Prefixes permittedPrefixes,
+            boolean acceptSuffixedInput) {
+            return create(
+                name,
+                family,
+                permittedPrefixes,
+                acceptSuffixedInput,
+                (String[][]) null);
+        }
+
+        public Option create(
+            String name,
+            String family,
+            Prefixes permittedPrefixes,
+            boolean acceptSuffixedInput,
+            String[][] permittedArguments) {
+            LangUtil.throwIaxIfNull(name, "name");
+            LangUtil.throwIaxIfNull(family, "family");
+            LangUtil.throwIaxIfNull(
+                permittedPrefixes,
+                "permittedPrefixes");
+            Family resolvedFamily;
+            synchronized (familyNameToFamily) {
+                resolvedFamily = (Family) familyNameToFamily.get(family);
+                if (null == resolvedFamily) {
+                    resolvedFamily = new Family(family, false);
+                    familyNameToFamily.put(family, resolvedFamily);
+                }
+            }
+            Option result =
+                new Option(
+                    name,
+                    resolvedFamily,
+                    permittedPrefixes,
+                    acceptSuffixedInput,
+                    permittedArguments);
+            synchronized (names) {
+                String optionIdentifier = result.optionIdentifier();
+                if (names.contains(optionIdentifier)) {
+                    String s = "not unique: " + result;
+                    throw new IllegalArgumentException(s);
+                } else {
+                    names.add(optionIdentifier);
+                }
+            }
+            return result;
+        }
+
+        public String toString() {
+            return Factory.class.getName() + ": " + factoryName;
+        }
+
+        private void checkUnique(Option result) {
+            String name = result.family + "." + result.name;
+        }
+    }
+
+    /**
+     * The actual input value for an option.
+     * When an option takes arguments, all the arguments 
+     * are absorbed/flattened into its value.
+     */
+    public static class Value {
+        private static final String FLATTEN_DELIM = "_";
+
+        private static final int NOTARGUMENT = -1;    
+
+        private static String flatten(String prefix, String suffix) {
+            return prefix + FLATTEN_DELIM + suffix;
+        }
+
+        private static String[] unflatten(Value value) {
+            if (value.argIndex == Value.NOTARGUMENT) {
+                return new String[] { value.value };
+            }
+            StringTokenizer st =
+                new StringTokenizer(value.value, FLATTEN_DELIM);
+            String[] result = new String[st.countTokens()];
+            // assert result.length == 1+inputIndex
+            for (int i = 0; i < result.length; i++) {
+                result[i] = st.nextToken();
+            }
+            return result;
+
+        }
+
+        public final String value;
+        public final Prefix prefix;
+        public final Option option;
+        private final int argIndex;
+
+        private Value(
+            String value,
+            Prefix prefix,
+            Option option) {
+            this(value, prefix, option, NOTARGUMENT);
+        }
+        private Value(
+            String value,
+            Prefix prefix,
+            Option option,
+            int argIndex) {
+            this.value = value;
+            this.prefix = prefix;
+            this.option = option;
+            this.argIndex = argIndex;
+            // asserts deferred - local clients only
+            // assert null != value
+            // assert null != prefix
+            // assert null != option
+            // assert 0 <= inputIndex
+            // assert inputIndex <= option.numArguments()
+            // assert {number of DELIM} == argIndex
+        }
+
+        public String[] unflatten() {
+            return unflatten(this);
+        }
+
+        /**
+         * Create new value same as this, but with new prefix.
+         * If the prefix is the same, return this.
+         * @param prefix the Prefix to convert to
+         * @return Value with new prefix - never null
+         */
+        public Value convert(Prefix prefix) {
+            LangUtil.throwIaxIfNull(prefix, "prefix");
+            if (this.prefix.equals(prefix)) {
+                return this;
+            }
+            return new Value(
+                this.value,
+                prefix,
+                this.option,
+                this.argIndex);
+        }
+
+        /** 
+         * 
+         * @param other
+         * @return true if other == this for purposes of collisions
+         */
+        public boolean sameValueIdentifier(Value other) {
+            return (
+                (null != other)
+                    && sameValueIdentifier(option, other.value));
+        }
+
+        public boolean sameValueIdentifier(Option option, String value) {
+            return (
+                (null != option)
+                    && this.option.sameOptionIdentifier(option)
+                    && this.value.equals(value));
+        }
+
+        public boolean conflictsWith(Value other) {
+            return (
+                (null != other)
+                    && option.equals(other.option)
+                    && (prefix.force == other.prefix.force)
+                    && ((prefix.set != other.prefix.set)
+                        || !value.equals(other.value)));
+        }
+
+        public String toString() {
+            return option + "=" + prefix + value;
+        }
+
+        final Value nextInput(String input) throws InvalidInputException {
+            final int index = argIndex + 1;
+            String err = option.validArgument(input, index);
+            if (null != err) {
+                throw new InvalidInputException(err, input, option);
+            }
+            return new Value(flatten(value, input), prefix, option, index);
+        }
+    }
+
+    /**
+     * A bunch of prefixes.
+     */
+    public static class Prefixes {
+        final List list;
+        private Prefixes(Prefix[] prefixes) {
+            if (LangUtil.isEmpty(prefixes)) {
+                list = Collections.EMPTY_LIST;
+            } else {
+                list =
+                    Collections.unmodifiableList(
+                        Arrays.asList(
+                            LangUtil.safeCopy(prefixes, new Prefix[0])));
+            }
+        }
+        public Iterator iterator() {
+            return list.iterator();
+        }
+    }
+
+    /**
+     * A permitted prefix for an option, mainly so that options
+     * "-verbose", "^verbose", and "!verbose" can be treated
+     * as variants (set, force-off, and force-on) of the
+     * same "verbose" option.
+     */
+    public static class Prefix {
+        private final String prefix;
+        private final int prefixLength;
+        private final String name;
+        private final boolean set;
+        private final boolean force;
+        private Prefix(
+            String prefix,
+            String name,
+            boolean set,
+            boolean force) {
+            this.prefix = prefix;
+            this.name = name;
+            this.set = set;
+            this.force = force;
+            this.prefixLength = prefix.length();
+        }
+
+        /** 
+         * Render a value for input if this is set.
+         * @param value the String to render as an input value
+         * @return null if value is null or option is not set,
+         *    "-" + value otherwise
+         */
+        public String render(String value) {
+            return ((!set || (null == value)) ? null : "-" + value);
+        }
+
+        boolean forceOff() {
+            return force && !set;
+        }
+        boolean forceOn() {
+            return force && set;
+        }
+        public boolean isSet() {
+            return set;
+        }
+        private int length() {
+            return prefixLength;
+        }
+        private int prefixLength(String input) {
+            if ((null != input) && input.startsWith(prefix)) {
+                return length();
+            }
+            return -1;
+        }
+        public String toString() {
+            return prefix;
+        }
+    }
+
+    /**
+     * Thrown when an Option specifies required arguments,
+     * but the arguments are not available.
+     */
+    public static class InvalidInputException extends Exception {
+        public final String err;
+        public final String input;
+        public final Option option;
+        InvalidInputException(String err, String input, Option option) {
+            super(err);
+            this.err = err;
+            this.input = input;
+            this.option = option;
+        }
+        public String getFullMessage() {
+            return "illegal input \""
+                + input
+                + "\" for option "
+                + option
+                + ": "
+                + err;
+        }
+    }
+}
diff --git a/testing/src/org/aspectj/testing/util/options/Options.java b/testing/src/org/aspectj/testing/util/options/Options.java
new file mode 100644
index 000000000..642ea0ca7
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/options/Options.java
@@ -0,0 +1,152 @@
+/* *******************************************************************
+ * Copyright (c) 2003 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: 
+ *     Wes Isberg     initial implementation 
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.util.LangUtil;
+
+/**
+ * A bunch of options that handle search boilerplate.
+ * This enforces an initialization phase by permitting
+ * options to be added only until frozen, and 
+ * permitting matching only after frozen.
+ */
+public class Options {
+
+    /** if true, then perform extra checks to debug problems */
+    private static final boolean verifying = false;
+    private static final boolean FROZEN = true;
+
+    /**
+     * List input unmatched by options, if any.
+     * @param input the String[] used to generate the values
+     * @param values the Option.Value[] found from the input
+     * @return null if no values are null, String list of missed otherwise
+     */
+    public static String missedMatchError(
+        String[] input,
+        Values values) {
+        int[] missed = values.indexMissedMatches();
+        LangUtil.throwIaxIfNull(input, "input");
+        LangUtil.throwIaxIfNull(values, "values");
+        LangUtil.throwIaxIfFalse(
+            input.length == values.length(),
+            "input is not matched by values");
+        if (0 == missed.length) {
+            return null;
+        }
+        StringBuffer sb = new StringBuffer();
+        sb.append("missed values: [");
+        for (int i = 0; i < missed.length; i++) {
+            if (i > 0) {
+                sb.append(", ");
+            }
+            sb.append(missed[i] + ": " + input[missed[i]]);
+        }
+        sb.append("]");
+        return sb.toString();
+    }
+
+    private final ArrayList options = new ArrayList();
+    private final boolean stopAtFirstMatch;
+    private boolean frozen = !FROZEN;
+
+    public Options(boolean stopAtFirstMatch) {
+        this.stopAtFirstMatch = stopAtFirstMatch;
+    }
+    
+    public void freeze() {
+        if (frozen != FROZEN) {
+            frozen = FROZEN;
+        } 
+    }
+
+    public boolean isFrozen() {
+        return (frozen == FROZEN);
+    }
+
+    public void addOption(Option option) {
+        checkFrozen("adding option", !FROZEN);
+        LangUtil.throwIaxIfNull(option, "option");
+        options.add(option);
+    }
+
+    /**
+     * Associate options matched, if any, with input by index.
+     * If an input element is not matched, the corresponding
+     * result element will be null.
+     * If there are multi-argument options matched, then
+     * only the initial element will be non-null, but it
+     * will contain the accumulated value of the arguments.
+     * @param input the String[] of input
+     * @return Option.Value[] corresponding to input
+     * @throws Option.InvalidInputException when encountering
+     *         invalid arguments to a matched multi-argument option.
+     */
+    public Values acceptInput(String[] input)
+        throws Option.InvalidInputException {
+        checkFrozen("matching options", FROZEN);
+        if ((null == input) || (0 == input.length)) {
+            return Values.EMPTY;
+        }
+        Option.Value[] results = new Option.Value[input.length];
+        for (int i = 0; i < input.length; i++) {
+            Option.Value result = firstMatch(input[i]);
+            final int index = i;
+            if (null != result) {
+                for (int len = result.option.numArguments();
+                    len > 0;
+                    len--) {
+                    i++;
+                    if (i >= input.length) {
+                        throw new Option.InvalidInputException(
+                            "not enough arguments",
+                            null,
+                            result.option);
+                    }
+                    result = result.nextInput(input[i]);
+                }
+            }
+            results[index] = result;
+        }
+        return Values.wrapValues(results);
+    }
+
+    private void checkFrozen(String actionLabel, boolean expectFrozen) {
+        if (expectFrozen != isFrozen()) {
+            if (null == actionLabel) {
+                actionLabel = "use";
+            }
+            if (expectFrozen) {
+                actionLabel = "must freeze before " + actionLabel;
+            } else {
+                actionLabel = "frozen before " + actionLabel;
+            }
+            throw new IllegalStateException(actionLabel);
+        }
+    }
+
+    private Option.Value firstMatch(String value) {
+        LangUtil.throwIaxIfNull(value, "value");
+        ArrayList list = new ArrayList();
+        for (Iterator iter = options.iterator(); iter.hasNext();) {
+            Option option = (Option) iter.next();
+            Option.Value result = option.acceptValue(value);
+            if (null != result) {
+                return result;
+            }
+        }
+        return null;
+    }
+}
diff --git a/testing/src/org/aspectj/testing/util/options/Values.java b/testing/src/org/aspectj/testing/util/options/Values.java
new file mode 100644
index 000000000..21cd3b52f
--- /dev/null
+++ b/testing/src/org/aspectj/testing/util/options/Values.java
@@ -0,0 +1,714 @@
+/* *******************************************************************
+ * Copyright (c) 2003 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: 
+ *     Wes Isberg     initial implementation 
+ * ******************************************************************/
+
+package org.aspectj.testing.util.options;
+
+import java.util.*;
+
+import org.aspectj.testing.util.options.Option.Value;
+import org.aspectj.util.LangUtil;
+
+/**
+ * Wrapper for Value[] that handles search boilerplate.
+ */
+public class Values {
+    public static final Values EMPTY;
+    /** used by methods taking Selector to halt processing early */
+    private static final boolean VERIFYING = true;
+    private static final boolean FIND_ALL = true;
+
+    private static final String NO_ERROR = "no error";
+
+    static {
+        EMPTY = new Values(new Value[0]);
+    }
+
+    public static Values wrapValues(Value[] values) {
+        if ((null == values) || (0 == values.length)) {
+            return EMPTY;
+        }
+        return new Values(values);
+    }
+
+    public static Values wrapValues(Values[] values) {
+        if ((null == values) || (0 == values.length)) {
+            return EMPTY;
+        }
+        Value[] input = null;
+        if (values.length == 1) {
+            input = values[0].asArray();
+            LangUtil.throwIaxIfNull(input, "values");
+        } else {
+            int length = 0;
+            for (int i = 0; i < values.length; i++) {            
+                if (values[i] == null) {
+                    LangUtil.throwIaxIfNull(null, "null value[" + i + "]");
+                }
+                length += values[i].length();
+            }
+            input = new Value[length];
+            length = 0;
+            Value[] temp;
+            for (int i = 0; i < values.length; i++) {
+                temp = values[i].asArray();
+                System.arraycopy(temp, 0, input, length, temp.length);
+                length += temp.length;            
+            }
+        }
+        return new Values(input);
+    }
+    
+    static int[] invert(int[] missed, int length) {
+        final int MAX = length;
+        final int len = MAX - missed.length;
+        final int[] result = new int[len];
+        int missedIndex = 0;
+        int resultIndex = 0;
+        for (int counter = 0; counter < MAX; counter++) {
+            // catch result up to missed
+            while (((missedIndex >= missed.length)
+                || (missed[missedIndex] > counter))
+                && (counter < MAX)) {
+                result[resultIndex++] = counter++;
+            }
+            // absorb missed up to counter
+            while ((missedIndex < missed.length)
+                && (missed[missedIndex] <= counter)
+                && (counter < MAX)) {
+                missedIndex++;
+            }
+        }
+        return result;
+    }
+
+    private static Option.Value[] toArray(ArrayList list) {
+        return (Option.Value[]) list.toArray(new Option.Value[0]);
+    }
+
+    /**
+     * Resolve input to remove any values matching the same options,
+     * where option conflicts are handled by option forcing.
+     * First, for any force-off value, all matching set-on and
+     * the force-off itself are removed.  At this time, if there
+     * is a matching force-on, then this will return an error.
+     * Next, for any force-on value, it is converted to set-on,
+     * and any other matching set-on value is removed.
+     * Finally, this signals a collision if two values share 
+     * the same option family and the family reports that this is
+     * a collision.
+     * In all cases, only the first error detected is reported.
+     * @param input the Option.Value[] matched from the input,
+     *              (forced/duplicate options will be set to null)
+     * @return String error during resolution, or null if no error
+     */
+    private static String resolve(Option.Value[] input) {
+        String err = null;
+        if (LangUtil.isEmpty(input)) {
+            return null;
+        }
+
+        Map familyToMatches = new TreeMap();
+        for (int i = 0;(null == err) && (i < input.length); i++) {
+            if (null != input[i]) {
+                Option.Family family = input[i].option.getFamily();
+                int[] matches = (int[]) familyToMatches.get(family);
+                if (null == matches) {
+                    matches = match(input, i);
+                    familyToMatches.put(family, matches);
+                }
+            }
+        }
+
+        familyToMatches = Collections.unmodifiableMap(familyToMatches);
+        for (Iterator iter = familyToMatches.entrySet().iterator();
+            (null == err) && iter.hasNext();
+            ) {
+            Map.Entry entry = (Map.Entry) iter.next();
+            int[] matches = (int[]) entry.getValue();
+            err = resolve(input, matches);
+        }
+        return err;
+    }
+
+    /**
+     * Resolve all related options into one 
+     * by nullifying or modifying the values.
+     * 
+     * First, for any force-off value, 
+     * remove all identical set-on 
+     * and the force-off itself, 
+     * and alert on any identical force-on.
+     *
+     * Next, for any force-on value, 
+     * convert to set-on,
+     * throw Error on any same-family force-off value,
+     * remove any identical force-on or set-on value,
+     * alert on any other non-identical same-family force-on value,
+     * remove any same-family set-on value,
+     * and alert on any same-family set-off value.
+     * 
+     * Finally, alert if any two remaining values share 
+     * the same option family, unless the option is marked
+     * as permitting multiple values.
+     * 
+     * @param input the Option.Value[] matching the input
+     * @param matches the int[] list of indexes into input for
+     *      values for related by option
+     *     (all such values must have option matched by family)
+     * @return String error, if any, or null if no error
+     * @see #match(Option.Value[], int)
+     */
+    private static String resolve(Option.Value[] input, int[] matches) {
+        String err = null;
+        // seek force-off
+        Option.Value forceOff = null;
+        Option option = null;
+        // find and remove any force-off
+        for (int i = 0;(null == err) && (i < matches.length); i++) {
+            Option.Value value = input[matches[i]];
+            if (null != value) {
+                // verify that matches are in the same family
+                if (VERIFYING) {
+                    if (null == option) {
+                        option = value.option;
+                    } else if (!(option.sameOptionFamily(value.option))) {
+                        String s =
+                            value.option
+                                + " has different family from "
+                                + option;
+                        throw new IllegalArgumentException(s);
+                    }
+                }
+                if (value.prefix.forceOff()) {
+                    err = removeForceOff(input, value, matches);
+                }
+            }
+        }
+        // find and set any force-on, removing others
+        for (int i = 0;(null == err) && (i < matches.length); i++) {
+            Option.Value value = input[matches[i]];
+            if (null != value) {
+                if (value.prefix.forceOn()) {
+                    err = convertForceOn(input, i, matches);
+                }
+            }
+        }
+        // remove any exact duplicates
+        for (int i = 0;(null == err) && (i < matches.length); i++) {
+            Option.Value value = input[matches[i]];
+            if (null != value) {
+                for (int j = i + 1; j < matches.length; j++) {
+                    if (value.sameValueIdentifier(input[matches[j]])) {
+                        input[matches[j]] = null;
+                    }
+                }
+            }
+        }
+        // signal error if two left unless permitMultipleFamilyValues
+        Option.Value first = null;
+        for (int i = 0;(null == err) && (i < matches.length); i++) {
+            Option.Value value = input[matches[i]];
+            if (null != value) {
+                if (null == first) {
+                    first = value;
+                    if (first
+                        .option
+                        .getFamily()
+                        .permitMultipleFamilyValues()) {
+                        break;
+                    }
+                } else {
+                    err = "collision between " + first + " and " + value;
+                }
+            }
+        }
+
+        return err;
+    }
+
+    /**
+     * For any force-off value, 
+     * remove all  set-on or force-off with same value
+     * (including the force-off itself), 
+     * and alert on any identical force-on.
+     * @param input the Option.Value[] matching the input
+     * @param value the force-off Option.Value to remove
+     * @param matches the int[] list of indexes into input for
+     *      values for related by option
+     *     (all such values must have matching option)
+     * @return String error if any
+     */
+    private static String removeForceOff(
+        Option.Value[] input,
+        Option.Value value,
+        int[] matches) {
+        if (!value.prefix.forceOff()) {
+            throw new IllegalArgumentException(
+                "expecting force-off: " + value);
+        }
+        for (int i = 0; i < matches.length; i++) {
+            Option.Value match = input[matches[i]];
+            if ((null != match) && value.sameValueIdentifier(match)) {
+                if (match.prefix.forceOn()) {
+                    return "force conflict between "
+                        + value
+                        + " and "
+                        + match;
+                } else {
+                    input[matches[i]] = null; // unset matches[i]? 
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * For this force-on value, convert to set-on,
+     * throw Error on any same-family force-off value,
+     * remove any identical force-on or set-on value,
+     * alert on any other non-identical same-family force-on value,
+     * remove any same-family set-on value,
+     * and alert on any same-family set-off value.
+     * This must be called after <code>removeForceOff(..)</code>.
+     * @param input the Option.Value[] to modify
+     * @param valueIndex the int index in matches to find the force-on
+     *                   and to start after
+     * @param matches the int[] map into input entries with matching options
+     * @return
+     * @throw Error if any matching force-off found
+     */
+    private static String convertForceOn(
+        Option.Value[] input,
+        int valueIndex,
+        int[] matches) {
+        Option.Value value = input[matches[valueIndex]];
+        if (!value.prefix.forceOn()) {
+            throw new IllegalArgumentException(
+                "expecting force-on: " + value);
+        }
+        input[matches[valueIndex]] = value.convert(Option.ON);
+        for (int i = 0; i < matches.length; i++) {
+            if (i == valueIndex) {
+                continue;
+            }
+            Option.Value match = input[matches[i]];
+            if (null != match) {
+                // assert match.sameOptionFamily(value);
+                if (match.prefix.forceOff()) {
+                    throw new Error(
+                        "unexpected force-off:"
+                            + match
+                            + " when processing "
+                            + value);
+                }
+                if (value.option.sameOptionIdentifier(match.option)) {
+                    input[matches[i]] = null;
+                    // remove any identical force-on or set
+                } else if (match.prefix.forceOn()) {
+                    return "conflict between " + match + " and " + value;
+                } else if (match.prefix.isSet()) {
+                    input[matches[i]] = null;
+                    // remove any same-value set-on value
+                } else { // same family, force-off
+                    return "collision between " + match + " and " + value;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get a list of input matching the option in the initial value,
+     * rendered as indexes into the input array.
+     * @param input the Option.Value[] to seek in
+     * @param start the int index of the starting position
+     * @return int[] of indexes into input with the same option
+     *         as index[start] - never null, but can be empty
+     */
+    private static int[] match(Option.Value[] input, int start) {
+        IntList result = new IntList();
+        Option.Family key = null;
+        Option.Family nextKey = null;
+        for (int i = start; i < input.length; i++) {
+            if (null != input[i]) {
+                nextKey = input[i].option.getFamily();
+                if (null == key) {
+                    key = nextKey;
+                    result.add(i);
+                } else if (key.equals(nextKey)) {
+                    result.add(i);
+                }
+            }
+        }
+        return result.getList();
+    }
+       
+    static int nullify(Option.Value[] values, Selector selector) {
+        LangUtil.throwIaxIfNull(selector, "selector");
+        int changed = 0;
+        for (int i = 0; i < values.length; i++) {
+            final boolean accepted;
+            try {
+                accepted = selector.accept(values[i]);
+            } catch (Error e) {
+                if (e != Selector.STOP) {
+                    throw e;
+                }
+                break;
+            }
+            if (accepted) {
+                if (null != values[i]) {
+                    values[i] = null;
+                    changed++;
+                }
+            }
+        }
+        return changed;
+    }
+
+    /**
+     * Render set values as String using associated prefix.
+     * @param values the Value[] to render
+     * @return String[] of values rendered for output
+     *   (never null or longer than values, but might be shorter)
+     */
+    private static String[] render(Value[] values) {
+        ArrayList list = new ArrayList();
+        for (int i = 0; i < values.length; i++) {
+            if (null != values[i]) {
+                String[] output = values[i].unflatten();
+                if (LangUtil.isEmpty(output)) {
+                    throw new Error("no output for " + values[i]);
+                }
+
+                String s = values[i].prefix.render(output[0]);
+                if (null != s) { // this means the prefix is set
+                    list.add(s);
+                    for (int j = 1; j < output.length; j++) {
+                        list.add(output[j]);
+                    }
+                }
+            }
+        }
+        return (String[]) list.toArray(new String[list.size()]);
+    }
+
+    private final Option.Value[] values;
+    private Option.Value[] valuesNotNull;
+    private String resolveError;
+
+    private Values(Value[] values) {
+        this.values = new Value[values.length];
+        System.arraycopy(values, 0, this.values, 0, values.length);
+    }
+
+    public int length() {
+        return values.length;
+    }
+
+    public Option.Value[] asArray() {
+        Option.Value[] result = new Option.Value[values.length];
+        System.arraycopy(values, 0, result, 0, result.length);
+        return result;
+    }
+
+    /**
+     * Emit as String[] the non-null values. 
+     * @return String[] of matched entries (never null, elements not null)
+     */
+    public String[] render() {
+        return Values.render(valuesNotNull());
+    }
+
+    public String toString() {
+        return Arrays.asList(values).toString();
+    }
+
+    /**
+     * Create index into values of those that were matched,
+     * including the options and their arguments.
+     * @return int[] of elements in values that are not null (options)
+     *         or that represent option arguments
+     */
+    public int[] indexMatches() {
+        // must be in order, low to high
+        final int[] missed = indexMissedMatches();
+        return invert(missed, length());
+    }
+
+    /**
+     * Create index into values of missed input,
+     * taking into account that matched arguments are 
+     * represented as null.
+     * @return int[] of elements in values that are null
+     *         or optionally represent option arguments
+     */
+    public int[] indexMissedMatches() {
+        MissedSelector selector = new MissedSelector();
+        find(selector, FIND_ALL);
+        String errors = selector.getErrors();
+        if (null != errors) {
+            throw new Error(errors);
+        }
+        return selector.getResult();
+    }
+
+    public Value firstInFamily(Option.Family family) {
+        return findFirst(new ValueSelector(family));
+    }
+
+    public Value[] allInFamily(Option.Family family) {
+        return find(new ValueSelector(family), FIND_ALL);
+    }
+
+    public Value firstOption(Option option) {
+        return findFirst(new ValueSelector(option));
+    }
+
+    public Value[] allOption(Option option) {
+        return find(new ValueSelector(option), FIND_ALL);
+    }
+
+    public Value firstValue(Option option, String value) {
+        LangUtil.throwIaxIfNull(value, "value");
+        return findFirst(new ValueSelector(option, value));
+    }
+
+    public Value[] allValues(Option option, String value) {
+        LangUtil.throwIaxIfNull(value, "value");
+        return find(new ValueSelector(option, value), FIND_ALL);
+    }
+
+    public boolean isResolved() {
+        return ((this != EMPTY) && (null != resolveError));
+    }
+
+    /**
+     * 
+     * @param selector the Selector to pick out entries to nullify
+     *    (should throw STOP to halt processing)
+     * @return Values resulting from nullifying entries,
+     * or this if none were changed
+     */
+    public Values nullify(Selector selector) {
+        if (null == selector) {
+            return this;
+        }
+        Value[] temp = asArray();
+        int changed = nullify(temp, selector);
+        if (0 == changed) {
+            return this;    
+        }
+        return new Values(temp);        
+    }
+    
+    /**
+     * Resolve options, removing duplicates by force if necessary.
+     * If any error is returned, then the values are left unchanged.
+     * @return String error, if any
+     * @throws IllegalStateException if <code>isResolved()</code>
+     */
+    public String resolve() {
+        if (isResolved()) {
+            throw new IllegalStateException("already resolved");
+        }
+        Option.Value[] temp = asArray();
+        resolveError = resolve(temp);
+        if (null == resolveError) {
+            System.arraycopy(temp, 0, values, 0, temp.length);
+            valuesNotNull = null;
+            resolveError = NO_ERROR;
+            return null;
+        }
+        return resolveError;
+    }
+
+    protected Option.Value findFirst(Selector filter) {
+        Option.Value[] result = find(filter, !FIND_ALL);
+        return (0 == result.length ? null : result[0]);
+    }
+
+    protected Option.Value[] find(Selector filter, boolean findAll) {
+        LangUtil.throwIaxIfNull(filter, "filter");
+        ArrayList result = new ArrayList();
+        for (int i = 0; i < values.length; i++) {
+            final boolean accepted;
+            try {
+                accepted = filter.accept(values[i]);
+            } catch (Error e) {
+                if (Selector.STOP != e) {
+                    throw e;
+                }
+                break;
+            }
+            if (accepted) {
+                result.add(values[i]);
+                if (findAll != FIND_ALL) {
+                    break;
+                }
+            }
+        }
+        return toArray(result);
+    }
+
+    private Option.Value[] valuesNotNull() {
+        if (null == valuesNotNull) {
+            ArrayList list = new ArrayList();
+            for (int i = 0; i < this.values.length; i++) {
+                if (null != this.values[i]) {
+                    list.add(this.values[i]);
+                }
+            }
+            valuesNotNull = toArray(list);
+        }
+        return valuesNotNull;
+    }
+
+    public static class Selector {
+        public static final Error STOP = new Error("stop invoking Selector");
+        protected Selector() {
+        }
+        protected boolean accept(Value value) {
+            return false;
+        }
+    }
+    protected static class ValueSelector extends Selector {
+
+        private final Option option;
+        private final Option.Family family;
+        private final String value;
+        ValueSelector(Option.Family family) {
+            LangUtil.throwIaxIfNull(family, "family");
+            this.family = family;
+            option = null;
+            value = null;
+        }
+        ValueSelector(Option option) {
+            this(option, (String) null);
+        }
+        ValueSelector(Option option, String value) {
+            LangUtil.throwIaxIfNull(option, "option");
+            this.option = option;
+            family = null;
+            this.value = value;
+        }
+        protected boolean accept(Value value) {
+            if (null == value) {
+                return false;
+            }
+            if (null != family) {
+                return family.sameFamily(value.option.getFamily());
+            } else if (!option.sameOptionIdentifier(value.option)) {
+                return false;
+            } else {
+                return ((null == this.value)
+                    || (this.value.equals(value.value)));
+            }
+        }
+    }
+
+    /** pick all null entries (except for args), return as int[] */
+    protected static class MissedSelector extends Selector {
+        public static final String DELIM = "; ";
+        final IntList result = new IntList();
+        int index;
+        final StringBuffer errors = new StringBuffer();
+        int argsExpected;
+        Option argsExpectedFor;
+        MissedSelector() {
+        }
+
+        int[] getResult() {
+            return result.getList();
+        }
+
+        /**
+         * add index if value is null
+         * unless skipArguments 
+         */
+        protected boolean accept(Value value) {
+            index++;
+            if (null != value) {
+                if (0 < argsExpected) { // expected more (null) args
+                    missedArgsFor(argsExpectedFor, argsExpected);
+                }
+                argsExpected = value.option.numArguments();
+                argsExpectedFor = value.option;
+            } else if (0 < argsExpected) { // ignore null in arg position
+                argsExpected--;
+                if (0 == argsExpected) {
+                    argsExpectedFor = null;
+                }
+            } else { // null, not expecting arg, so missing
+                result.add(index - 1);
+                return true;
+            }
+            return false;
+        }
+
+        private void missedArgsFor(Option option, int numArgs) {
+            errors.append("missed ");
+            errors.append(numArgs + " args for ");
+            errors.append(option + DELIM);
+        }
+
+        String getErrors() {
+            if (0 < argsExpected) {
+            }
+            if (0 == errors.length()) {
+                return null;
+            }
+            return errors.toString();
+        }
+    }
+
+    static class IntList {
+        // not synchronized - used only in one thread
+        static String render(int[] input) {
+            if (null == input) {
+                return "null";
+            }
+            StringBuffer sb = new StringBuffer();
+            sb.append("[");
+            for (int i = 0; i < input.length; i++) {
+                if (i > 0) {
+                    sb.append(", " + input[i]);
+                } else {
+                    sb.append("" + input[i]);
+                }
+            }
+            sb.append("]");
+            return sb.toString();
+        }
+
+        private int[] input = new int[256];
+        private int insert;
+        private void add(int i) {
+            if (insert >= input.length) {
+                int[] temp = new int[insert + 256];
+                for (int j = 0; j < input.length; j++) {
+                    temp[j] = input[j];
+                }
+                input = temp;
+            }
+            input[insert++] = i;
+        }
+
+        private int[] getList() {
+            int[] result = new int[insert];
+            for (int i = 0; i < result.length; i++) {
+                result[i] = input[i];
+            }
+            return result;
+        }
+    }
+}
-- 
cgit v1.2.3