Rewrite Support_Exec to support timeouts on waiting processes.

Also rewriting SupportExec to use ProcessBuilder rather
than Runtime.exec(). Changed callers to use the ProcessBuilder
directly rather than calling-through chained methods.
diff --git a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java
index 77fbb15..601fe42 100644
--- a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java
+++ b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/DalvikExecTest.java
@@ -21,10 +21,8 @@
 import dalvik.annotation.TestTargetClass;
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
-
 import junit.framework.TestCase;
-
-import tests.support.Support_Exec;
+import static tests.support.Support_Exec.execAndGetOutput;
 import tests.support.resource.Support_Resources;
 
 import java.io.ByteArrayOutputStream;
@@ -32,49 +30,47 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.ArrayList;
 import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
 import java.util.jar.JarOutputStream;
 import java.util.jar.Manifest;
-import java.util.jar.JarFile;
 
 
 @TestTargetClass(JarOutputStream.class)
 @AndroidOnly("dalvik vm specific")
 public class DalvikExecTest extends TestCase {
 
-    String execDalvik1 (String classpath, String mainClass, String arg1)
+    String execDalvik1(String classpath, String mainClass, String arg1)
             throws IOException, InterruptedException {
 
-        ArrayList<String> cmdLine = new ArrayList<String>(10);
+        ProcessBuilder builder = new ProcessBuilder();
 
         String base = System.getenv("OUT");
-        cmdLine.add(base + "/system/bin/dalvikvm");
+        builder.command().add(base + "/system/bin/dalvikvm");
 
-        cmdLine.add("-Djava.io.tmpdir=/tmp/mc");
-        cmdLine.add("-Duser.language=en");
-        cmdLine.add("-Duser.region=US");
+        builder.command().add("-Djava.io.tmpdir=/tmp/mc");
+        builder.command().add("-Duser.language=en");
+        builder.command().add("-Duser.region=US");
 
         if ("true".equals(System.getenv("TARGET_SIMULATOR"))) {
             // Test against SIMULATOR:
 //            cmdLine.add("-Xmx512M");
 //            cmdLine.add("-Xcheck:jni");
-            cmdLine.add("-Xbootclasspath:" + System.getProperty("java.boot.class.path"));
+            builder.command().add("-Xbootclasspath:" + System.getProperty("java.boot.class.path"));
         } else {
             // Test against EMULATOR:
         }
 
-        cmdLine.add("-classpath");
-        cmdLine.add(classpath);
-        cmdLine.add(mainClass);
+        builder.command().add("-classpath");
+        builder.command().add(classpath);
+        builder.command().add(mainClass);
 
-        if (arg1 != null) cmdLine.add(arg1);
+        if (arg1 != null) {
+            builder.command().add(arg1);
+        }
 
-        Object[] res = Support_Exec.execAndDigestOutput(
-                cmdLine.toArray(new String[cmdLine.size()]),
-                null );
-        return Support_Exec.getProcessOutput(res, true);
+        return execAndGetOutput(builder);
     }
 
     String execDalvik (String classpath, String mainClass)
diff --git a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java
index 4c209d1..01f5a8c 100644
--- a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java
+++ b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarExecTest.java
@@ -21,7 +21,8 @@
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
 import dalvik.annotation.TestTargetNew;
-import tests.support.Support_Exec;
+import static tests.support.Support_Exec.execAndGetOutput;
+import static tests.support.Support_Exec.javaProcessBuilder;
 import tests.support.resource.Support_Resources;
 
 import java.io.File;
@@ -73,15 +74,12 @@
 
         jout.close();
 
-
-        // set up the VM parameters
-        String[] args = new String[] {"-jar", outputJar.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(outputJar.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
     /**
@@ -123,13 +121,12 @@
         joutBar.write(getResource(resources, "hyts_Bar.ser"));
         joutBar.close();
 
-        String[] args = new String[] {"-jar", fooJar.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(fooJar.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
 
         // rewrite manifest so it contains not only reference to bar but useless
         // entries as well
@@ -139,10 +136,8 @@
         joutFoo.write(getResource(resources, "hyts_Foo.ser"));
         joutFoo.close();
         // execute the JAR and read the result
-        res = Support_Exec.execJava(args, null, false);
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
-
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith( "FOOBAR"));
 
         // play with relative file names - put relative path as ../<parent dir
         // name>/xx.jar
@@ -154,9 +149,8 @@
         joutFoo.write(getResource(resources, "hyts_Foo.ser"));
         joutFoo.close();
         // execute the JAR and read the result
-        res = Support_Exec.execJava(args, null, false);
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith( "FOOBAR"));
     }
 
     /**
@@ -199,13 +193,12 @@
         joutBar.write(getResource(resources, "hyts_Bar.ser"));
         joutBar.close();
 
-        String[] args = new String[] {"-jar", barJar.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(barJar.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
     @TestTargetNew(
@@ -229,15 +222,13 @@
         joutFoo.write(getResource(resources, "hyts_Bar.ser"));
         joutFoo.close();
 
-        String[] args = new String[] {"foo.bar.execjartest.Foo"};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null,
-                new String[] {"CLASSPATH=" + fooJar.getAbsolutePath()}, false);
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.environment().put("CLASSPATH", fooJar.getAbsolutePath());
+        builder.command().add("foo.bar.execjartest.Foo");
 
-        assertTrue(
-                "Error executing class from ClassPath : result returned was incorrect.",
-                res.startsWith("FOOBAR"));
+        assertTrue("Error executing class from ClassPath",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
 
         // ok - next try - add -cp to path - it should override env
         File booJar = File.createTempFile("hyts_", ".jar");
@@ -257,13 +248,14 @@
         joutBoo.write(farBody.getBytes("iso-8859-1"));
         joutBoo.close();
 
-        res = Support_Exec.execJava(args, new String[] {booJar
-                .getAbsolutePath()}, new String[] {"CLASSPATH="
-                + fooJar.getAbsolutePath()}, false);
+        builder = javaProcessBuilder();
+        builder.environment().put("CLASSPATH", fooJar.getAbsolutePath());
+        builder.command().add("-cp");
+        builder.command().add(booJar.getAbsolutePath());
+        builder.command().add("foo.bar.execjartest.Foo");
 
-        assertTrue(
-                "Error executing class specified by -cp : result returned was incorrect.",
-                res.startsWith("BOOFAR"));
+        assertTrue("Error executing class specified by -cp",
+                execAndGetOutput(builder).startsWith("BOOFAR"));
 
         // now add -jar option - it should override env and classpath
         Manifest man = new Manifest();
@@ -288,15 +280,15 @@
         joutZoo.write(zarBody.getBytes("iso-8859-1"));
         joutZoo.close();
 
-        args = new String[] {"-jar", zooJar.getAbsolutePath()};
+        builder = javaProcessBuilder();
+        builder.environment().put("CLASSPATH", fooJar.getAbsolutePath());
+        builder.command().add("-cp");
+        builder.command().add(booJar.getAbsolutePath());
+        builder.command().add("-jar");
+        builder.command().add(zooJar.getAbsolutePath());
 
-        res = Support_Exec.execJava(args, new String[] {booJar
-                .getAbsolutePath()}, new String[] {"CLASSPATH="
-                + fooJar.getAbsolutePath()}, false);
-
-        assertTrue(
-                "Error executing class specified by -cp : result returned was incorrect.",
-                res.startsWith("ZOOZAR"));
+        assertTrue("Error executing class specified by -jar",
+                execAndGetOutput(builder).startsWith("ZOOZAR"));
     }
 
     private static byte[] getResource(File tempDir, String resourceName)
diff --git a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java
index acdad71..b2ecdec 100644
--- a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java
+++ b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/JarOutputStreamTest.java
@@ -34,7 +34,6 @@
 import java.util.jar.Manifest;
 import java.util.zip.ZipEntry;
 
-import tests.support.Support_Exec;
 import tests.support.resource.Support_Resources;
 
 @TestTargetClass(JarOutputStream.class)
diff --git a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java
index c6f07de..013974c 100644
--- a/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java
+++ b/libcore/archive/src/test/java/org/apache/harmony/archive/tests/java/util/jar/ZipExecTest.java
@@ -21,7 +21,8 @@
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
 import dalvik.annotation.TestTargetNew;
-import tests.support.Support_Exec;
+import static tests.support.Support_Exec.javaProcessBuilder;
+import static tests.support.Support_Exec.execAndGetOutput;
 import tests.support.resource.Support_Resources;
 
 import java.io.File;
@@ -72,15 +73,12 @@
         man.write(zout);
         zout.close();
 
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(outputZip.getAbsolutePath());
 
-        // set up the VM parameters
-        String[] args = new String[] {"-jar", outputZip.getAbsolutePath()};
-
-        // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing ZIP : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        assertTrue("Error executing ZIP",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
     /**
@@ -124,13 +122,12 @@
         zoutBar.write(getResource(resources, "hyts_Bar.ser"));
         zoutBar.close();
 
-        String[] args = new String[] {"-jar", fooZip.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(fooZip.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith( "FOOBAR"));
 
         // rewrite manifest so it contains not only reference to bar but useless
         // entries as well
@@ -142,9 +139,8 @@
         zoutFoo.write(getResource(resources, "hyts_Foo.ser"));
         zoutFoo.close();
         // execute the JAR and read the result
-        res = Support_Exec.execJava(args, null, false);
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
 
 
         // play with relative file names - put relative path as ../<parent dir
@@ -159,9 +155,8 @@
         zoutFoo.write(getResource(resources, "hyts_Foo.ser"));
         zoutFoo.close();
         // execute the ZIP and read the result
-        res = Support_Exec.execJava(args, null, false);
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
 
@@ -199,13 +194,11 @@
         zoutBar.write(getResource(resources, "hyts_Bar.ser"));
         zoutBar.close();
 
-        String[] args = new String[] {"-jar", fooJar.getAbsolutePath()};
-
-        // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(fooJar.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
     @TestTargetNew(
@@ -244,13 +237,12 @@
         joutBar.write(getResource(resources, "hyts_Bar.ser"));
         joutBar.close();
 
-        String[] args = new String[] {"-jar", fooZip.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing ZIP : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(fooZip.getAbsolutePath());
+        assertTrue("Error executing ZIP", 
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
     /**
@@ -296,13 +288,12 @@
         zoutBar.write(getResource(resources, "hyts_Bar.ser"));
         zoutBar.close();
 
-        String[] args = new String[] {"-jar", barZip.getAbsolutePath()};
-
         // execute the JAR and read the result
-        String res = Support_Exec.execJava(args, null, false);
-
-        assertTrue("Error executing JAR : result returned was incorrect.", res
-                .startsWith("FOOBAR"));
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-jar");
+        builder.command().add(barZip.getAbsolutePath());
+        assertTrue("Error executing JAR",
+                execAndGetOutput(builder).startsWith("FOOBAR"));
     }
 
 
diff --git a/libcore/luni/src/test/java/tests/api/java/io/FileTest.java b/libcore/luni/src/test/java/tests/api/java/io/FileTest.java
index 5c0cb2a..3063b89 100644
--- a/libcore/luni/src/test/java/tests/api/java/io/FileTest.java
+++ b/libcore/luni/src/test/java/tests/api/java/io/FileTest.java
@@ -31,13 +31,14 @@
 import java.net.URISyntaxException;
 import java.net.URL;
 
-import tests.support.Support_Exec;
 import dalvik.annotation.AndroidOnly;
 import dalvik.annotation.KnownFailure;
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetClass;
 import dalvik.annotation.TestTargetNew;
 import dalvik.annotation.TestTargets;
+import static tests.support.Support_Exec.javaProcessBuilder;
+import static tests.support.Support_Exec.execAndGetOutput;
 
 @TestTargetClass(File.class) 
 public class FileTest extends junit.framework.TestCase {
@@ -2465,12 +2466,14 @@
         assertTrue("could not find the path of the test jar/apk", idx > 0);
         classPath = classPath.substring(9, idx); // cutting off jar:file:
 
-        Support_Exec.execJava(new String[] {
-                "tests.support.Support_DeleteOnExitTest",
-                dir.getAbsolutePath(), subDir.getAbsolutePath() },
-                new String[] { System.getProperty("java.class.path"),
-                classPath }, false);
-        Thread.sleep(2000);
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-cp");
+        builder.command().add(System.getProperty("java.class.path"));
+        builder.command().add("tests.support.Support_DeleteOnExitTest");
+        builder.command().add(dir.getAbsolutePath());
+        builder.command().add(subDir.getAbsolutePath());
+        execAndGetOutput(builder);
+
         assertFalse(dir.exists());
         assertFalse(subDir.exists());
     }
diff --git a/libcore/luni/src/test/java/tests/api/java/lang/Process2Test.java b/libcore/luni/src/test/java/tests/api/java/lang/Process2Test.java
index 51b29d8..2e60e77 100644
--- a/libcore/luni/src/test/java/tests/api/java/lang/Process2Test.java
+++ b/libcore/luni/src/test/java/tests/api/java/lang/Process2Test.java
@@ -30,6 +30,7 @@
 import java.io.OutputStream;
 
 import tests.support.Support_Exec;
+import static tests.support.Support_Exec.javaProcessBuilder;
 
 @TestTargetClass(Process.class) 
 public class Process2Test extends junit.framework.TestCase {
@@ -60,23 +61,12 @@
         )
     })
     @AndroidOnly("dalvikvm specific")
-    public void test_isBufferedStreams() {
-        // Regression test for HARMONY-2735.
-        try {
-            Object[] execArgs = Support_Exec.execJava2(new String[0], null, true);
-            Process p = (Process) execArgs[0];
-            InputStream in = p.getInputStream();
-                  assertNotNull(in);
-                  in = p.getErrorStream();
-                  assertNotNull(in);
-                  OutputStream out = p.getOutputStream();
-                  assertNotNull(out);
-                  in.close();
-                  out.close();
-                  p.destroy();
-        } catch (Exception ex) {
-            fail("Unexpected exception got: " + ex);
-        }
+    public void test_streams()
+            throws IOException, InterruptedException {
+        Process p = javaProcessBuilder().start();
+        assertNotNull(p.getInputStream());
+        assertNotNull(p.getErrorStream());
+        assertNotNull(p.getOutputStream());
     }
     
     @TestTargetNew(
diff --git a/libcore/security/src/test/java/tests/api/java/security/PermissionCollectionTest.java b/libcore/security/src/test/java/tests/api/java/security/PermissionCollectionTest.java
index f36f119..35e3749 100644
--- a/libcore/security/src/test/java/tests/api/java/security/PermissionCollectionTest.java
+++ b/libcore/security/src/test/java/tests/api/java/security/PermissionCollectionTest.java
@@ -28,6 +28,8 @@
 import java.util.StringTokenizer;
 
 import tests.support.Support_Exec;
+import static tests.support.Support_Exec.javaProcessBuilder;
+import static tests.support.Support_Exec.execAndGetOutput;
 import tests.support.Support_GetLocal;
 import tests.support.resource.Support_Resources;
 import dalvik.annotation.KnownFailure;
@@ -168,17 +170,14 @@
             }
         }
 
-        String classPath = new File(classURL.getFile()).getPath();
-
-        // Execute Support_PermissionCollection in another VM
-        String[] classPathArray = new String[2];
-        classPathArray[0] = classPath;
-        classPathArray[1] = jarFile.getPath();
-        String[] args = { "-Djava.security.policy=" + policyFile.toURL(),
-                "tests.support.Support_PermissionCollection",
-                signedBKS.toExternalForm() };
-
-        String result = Support_Exec.execJava(args, classPathArray, true);
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("-cp");
+        builder.command().add(Support_Exec.createPath(
+                new File(classURL.getFile()).getPath(), jarFile.getPath()));
+        builder.command().add("-Djava.security.policy=" + policyFile.toURL());
+        builder.command().add("tests.support.Support_PermissionCollection");
+        builder.command().add(signedBKS.toExternalForm());
+        String result = execAndGetOutput(builder);
 
         StringTokenizer resultTokenizer = new StringTokenizer(result, ",");
 
diff --git a/libcore/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java b/libcore/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
index 68ac6c5..ebb055c 100644
--- a/libcore/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
+++ b/libcore/sql/src/test/java/org/apache/harmony/sql/tests/java/sql/DriverManagerTest.java
@@ -19,12 +19,10 @@
 
 import dalvik.annotation.KnownFailure;
 import dalvik.annotation.TestTargetClass;
-import dalvik.annotation.TestTargets;
 import dalvik.annotation.TestLevel;
 import dalvik.annotation.TestTargetNew;
 
 import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.io.PrintStream;
 import java.io.PrintWriter;
 import java.lang.reflect.Method;
@@ -37,9 +35,11 @@
 import java.sql.SQLPermission;
 import java.util.Enumeration;
 import java.util.Properties;
-import tests.support.Support_Exec;
 
 import junit.framework.TestCase;
+import static tests.support.Support_Exec.javaProcessBuilder;
+import static tests.support.Support_Exec.execAndGetOutput;
+
 @TestTargetClass(DriverManager.class)
 /**
  * JUnit Testcase for the java.sql.DriverManager class
@@ -715,10 +715,9 @@
      * Regression for HARMONY-4303
      */
     public void test_initClass() throws Exception {
-        String[] arg = new String[1];
-        arg[0] = "org/apache/harmony/sql/tests/java/sql/TestMainForDriver";
-        String result = Support_Exec.execJava(arg, null, true);
-        assertEquals("", result);
+        ProcessBuilder builder = javaProcessBuilder();
+        builder.command().add("org/apache/harmony/sql/tests/java/sql/TestMainForDriver");
+        assertEquals("", execAndGetOutput(builder));
     }
 
     private static class BadDummyDriver extends DummyDriver {
diff --git a/libcore/support/src/test/java/tests/support/Support_Exec.java b/libcore/support/src/test/java/tests/support/Support_Exec.java
index c3a5ccc..d058927 100644
--- a/libcore/support/src/test/java/tests/support/Support_Exec.java
+++ b/libcore/support/src/test/java/tests/support/Support_Exec.java
@@ -17,204 +17,112 @@
 
 package tests.support;
 
-import java.io.ByteArrayOutputStream;
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.PrintStream;
-import java.util.ArrayList;
-import java.util.StringTokenizer;
-
-import junit.framework.TestCase;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 public class Support_Exec extends TestCase {
 
-    static final boolean againstDalvik;
-    static {
-        String platform = System.getProperty("java.vendor");
-        againstDalvik = (platform.contains("Android"));
-    }
+    private static final boolean againstDalvik
+            = System.getProperty("java.vendor").contains("Android");
 
     /**
-     *  This function returns the output of the process as a string
+     * Returns a builder configured with the appropriate VM ("dalvikvm" or
+     * "java") and arguments (as specified by the system property
+     * {@code hy.test.vmargs}).
      */
-    public static String execJava(String[] args, String[] classpath,
-            boolean displayOutput) throws IOException, InterruptedException {
-        Object[] arr =
-                execJavaCommon(args, classpath, null, displayOutput, true);
-
-        return getProcessOutput(arr, displayOutput);
-    }
-
-    /**
-     * This function returns the output of the process as a string
-     */
-    public static String execJava(String[] args, String[] classpath, String[] envp,
-            boolean displayOutput) throws IOException, InterruptedException {
-        Object[] arr =
-                execJavaCommon(args, classpath, envp, displayOutput, false);
-
-        return getProcessOutput(arr, displayOutput);
-    }
-
-    public static String getProcessOutput(Object[] arr, boolean displayOutput)
+    public static ProcessBuilder javaProcessBuilder()
             throws IOException, InterruptedException {
-        Process proc = (Process) arr[0];
-        StringBuilder output = new StringBuilder();
-        InputStream in = proc.getInputStream();
-        int result;
-        byte[] bytes = new byte[1024];
-
-        while ((result = in.read(bytes)) != -1) {
-            output.append(new String(bytes, 0, result));
-
-            if (displayOutput) {
-                System.out.write(bytes, 0, result);
-            }
-        }
-
-        in.close();
-        proc.waitFor();
-        checkStderr(arr);
-        proc.destroy();
-
-        return output.toString();
-    }
-
-    public static void checkStderr(Object[] execArgs) {
-        StringBuilder errBuf = (StringBuilder) execArgs[1];
-
-        synchronized (errBuf) {
-            if (errBuf.length() > 0) {
-                fail(errBuf.toString());
-            }
-        }
-    }
-
-    public static Object[] execJava2(String[] args, String[] classpath,
-            boolean displayOutput) throws IOException, InterruptedException {
-        return execJavaCommon(args, classpath, null, displayOutput, true);
-    }
-
-    private static Object[] execJavaCommon(String[] args, String[] classpath,
-            String[] envp, boolean displayOutput, boolean appendToSystemClassPath)
-            throws IOException, InterruptedException {
-        // this function returns the resulting process from the exec
-        ArrayList<String> execArgs = null;
-        StringBuilder classPathString = new StringBuilder();
-        StringBuilder command;
-        String executable;
-        String testVMArgs;
-        StringTokenizer st;
-
-        execArgs = new ArrayList<String>(3 + args.length);
+        ProcessBuilder builder = new ProcessBuilder();
 
         // construct the name of executable file
-        if (againstDalvik) {
-            execArgs.add("dalvikvm");
-        } else {
-            execArgs.add("java");
-        }
-
-        // add classpath string
-        if (classpath != null) {
-            for (String element : classpath) {
-                classPathString.append(File.pathSeparator);
-                classPathString.append(element);
-            }
-        }
-        if (appendToSystemClassPath) {
-            execArgs.add("-cp");
-            execArgs.add(System.getProperty("java.class.path") +
-                    classPathString);
-        } else {
-            if (classpath != null) {
-                execArgs.add("-cp");
-                execArgs.add(classPathString.toString());
-            }
-        }
+        builder.command().add(againstDalvik ? "dalvikvm" : "java");
 
         // parse hy.test.vmargs if was given
-        testVMArgs = System.getProperty("hy.test.vmargs");
+        String testVMArgs = System.getProperty("hy.test.vmargs");
         if (testVMArgs != null) {
-            st = new StringTokenizer(testVMArgs, " ");
-
-            while (st.hasMoreTokens()) {
-                execArgs.add(st.nextToken());
-            }
+            builder.command().addAll(Arrays.asList(testVMArgs.split("\\s+")));
         }
 
-        // add custom args given as parameter
-        for (String arg : args) {
-            execArgs.add(arg);
-        }
-
-        if (displayOutput) {
-            // Construct command line string and print it to stdout.
-            command = new StringBuilder(execArgs.get(0));
-            for (int i = 1; i < execArgs.size(); i++) {
-                command.append(" ");
-                command.append(execArgs.get(i));
-            }
-        }
-
-        // execute java process
-        return execAndDigestOutput(execArgs.toArray(new String[execArgs.size()]), envp);
+        return builder;
     }
 
-    //
-    // mc: This looks like functionaly worth publicity:
-    //
-    public static Object[] execAndDigestOutput (String[] cmdLine, String[] envp)
-            throws IOException, InterruptedException {
+    /**
+     * Returns a command-line ready path formed by joining the path elements
+     * with the system path separator as a separator.
+     */
+    public static String createPath(String... elements) {
+        StringBuilder result = new StringBuilder();
+        for (String element : elements) {
+            result.append(File.pathSeparator);
+            result.append(element);
+        }
+        return result.toString();
+    }
 
-//        System.out.println("Commandline BEGIN");
-//        for (int i = 0; i < cmdLine.length; i++) {
-//            System.out.println(cmdLine[i]);
-//        }
-//        System.out.println("END");
-
-        final Process proc = Runtime.getRuntime().exec(cmdLine, envp);
-        final StringBuilder errBuf = new StringBuilder();
-
-        Thread errThread = new Thread(new Runnable() {
-            public void run() {
-                synchronized (errBuf) {
-                    InputStream err;
-                    int result;
-                    byte[] bytes = new byte[1024];
-
-                    synchronized (proc) {
-                        proc.notifyAll();
-                    }
-
-                    err = proc.getErrorStream();
-                    try {
-                        while ((result = err.read(bytes)) != -1) {
-                            System.err.write(bytes, 0, result);
-                            errBuf.append(new String(bytes));
-                        }
-                        err.close();
-                    } catch (IOException e) {
-                        ByteArrayOutputStream out = new ByteArrayOutputStream();
-                        PrintStream printer = new PrintStream(out);
-
-                        e.printStackTrace();
-                        e.printStackTrace(printer);
-                        printer.close();
-                        errBuf.append(new String(out.toByteArray()));
-                    }
+    /**
+     * Starts the specified process, collects its output from standard out, and
+     * returns. If the stream emits anything to standard err, an
+     * AssertionFailedError will be thrown.
+     *
+     * <p>This method assumes the target process will complete within ten
+     * seconds. If it does not, an AssertionFailedError will be thrown.
+     */
+    public static String execAndGetOutput(ProcessBuilder builder)
+            throws IOException {
+        final Process process = builder.start();
+        ExecutorService executorService = Executors.newFixedThreadPool(1);
+        try {
+            Future<Throwable> future = executorService.submit(new Callable<Throwable>() {
+                public Throwable call() throws Exception {
+                    String err = streamToString(process.getErrorStream());
+                    return err.length() > 0
+                            ? new AssertionFailedError("Unexpected err stream data:\n" + err)
+                            : null;
                 }
+            });
+
+            String out = streamToString(process.getInputStream());
+
+            Throwable failure;
+            try {
+                failure = future.get(10, TimeUnit.SECONDS);
+            } catch (Exception e) {
+                failure = e;
             }
-        });
 
-        synchronized (proc) {
-            errThread.start();
-            // wait for errThread to start
-            proc.wait();
+            if (failure != null) {
+                AssertionFailedError error = new AssertionFailedError(
+                        "Failed to execute " + builder.command() + "; output was:\n" + out);
+                error.initCause(failure);
+                throw error;
+            } else {
+                return out;
+            }
+        } finally {
+            executorService.shutdown();
         }
-
-        return new Object[] { proc, errBuf };
     }
 
+    private static String streamToString(InputStream in) throws IOException {
+        StringWriter writer = new StringWriter();
+        Reader reader = new InputStreamReader(in);
+        int c;
+        while ((c = reader.read()) != -1) {
+            writer.write(c);
+        }
+        return writer.toString();
+    }
 }