Change DalvikRunner to use newly added OptionParser.

--skip-clean is now --no-clean
--java-home now defaults to 1.5
--mode is now used to specify to run on host's java-home

DalvikRunner page examples are updated with new --mode and --java-home

Fixes junit tests to work on host by using changing classpath to be absolute paths

Some minor scaffolding to prepare for running tests within an android.app.Activity.

(Patchset 2 adds default --java-home location to usage output)
(Patchset 3 address reviewer comments, mostly newbie style issues)
(Patchset 4 address enh OptionParser issues, adds short/byte as well)
(Patchset 5 address enh OptionParser issues, work with "java" from PATH, change deviceRunnerDir to File)
diff --git a/libcore/tools/runner/Android.mk b/libcore/tools/runner/Android.mk
index ee5c4f1..851214b 100644
--- a/libcore/tools/runner/Android.mk
+++ b/libcore/tools/runner/Android.mk
@@ -32,6 +32,8 @@
         java/dalvik/runner/MainFinder.java \
         java/dalvik/runner/MainRunner.java \
         java/dalvik/runner/NamingPatternCodeFinder.java \
+        java/dalvik/runner/Option.java \
+        java/dalvik/runner/OptionParser.java \
         java/dalvik/runner/Result.java \
         java/dalvik/runner/Strings.java \
         java/dalvik/runner/TestRun.java \
diff --git a/libcore/tools/runner/java/dalvik/runner/DalvikRunner.java b/libcore/tools/runner/java/dalvik/runner/DalvikRunner.java
index 84d54ef..20e722c 100644
--- a/libcore/tools/runner/java/dalvik/runner/DalvikRunner.java
+++ b/libcore/tools/runner/java/dalvik/runner/DalvikRunner.java
@@ -17,6 +17,7 @@
 package dalvik.runner;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.LinkedHashSet;
@@ -34,25 +35,203 @@
  */
 public final class DalvikRunner {
 
-    private final File localTemp;
-    private File sdkJar;
-    private Integer debugPort;
-    private long timeoutSeconds;
-    private Set<File> expectationFiles = new LinkedHashSet<File>();
-    private File xmlReportsDirectory;
-    private String javaHome;
-    private List<String> vmArgs = new ArrayList<String>();
-    private boolean clean = true;
-    private String deviceRunnerDir = "/sdcard/dalvikrunner";
-    private List<File> testFiles = new ArrayList<File>();
+    private static class Options {
 
-    private DalvikRunner() {
-        localTemp = new File("/tmp/" + UUID.randomUUID());
-        timeoutSeconds = 10 * 60; // default is ten minutes
-        sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar");
-        expectationFiles.add(new File("dalvik/libcore/tools/runner/expectations.txt"));
+        private final List<File> testFiles = new ArrayList<File>();
+
+        @Option(names = { "--expectations" })
+        private Set<File> expectationFiles = new LinkedHashSet<File>();
+        {
+            expectationFiles.add(new File("dalvik/libcore/tools/runner/expectations.txt"));
+        }
+
+        private static String MODE_DEVICE = "device";
+        private static String MODE_HOST = "host";
+        private static String MODE_ACTIVITY = "activity";
+        @Option(names = { "--mode" })
+        private String mode = MODE_DEVICE;
+
+        @Option(names = { "--timeout" })
+        private long timeoutSeconds = 10 * 60; // default is ten minutes;
+
+        @Option(names = { "--clean" })
+        private boolean clean = true;
+
+        @Option(names = { "--xml-reports-directory" })
+        private File xmlReportsDirectory;
+
+        @Option(names = { "--verbose" })
+        private boolean verbose;
+
+        @Option(names = { "--debug" })
+        private Integer debugPort;
+
+        @Option(names = { "--device-runner-dir" })
+        private File deviceRunnerDir = new File("/sdcard/dalvikrunner");
+
+        @Option(names = { "--vm-arg" })
+        private List<String> vmArgs = new ArrayList<String>();
+
+        @Option(names = { "--java-home" })
+        private File javaHome;
+
+        @Option(names = { "--sdk" })
+        private File sdkJar = new File("/home/dalvik-prebuild/android-sdk-linux/platforms/android-2.0/android.jar");
+
+        private void printUsage() {
+            System.out.println("Usage: DalvikRunner [options]... <tests>...");
+            System.out.println();
+            System.out.println("  <tests>: a .java file containing a jtreg test, JUnit test,");
+            System.out.println("      Caliper benchmark, or a directory of such tests.");
+            System.out.println();
+            System.out.println("GENERAL OPTIONS");
+            System.out.println();
+            System.out.println("  --expectations <file>: include the specified file when looking for");
+            System.out.println("      test expectations. The file should include qualified test names");
+            System.out.println("      and the corresponding expected output.");
+            System.out.println("      Default is: " + expectationFiles);
+            System.out.println();
+            System.out.println("  --mode <device|host|activity>: specify which environment to run the");
+            System.out.println("      tests in. Options are on the device VM, on the host VM, and on");
+            System.out.println("      device within an android.app.Activity.");
+            System.out.println("      Default is: " + mode);
+            System.out.println();
+            System.out.println("  --clean: remove temporary files (default). Disable with --no-clean");
+            System.out.println("      and use with --verbose if you'd like to manually re-run");
+            System.out.println("      commands afterwards.");
+            System.out.println();
+            System.out.println("  --timeout-seconds <seconds>: maximum execution time of each");
+            System.out.println("      test before the runner aborts it.");
+            System.out.println("      Default is: " + timeoutSeconds);
+            System.out.println();
+            System.out.println("  --xml-reports-directory <path>: directory to emit JUnit-style");
+            System.out.println("      XML test results.");
+            System.out.println();
+            System.out.println("  --verbose: turn on verbose output");
+            System.out.println();
+            System.out.println("DEVICE OPTIONS");
+            System.out.println();
+            System.out.println("  --debug <port>: enable Java debugging on the specified port.");
+            System.out.println("      This port must be free both on the device and on the local");
+            System.out.println("      system.");
+            System.out.println();
+            System.out.println("  --device-runner-dir <directory>: use the specified directory for");
+            System.out.println("      on-device temporary files and code.");
+            System.out.println("      Default is: " + deviceRunnerDir);
+            System.out.println();
+            System.out.println("GENERAL VM OPTIONS");
+            System.out.println();
+            System.out.println("  --vm-arg <argument>: include the specified argument when spawning a");
+            System.out.println("      virtual machine. Examples: -Xint:fast, -ea, -Xmx16M");
+            System.out.println();
+            System.out.println("HOST VM OPTIONS");
+            System.out.println();
+            System.out.println("  --java-home <java_home>: execute the tests on the local workstation");
+            System.out.println("      using the specified java home directory. This does not impact");
+            System.out.println("      which javac gets used. When unset, java is used from the PATH.");
+            System.out.println();
+            System.out.println("COMPILE OPTIONS");
+            System.out.println();
+            System.out.println("  --sdk <android jar>: the API jar file to compile against.");
+            System.out.println("      Usually this is <SDK>/platforms/android-<X.X>/android.jar");
+            System.out.println("      where <SDK> is the path to an Android SDK path and <X.X> is");
+            System.out.println("      a release version like 1.5.");
+            System.out.println("      Default is: " + sdkJar);
+            System.out.println();
+        }
+
+        private boolean parseArgs(String[] args) {
+            final List<String> testFilenames;
+            try {
+                testFilenames = new OptionParser(this).parse(args);
+            } catch (RuntimeException e) {
+                System.out.println(e.getMessage());
+                return false;
+            }
+
+            //
+            // Semantic error validation
+            //
+
+            boolean device;
+            boolean vm;
+            if (mode.equals(MODE_DEVICE)) {
+                device = true;
+                vm = true;
+            } else if (mode.equals(MODE_HOST)) {
+                device = false;
+                vm = true;
+            } else if (mode.equals(MODE_ACTIVITY)) {
+                device = true;
+                vm = false;
+            } else {
+                System.out.println("Unknown mode: " + mode);
+                return false;
+            }
+
+
+            if (device) { // check device option consistency
+                if (javaHome != null) {
+                    System.out.println("java home " + javaHome + " should not be specified for mode " + mode);
+                    return false;
+                }
+
+            } else { // check host (!device) option consistency
+                if (javaHome != null && !new File(javaHome, "/bin/java").exists()) {
+                    System.out.println("Invalid java home: " + javaHome);
+                    return false;
+                }
+                if (debugPort != null) {
+                    System.out.println("debug port " + debugPort + " should not be specified for mode " + mode);
+                    return false;
+                }
+            }
+
+            // check vm option consistency
+            if (!vm) {
+                if (!vmArgs.isEmpty()) {
+                    System.out.println("vm args " + vmArgs + " should not be specified for mode " + mode);
+                    return false;
+                }
+            }
+
+            if (!sdkJar.exists()) {
+                System.out.println("Could not find SDK jar: " + sdkJar);
+                return false;
+            }
+
+            if (xmlReportsDirectory != null && !xmlReportsDirectory.isDirectory()) {
+                System.out.println("Invalid XML reports directory: " + xmlReportsDirectory);
+                return false;
+            }
+
+            if (testFilenames.isEmpty()) {
+                System.out.println("No tests provided.");
+                return false;
+            }
+
+            //
+            // Post-processing arguments
+            //
+
+            for (String testFilename : testFilenames) {
+                testFiles.add(new File(testFilename));
+            }
+
+            if (verbose) {
+                Logger.getLogger("dalvik.runner").setLevel(Level.FINE);
+            }
+
+            return true;
+        }
+
     }
 
+    private final Options options = new Options();
+    private final File localTemp = new File("/tmp/" + UUID.randomUUID());
+
+    private DalvikRunner() {}
+
     private void prepareLogging() {
         ConsoleHandler handler = new ConsoleHandler();
         handler.setLevel(Level.ALL);
@@ -66,139 +245,61 @@
         logger.setUseParentHandlers(false);
     }
 
-    private boolean parseArgs(String[] args) throws Exception {
-        for (int i = 0; i < args.length; i++) {
-            if ("--debug".equals(args[i])) {
-                debugPort = Integer.valueOf(args[++i]);
-
-            } else if ("--device-runner-dir".equals(args[i])) {
-                deviceRunnerDir = args[++i];
-
-            } else if ("--expectations".equals(args[i])) {
-                expectationFiles.add(new File(args[++i]));
-
-            } else if ("--java-home".equals(args[i])) {
-                javaHome = args[++i];
-                if (!new File(javaHome, "/bin/java").exists()) {
-                    System.out.println("Invalid java home: " + javaHome);
-                    return false;
-                }
-
-            } else if ("--timeout-seconds".equals(args[i])) {
-                timeoutSeconds = Long.valueOf(args[++i]);
-
-            } else if ("--sdk".equals(args[i])) {
-                sdkJar = new File(args[++i]);
-                if (!sdkJar.exists()) {
-                    System.out.println("Could not find SDK jar: " + sdkJar);
-                    return false;
-                }
-
-            } else if ("--skip-clean".equals(args[i])) {
-                clean = false;
-
-            } else if ("--verbose".equals(args[i])) {
-                Logger.getLogger("dalvik.runner").setLevel(Level.FINE);
-
-            } else if ("--vm-arg".equals(args[i])) {
-                vmArgs.add(args[++i]);
-
-            } else if ("--xml-reports-directory".equals(args[i])) {
-                xmlReportsDirectory = new File(args[++i]);
-                if (!xmlReportsDirectory.isDirectory()) {
-                    System.out.println("Invalid XML reports directory: " + xmlReportsDirectory);
-                    return false;
-                }
-
-            } else if (args[i].startsWith("-")) {
-                System.out.println("Unrecognized option: " + args[i]);
-                return false;
-
-            } else {
-                testFiles.add(new File(args[i]));
-            }
+    private void run() {
+        Vm vm;
+        if (options.mode.equals(Options.MODE_DEVICE)) {
+            vm = new DeviceDalvikVm(
+                    options.debugPort,
+                    options.timeoutSeconds,
+                    options.sdkJar,
+                    localTemp,
+                    options.vmArgs,
+                    options.clean,
+                    options.deviceRunnerDir);
+        } else if (options.mode.equals(Options.MODE_HOST)) {
+            vm = new JavaVm(
+                    options.debugPort,
+                    options.timeoutSeconds,
+                    options.sdkJar,
+                    localTemp,
+                    options.javaHome,
+                    options.vmArgs,
+                    options.clean);
+        } else if (options.mode.equals(Options.MODE_ACTIVITY)) {
+            vm = null;
+            System.out.println("Mode " + options.mode + " not currently supported.");
+            return;
+        } else {
+            System.out.println("Unknown mode mode " + options.mode + ".");
+            return;
         }
 
-        if (testFiles.isEmpty()) {
-            System.out.println("No tests provided.");
-            return false;
-        }
-
-        return true;
-    }
-
-    private void printUsage() {
-        System.out.println("Usage: DalvikRunner [options]... <tests>...");
-        System.out.println();
-        System.out.println("  <tests>: a .java file containing a jtreg test, JUnit test,");
-        System.out.println("      Caliper benchmark, or a directory of such tests.");
-        System.out.println();
-        System.out.println("OPTIONS");
-        System.out.println();
-        System.out.println("  --debug <port>: enable Java debugging on the specified port.");
-        System.out.println("      This port must be free both on the device and on the local");
-        System.out.println("      system.");
-        System.out.println();
-        System.out.println("  ----device-runner-dir <directory>: use the specified directory for");
-        System.out.println("      on-device temporary files and code.");
-        System.out.println("      Default is: " + deviceRunnerDir);
-        System.out.println();
-        System.out.println("  --expectations <file>: include the specified file when looking for");
-        System.out.println("      test expectations. The file should include qualified test names");
-        System.out.println("      and the corresponding expected output.");
-        System.out.println("      Default is: " + expectationFiles);
-        System.out.println();
-        System.out.println("  --java-home <java_home>: execute the tests on the local workstation");
-        System.out.println("      using the specified java home directory. This does not impact");
-        System.out.println("      which javac gets used. When unset, tests are run on a device");
-        System.out.println("      using adb.");
-        System.out.println();
-        System.out.println("  --sdk <android jar>: the API jar file to compile against.");
-        System.out.println("      Usually this is <SDK>/platforms/android-<X.X>/android.jar");
-        System.out.println("      where <SDK> is the path to an Android SDK path and <X.X> is");
-        System.out.println("      a release version like 1.5.");
-        System.out.println("      Default is: " + sdkJar);
-        System.out.println();
-        System.out.println("  --skip-clean: leave temporary files in their place. Useful when");
-        System.out.println("      coupled with --verbose if you'd like to manually re-run");
-        System.out.println("      commands afterwards.");
-        System.out.println();
-        System.out.println("  --timeout-seconds <seconds>: maximum execution time of each");
-        System.out.println("      test before the runner aborts it.");
-        System.out.println("      Default is: " + timeoutSeconds);
-        System.out.println();
-        System.out.println("  --vm-arg <argument>: include the specified argument when spawning a");
-        System.out.println("      virtual machine. Examples: -Xint:fast, -ea, -Xmx16M");
-        System.out.println();
-        System.out.println("  --xml-reports-directory <path>: directory to emit JUnit-style");
-        System.out.println("      XML test results.");
-        System.out.println();
-        System.out.println("  --verbose: turn on verbose output");
-        System.out.println();
-    }
-
-    private void run() throws Exception {
-        Vm vm = javaHome != null
-                ? new JavaVm(debugPort, timeoutSeconds, sdkJar, localTemp,
-                        javaHome, vmArgs, clean)
-                : new DeviceDalvikVm(debugPort, timeoutSeconds, sdkJar,
-                        localTemp, vmArgs, clean, deviceRunnerDir);
         List<CodeFinder> codeFinders = Arrays.asList(
                 new JtregFinder(localTemp),
                 new JUnitFinder(),
                 new CaliperFinder(),
                 new MainFinder());
-        Driver driver = new Driver(localTemp,
-                vm, expectationFiles, xmlReportsDirectory, codeFinders);
-        driver.loadExpectations();
-        driver.buildAndRunAllTests(testFiles);
+        Driver driver = new Driver(
+                localTemp,
+                vm,
+                options.expectationFiles,
+                options.xmlReportsDirectory,
+                codeFinders);
+        try {
+            driver.loadExpectations();
+        } catch (IOException e) {
+            System.out.println("Problem loading expectations: " + e);
+            return;
+        }
+
+        driver.buildAndRunAllTests(options.testFiles);
         vm.shutdown();
     }
 
-    public static void main(String[] args) throws Exception {
+    public static void main(String[] args) {
         DalvikRunner dalvikRunner = new DalvikRunner();
-        if (!dalvikRunner.parseArgs(args)) {
-            dalvikRunner.printUsage();
+        if (!dalvikRunner.options.parseArgs(args)) {
+            dalvikRunner.options.printUsage();
             return;
         }
         dalvikRunner.prepareLogging();
diff --git a/libcore/tools/runner/java/dalvik/runner/DeviceDalvikVm.java b/libcore/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
index 47db11f..4388565 100644
--- a/libcore/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
+++ b/libcore/tools/runner/java/dalvik/runner/DeviceDalvikVm.java
@@ -38,10 +38,10 @@
     private final Adb adb = new Adb();
 
     DeviceDalvikVm(Integer debugPort, long timeoutSeconds, File sdkJar,
-            File localTemp, List<String> additionalVmArgs, boolean clean, String runnerDir) {
+            File localTemp, List<String> additionalVmArgs, boolean clean, File runnerDir) {
         super(debugPort, timeoutSeconds, sdkJar, localTemp, additionalVmArgs, clean);
 
-        this.runnerDir = new File(runnerDir);
+        this.runnerDir = runnerDir;
         this.testTemp = new File(this.runnerDir, "/tests.tmp");
     }
 
diff --git a/libcore/tools/runner/java/dalvik/runner/Driver.java b/libcore/tools/runner/java/dalvik/runner/Driver.java
index 7a30ab7..81c7d6b 100644
--- a/libcore/tools/runner/java/dalvik/runner/Driver.java
+++ b/libcore/tools/runner/java/dalvik/runner/Driver.java
@@ -72,7 +72,7 @@
     /**
      * Builds and executes all tests in the test directory.
      */
-    public void buildAndRunAllTests(Collection<File> testFiles) throws Exception {
+    public void buildAndRunAllTests(Collection<File> testFiles) {
         localTemp.mkdirs();
 
         final BlockingQueue<TestRun> readyToRun = new ArrayBlockingQueue<TestRun>(4);
@@ -136,10 +136,15 @@
                     + readyToRun.size() + " are ready to run");
 
             // if it takes 5 minutes for build and install, something is broken
-            TestRun testRun = readyToRun.poll(300, TimeUnit.SECONDS);
+            TestRun testRun;
+            try {
+                testRun = readyToRun.poll(5 * 60, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                throw new RuntimeException("Unexpected interruption waiting for build and install", e);
+            }
+
             if (testRun == null) {
-                throw new IllegalStateException(
-                        "Expected " + tests.size() + " tests but found only " + i);
+                throw new IllegalStateException("Expected " + tests.size() + " tests but found only " + i);
             }
 
             runs.add(testRun);
diff --git a/libcore/tools/runner/java/dalvik/runner/JavaVm.java b/libcore/tools/runner/java/dalvik/runner/JavaVm.java
index 8b53477..e29b36b 100644
--- a/libcore/tools/runner/java/dalvik/runner/JavaVm.java
+++ b/libcore/tools/runner/java/dalvik/runner/JavaVm.java
@@ -24,18 +24,19 @@
  */
 final class JavaVm extends Vm {
 
-    private final String javaHome;
+    private final File javaHome;
 
     JavaVm(Integer debugPort, long timeoutSeconds, File sdkJar, File localTemp,
-            String javaHome, List<String> additionalVmArgs, boolean clean) {
+            File javaHome, List<String> additionalVmArgs, boolean clean) {
         super(debugPort, timeoutSeconds, sdkJar, localTemp, additionalVmArgs, clean);
         this.javaHome = javaHome;
     }
 
     @Override protected VmCommandBuilder newVmCommandBuilder(
             File workingDirectory) {
+        String java = javaHome == null ? "java" : new File(javaHome, "bin/java").getPath();
         return new VmCommandBuilder()
-                .vmCommand(javaHome + "/bin/java")
+                .vmCommand(java)
                 .workingDir(workingDirectory);
     }
 }
diff --git a/libcore/tools/runner/java/dalvik/runner/Option.java b/libcore/tools/runner/java/dalvik/runner/Option.java
new file mode 100644
index 0000000..779aa63
--- /dev/null
+++ b/libcore/tools/runner/java/dalvik/runner/Option.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.runner;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a field as representing a command-line option for OptionParser.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface Option {
+    /**
+     * The names for this option, such as { "-h", "--help" }.
+     * Names must start with one or two '-'s.
+     * An option must have at least one name.
+     */
+    String[] names();
+}
diff --git a/libcore/tools/runner/java/dalvik/runner/OptionParser.java b/libcore/tools/runner/java/dalvik/runner/OptionParser.java
new file mode 100644
index 0000000..64af51c
--- /dev/null
+++ b/libcore/tools/runner/java/dalvik/runner/OptionParser.java
@@ -0,0 +1,443 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package dalvik.runner;
+
+import java.io.File;
+import java.lang.reflect.Field;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Parses command line options.
+ *
+ * Strings in the passed-in String[] are parsed left-to-right. Each
+ * String is classified as a short option (such as "-v"), a long
+ * option (such as "--verbose"), an argument to an option (such as
+ * "out.txt" in "-f out.txt"), or a non-option positional argument.
+ *
+ * A simple short option is a "-" followed by a short option
+ * character. If the option requires an argument (which is true of any
+ * non-boolean option), it may be written as a separate parameter, but
+ * need not be. That is, "-f out.txt" and "-fout.txt" are both
+ * acceptable.
+ *
+ * It is possible to specify multiple short options after a single "-"
+ * as long as all (except possibly the last) do not require arguments.
+ *
+ * A long option begins with "--" followed by several characters. If
+ * the option requires an argument, it may be written directly after
+ * the option name, separated by "=", or as the next argument. (That
+ * is, "--file=out.txt" or "--file out.txt".)
+ *
+ * A boolean long option '--name' automatically gets a '--no-name'
+ * companion. Given an option "--flag", then, "--flag", "--no-flag",
+ * "--flag=true" and "--flag=false" are all valid, though neither
+ * "--flag true" nor "--flag false" are allowed (since "--flag" by
+ * itself is sufficient, the following "true" or "false" is
+ * interpreted separately). You can use "yes" and "no" as synonyms for
+ * "true" and "false".
+ *
+ * Each String not starting with a "-" and not a required argument of
+ * a previous option is a non-option positional argument, as are all
+ * successive Strings. Each String after a "--" is a non-option
+ * positional argument.
+ *
+ * Parsing of numeric fields such byte, short, int, long, float, and
+ * double fields is supported. This includes both unboxed and boxed
+ * versions (e.g. int vs Integer). If there is a problem parsing the
+ * argument to match the desired type, a runtime exception is thrown.
+ *
+ * File option fields are supported by simply wrapping the string
+ * argument in a File object without testing for the existance of the
+ * file.
+ *
+ * Parameterized Collection fields such as List<File> and Set<String>
+ * are supported as long as the parameter type is otherwise supported
+ * by the option parser. The collection field should be initialized
+ * with an appropriate collection instance.
+ *
+ * The fields corresponding to options are updated as their options
+ * are processed. Any remaining positional arguments are returned as a
+ * List<String>.
+ *
+ * Here's a simple example:
+ *
+ * // This doesn't need to be a separate class, if your application doesn't warrant it.
+ * // Non-@Option fields will be ignored.
+ * class Options {
+ *     @Option(names = { "-q", "--quiet" })
+ *     boolean quiet = false;
+ *
+ *     // Boolean options require a long name if it's to be possible to explicitly turn them off.
+ *     // Here the user can use --no-color.
+ *     @Option(names = { "--color" })
+ *     boolean color = true;
+ *
+ *     @Option(names = { "-m", "--mode" })
+ *     String mode = "standard; // Supply a default just by setting the field.
+ *
+ *     @Option(names = { "-p", "--port" })
+ *     int portNumber = 8888;
+ *
+ *     // There's no need to offer a short name for rarely-used options.
+ *     @Option(names = { "--timeout" })
+ *     double timeout = 1.0;
+ *
+ *     @Option(names = { "-o", "--output-file" })
+ *     File output;
+ *
+ *     // Multiple options are added to the collection.
+ *     // The collection field itself must be non-null.
+ *     @Option(names = { "-i", "--input-file" })
+ *     List<File> inputs = new ArrayList<File>();
+ *
+ * }
+ *
+ * class Main {
+ *     public static void main(String[] args) {
+ *         Options options = new Options();
+ *         List<String> inputFilenames = new OptionParser(options).parse(args);
+ *         for (String inputFilename : inputFilenames) {
+ *             if (!options.quiet) {
+ *                 ...
+ *             }
+ *             ...
+ *         }
+ *     }
+ * }
+ *
+ * See also:
+ *
+ *  the getopt(1) man page
+ *  Python's "optparse" module (http://docs.python.org/library/optparse.html)
+ *  the POSIX "Utility Syntax Guidelines" (http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap12.html#tag_12_02)
+ *  the GNU "Standards for Command Line Interfaces" (http://www.gnu.org/prep/standards/standards.html#Command_002dLine-Interfaces)
+ */
+public class OptionParser {
+    private static final HashMap<Class<?>, Handler> handlers = new HashMap<Class<?>, Handler>();
+    static {
+        handlers.put(boolean.class, new BooleanHandler());
+        handlers.put(Boolean.class, new BooleanHandler());
+
+        handlers.put(byte.class, new ByteHandler());
+        handlers.put(Byte.class, new ByteHandler());
+        handlers.put(short.class, new ShortHandler());
+        handlers.put(Short.class, new ShortHandler());
+        handlers.put(int.class, new IntegerHandler());
+        handlers.put(Integer.class, new IntegerHandler());
+        handlers.put(long.class, new LongHandler());
+        handlers.put(Long.class, new LongHandler());
+
+        handlers.put(float.class, new FloatHandler());
+        handlers.put(Float.class, new FloatHandler());
+        handlers.put(double.class, new DoubleHandler());
+        handlers.put(Double.class, new DoubleHandler());
+
+        handlers.put(String.class, new StringHandler());
+        handlers.put(File.class, new FileHandler());
+    }
+    Handler getHandler(Type type) {
+        if (type instanceof ParameterizedType) {
+            ParameterizedType parameterizedType = (ParameterizedType) type;
+            Class rawClass = (Class<?>) parameterizedType.getRawType();
+            if (!Collection.class.isAssignableFrom(rawClass)) {
+                throw new RuntimeException("cannot handle non-collection parameterized type " + type);
+            }
+            Type actualType = parameterizedType.getActualTypeArguments()[0];
+            if (!(actualType instanceof Class)) {
+                throw new RuntimeException("cannot handle nested parameterized type " + type);
+            }
+            return getHandler(actualType);
+        }
+        if (type instanceof Class) {
+            if (Collection.class.isAssignableFrom((Class) type)) {
+                // could handle by just having a default of treating
+                // contents as String but consciously decided this
+                // should be an error
+                throw new RuntimeException(
+                        "cannot handle non-parameterized collection " + type + ". " +
+                        "use a generic Collection to specify a desired element type");
+            }
+            return handlers.get((Class<?>) type);
+        }
+        throw new RuntimeException("cannot handle unknown field type " + type);
+    }
+
+    private final Object optionSource;
+    private final HashMap<String, Field> optionMap;
+
+    /**
+     * Constructs a new OptionParser for setting the @Option fields of 'optionSource'.
+     */
+    public OptionParser(Object optionSource) {
+        this.optionSource = optionSource;
+        this.optionMap = makeOptionMap();
+    }
+
+    /**
+     * Parses the command-line arguments 'args', setting the @Option fields of the 'optionSource' provided to the constructor.
+     * Returns a list of the positional arguments left over after processing all options.
+     */
+    public List<String> parse(String[] args) {
+        return parseOptions(Arrays.asList(args).iterator());
+    }
+
+    private List<String> parseOptions(Iterator<String> args) {
+        final List<String> leftovers = new ArrayList<String>();
+
+        // Scan 'args'.
+        while (args.hasNext()) {
+            final String arg = args.next();
+            if (arg.equals("--")) {
+                // "--" marks the end of options and the beginning of positional arguments.
+                break;
+            } else if (arg.startsWith("--")) {
+                // A long option.
+                parseLongOption(arg, args);
+            } else if (arg.startsWith("-")) {
+                // A short option.
+                parseGroupedShortOptions(arg, args);
+            } else {
+                // The first non-option marks the end of options.
+                leftovers.add(arg);
+                break;
+            }
+        }
+
+        // Package up the leftovers.
+        while (args.hasNext()) {
+            leftovers.add(args.next());
+        }
+        return leftovers;
+    }
+
+    private Field fieldForArg(String name) {
+        final Field field = optionMap.get(name);
+        if (field == null) {
+            throw new RuntimeException("unrecognized option '" + name + "'");
+        }
+        return field;
+    }
+
+    private void parseLongOption(String arg, Iterator<String> args) {
+        String name = arg.replaceFirst("^--no-", "--");
+        String value = null;
+
+        // Support "--name=value" as well as "--name value".
+        final int equalsIndex = name.indexOf('=');
+        if (equalsIndex != -1) {
+            value = name.substring(equalsIndex + 1);
+            name = name.substring(0, equalsIndex);
+        }
+
+        final Field field = fieldForArg(name);
+        final Handler handler = getHandler(field.getGenericType());
+        if (value == null) {
+            if (handler.isBoolean()) {
+                value = arg.startsWith("--no-") ? "false" : "true";
+            } else {
+                value = grabNextValue(args, name, field);
+            }
+        }
+        setValue(optionSource, field, arg, handler, value);
+    }
+
+    // Given boolean options a and b, and non-boolean option f, we want to allow:
+    // -ab
+    // -abf out.txt
+    // -abfout.txt
+    // (But not -abf=out.txt --- POSIX doesn't mention that either way, but GNU expressly forbids it.)
+    private void parseGroupedShortOptions(String arg, Iterator<String> args) {
+        for (int i = 1; i < arg.length(); ++i) {
+            final String name = "-" + arg.charAt(i);
+            final Field field = fieldForArg(name);
+            final Handler handler = getHandler(field.getGenericType());
+            String value;
+            if (handler.isBoolean()) {
+                value = "true";
+            } else {
+                // We need a value. If there's anything left, we take the rest of this "short option".
+                if (i + 1 < arg.length()) {
+                    value = arg.substring(i + 1);
+                    i = arg.length() - 1;
+                } else {
+                    value = grabNextValue(args, name, field);
+                }
+            }
+            setValue(optionSource, field, arg, handler, value);
+        }
+    }
+
+    private static void setValue(Object object, Field field, String arg, Handler handler, String valueText) {
+
+        Object value = handler.translate(valueText);
+        if (value == null) {
+            final String type = field.getType().getSimpleName().toLowerCase();
+            throw new RuntimeException("couldn't convert '" + valueText + "' to a " + type + " for option '" + arg + "'");
+        }
+        try {
+            field.setAccessible(true);
+            if (Collection.class.isAssignableFrom(field.getType())) {
+                Collection collection = (Collection)field.get(object);
+                collection.add(value);
+            } else {
+                field.set(object, value);
+            }
+        } catch (IllegalAccessException ex) {
+            throw new RuntimeException("internal error", ex);
+        }
+    }
+
+    // Returns the next element of 'args' if there is one. Uses 'name' and 'field' to construct a helpful error message.
+    private String grabNextValue(Iterator<String> args, String name, Field field) {
+        if (!args.hasNext()) {
+            final String type = field.getType().getSimpleName().toLowerCase();
+            throw new RuntimeException("option '" + name + "' requires a " + type + " argument");
+        }
+        return args.next();
+    }
+
+    // Cache the available options and report any problems with the options themselves right away.
+    private HashMap<String, Field> makeOptionMap() {
+        final HashMap<String, Field> optionMap = new HashMap<String, Field>();
+        final Class<?> optionClass = optionSource.getClass();
+        for (Field field : optionClass.getDeclaredFields()) {
+            if (field.isAnnotationPresent(Option.class)) {
+                final Option option = field.getAnnotation(Option.class);
+                final String[] names = option.names();
+                if (names.length == 0) {
+                    throw new RuntimeException("found an @Option with no name!");
+                }
+                for (String name : names) {
+                    if (optionMap.put(name, field) != null) {
+                        throw new RuntimeException("found multiple @Options sharing the name '" + name + "'");
+                    }
+                }
+                if (getHandler(field.getGenericType()) == null) {
+                    throw new RuntimeException("unsupported @Option field type '" + field.getType() + "'");
+                }
+            }
+        }
+        return optionMap;
+    }
+
+    static abstract class Handler {
+        // Only BooleanHandler should ever override this.
+        boolean isBoolean() {
+            return false;
+        }
+
+        /**
+         * Returns an object of appropriate type for the given Handle, corresponding to 'valueText'.
+         * Returns null on failure.
+         */
+        abstract Object translate(String valueText);
+    }
+
+    static class BooleanHandler extends Handler {
+        @Override boolean isBoolean() {
+            return true;
+        }
+
+        Object translate(String valueText) {
+            if (valueText.equalsIgnoreCase("true") || valueText.equalsIgnoreCase("yes")) {
+                return Boolean.TRUE;
+            } else if (valueText.equalsIgnoreCase("false") || valueText.equalsIgnoreCase("no")) {
+                return Boolean.FALSE;
+            }
+            return null;
+        }
+    }
+
+    static class ByteHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Byte.parseByte(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class ShortHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Short.parseShort(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class IntegerHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Integer.parseInt(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class LongHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Long.parseLong(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class FloatHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Float.parseFloat(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class DoubleHandler extends Handler {
+        Object translate(String valueText) {
+            try {
+                return Double.parseDouble(valueText);
+            } catch (NumberFormatException ex) {
+                return null;
+            }
+        }
+    }
+
+    static class StringHandler extends Handler {
+        Object translate(String valueText) {
+            return valueText;
+        }
+    }
+
+    static class FileHandler extends Handler {
+        Object translate(String valueText) {
+            return new File(valueText);
+        }
+    }
+}
diff --git a/libcore/tools/runner/java/dalvik/runner/Vm.java b/libcore/tools/runner/java/dalvik/runner/Vm.java
index 3d66fce..3afd7ae 100644
--- a/libcore/tools/runner/java/dalvik/runner/Vm.java
+++ b/libcore/tools/runner/java/dalvik/runner/Vm.java
@@ -51,11 +51,11 @@
 
     private final Pattern JAVA_TEST_PATTERN = Pattern.compile("\\/(\\w)+\\.java$");
     static final Classpath COMPILATION_CLASSPATH = Classpath.of(
-            new File("out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar"),
-            new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar"),
-            new File("out/target/common/obj/JAVA_LIBRARIES/jsr305_intermediates/classes.jar"),
-            new File("out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/classes.jar"),
-            new File("out/target/common/obj/JAVA_LIBRARIES/caliper_intermediates/classes.jar"));
+            new File("out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar").getAbsoluteFile(),
+            new File("out/target/common/obj/JAVA_LIBRARIES/core-tests_intermediates/classes.jar").getAbsoluteFile(),
+            new File("out/target/common/obj/JAVA_LIBRARIES/jsr305_intermediates/classes.jar").getAbsoluteFile(),
+            new File("out/target/common/obj/JAVA_LIBRARIES/guava_intermediates/classes.jar").getAbsoluteFile(),
+            new File("out/target/common/obj/JAVA_LIBRARIES/caliper_intermediates/classes.jar").getAbsoluteFile());
 
     private static final Logger logger = Logger.getLogger(Vm.class.getName());