Clover coverage report - PLT Utilities Test Coverage (plt-20120304-r5436)
Coverage timestamp: Sat Mar 3 2012 22:01:56 CST
file stats: LOC: 407   Methods: 40
NCLOC: 229   Classes: 1
 
 Source file Conditionals Statements Methods TOTAL
JVMBuilder.java 28.9% 51.9% 37.5% 44%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK*
 2   
 3    PLT Utilities BSD License
 4   
 5    Copyright (c) 2007-2010 JavaPLT group at Rice University
 6    All rights reserved.
 7   
 8    Developed by: Java Programming Languages Team
 9    Rice University
 10    http://www.cs.rice.edu/~javaplt/
 11   
 12    Redistribution and use in source and binary forms, with or without modification, are permitted
 13    provided that the following conditions are met:
 14   
 15    - Redistributions of source code must retain the above copyright notice, this list of conditions
 16    and the following disclaimer.
 17    - Redistributions in binary form must reproduce the above copyright notice, this list of
 18    conditions and the following disclaimer in the documentation and/or other materials provided
 19    with the distribution.
 20    - Neither the name of the JavaPLT group, Rice University, nor the names of the library's
 21    contributors may be used to endorse or promote products derived from this software without
 22    specific prior written permission.
 23   
 24    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
 25    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 26    FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND
 27    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 28    DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 29    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 30    IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 31    OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 32   
 33    *END_COPYRIGHT_BLOCK*/
 34   
 35    package edu.rice.cs.plt.concurrent;
 36   
 37    import java.io.File;
 38    import java.io.IOException;
 39    import java.util.Collections;
 40    import java.util.Enumeration;
 41    import java.util.HashMap;
 42    import java.util.LinkedList;
 43    import java.util.List;
 44    import java.util.Map;
 45    import java.util.Properties;
 46   
 47    import edu.rice.cs.plt.collect.CollectUtil;
 48    import edu.rice.cs.plt.io.IOUtil;
 49    import edu.rice.cs.plt.iter.IterUtil;
 50    import edu.rice.cs.plt.iter.SizedIterable;
 51    import edu.rice.cs.plt.lambda.Lambda2;
 52    import edu.rice.cs.plt.lambda.WrappedException;
 53    import edu.rice.cs.plt.reflect.ReflectUtil;
 54    import edu.rice.cs.plt.text.TextUtil;
 55   
 56    import static edu.rice.cs.plt.io.IOUtil.attemptAbsoluteFiles;
 57    import static edu.rice.cs.plt.iter.IterUtil.snapshot;
 58    import static edu.rice.cs.plt.collect.CollectUtil.snapshot;
 59    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 60   
 61    /**
 62    * Creates Java subprocesses via an interface similar to that of {@link ProcessBuilder}. Each JVMBuilder
 63    * object is immutable, but can be used as a template for creating other JVMBuilder with altered parameters.
 64    * (Unlike ProcessBuilder, there is no implicit mutation via mutable maps returned by getters.) The following
 65    * parameters are supported:<ul>
 66    * <li>{@code javaCommand}: Command, file, or directory used to invoke the JVM. If a directory is given,
 67    * the relative directories {@code bin} and {@code ../bin} are also searched for a {@code java} executable.
 68    * Default: the {@code java.home} property.</li>
 69    * <li>{@code jvmArgs}: Arguments to pass to the {@code java} executable. These are passed <em>before</em> all
 70    * standard arguments supported here (e.g. {@code "-classpath"}). Default: empty.</li>
 71    * <li>{@code classPath}: Class path to use in the JVM. Default: {@code ReflectUtil.SYSTEM_CLASS_PATH}.</li>
 72    * <li>{@code dir}: Working directory of the new process. Default: {@code System.getProperty("user.dir", "")}.</li>
 73    * <li>{@code properties}: Java properties to define in the new JVM (passed to the {@code java} executable with
 74    * arguments of the form {@code "-D<key>=<value>"}); non-string entries are converted to strings via
 75    * {@code toString}. Default: empty.</li>
 76    * <li>{@code environment}: System environment variables to define in the new JVM, or {@code null} signifying that
 77    * the current environment should be duplicated. Default: {@code null}.
 78    * </ul>
 79    */
 80    public class JVMBuilder implements Lambda2<String, Iterable<? extends String>, Process> {
 81   
 82    private static final String DEFAULT_JAVA_COMMAND = findJavaCommand(System.getProperty("java.home", ""));
 83    private static final SizedIterable<String> DEFAULT_JVM_ARGS = IterUtil.empty();
 84    private static final SizedIterable<File> DEFAULT_CLASS_PATH = attemptAbsoluteFiles(ReflectUtil.SYSTEM_CLASS_PATH);
 85    private static final File DEFAULT_DIR = IOUtil.WORKING_DIRECTORY;
 86    private static final Map<String, String> DEFAULT_PROPERTIES = Collections.emptyMap();
 87    private static final Map<String, String> DEFAULT_ENVIRONMENT = null;
 88   
 89    public static final JVMBuilder DEFAULT = new JVMBuilder();
 90   
 91    private final String _javaCommand;
 92    private final SizedIterable<String> _jvmArgs;
 93    private final SizedIterable<File> _classPath;
 94    private final File _dir;
 95    private final Map<String, String> _properties;
 96    private final Map<String, String> _environment;
 97   
 98  3 private JVMBuilder() {
 99  3 this(DEFAULT_JAVA_COMMAND, DEFAULT_JVM_ARGS, DEFAULT_CLASS_PATH, DEFAULT_DIR, DEFAULT_PROPERTIES,
 100    DEFAULT_ENVIRONMENT, true);
 101    }
 102   
 103  0 public JVMBuilder(String javaCommand) {
 104  0 this(findJavaCommand(javaCommand), DEFAULT_JVM_ARGS, DEFAULT_CLASS_PATH, DEFAULT_DIR,
 105    DEFAULT_PROPERTIES, DEFAULT_ENVIRONMENT, true);
 106    }
 107   
 108  0 public JVMBuilder(String javaCommand, Iterable<? extends String> jvmArgs) {
 109  0 this(findJavaCommand(javaCommand), snapshot(jvmArgs), DEFAULT_CLASS_PATH, DEFAULT_DIR,
 110    DEFAULT_PROPERTIES, DEFAULT_ENVIRONMENT, true);
 111    }
 112   
 113  1 public JVMBuilder(Iterable<? extends File> classPath) {
 114  1 this(DEFAULT_JAVA_COMMAND, DEFAULT_JVM_ARGS, attemptAbsoluteFiles(classPath), DEFAULT_DIR,
 115    DEFAULT_PROPERTIES, DEFAULT_ENVIRONMENT, true);
 116    }
 117   
 118  1 public JVMBuilder(File dir) {
 119  1 this(DEFAULT_JAVA_COMMAND, DEFAULT_JVM_ARGS, DEFAULT_CLASS_PATH, dir, DEFAULT_PROPERTIES,
 120    DEFAULT_ENVIRONMENT, true);
 121    }
 122   
 123  0 public JVMBuilder(String javaCommand, Iterable<? extends String> jvmArgs, Iterable<? extends File> classPath,
 124    File dir, Map<? extends String, ? extends String> properties,
 125    Map<? extends String, ? extends String> environment) {
 126  0 this(findJavaCommand(javaCommand), snapshot(jvmArgs), attemptAbsoluteFiles(classPath), dir,
 127  0 snapshot(properties), (environment == null) ? null : snapshot(environment), true);
 128    }
 129   
 130    /**
 131    * Private constructor. Preprocessing of arguments has already happened; no arguments will be externally
 132    * mutated. dummy parameter is to distinguish this constructor from other overloads.
 133    */
 134  17 private JVMBuilder(String javaCommand, SizedIterable<String> jvmArgs, SizedIterable<File> classPath,
 135    File dir, Map<String, String> properties, Map<String, String> environment, boolean dummy) {
 136  17 _javaCommand = javaCommand;
 137  17 _jvmArgs = jvmArgs;
 138  17 _classPath = classPath;
 139  17 _dir = dir;
 140  17 _properties = properties;
 141  17 _environment = environment;
 142    }
 143   
 144  0 public String javaCommand() { return _javaCommand; }
 145   
 146  0 public JVMBuilder javaCommand(String javaCommand) {
 147  0 return new JVMBuilder(findJavaCommand(javaCommand), _jvmArgs, _classPath, _dir, _properties, _environment, true);
 148    }
 149   
 150  0 public JVMBuilder javaCommand(File javaCommand) {
 151  0 return new JVMBuilder(findJavaCommand(javaCommand), _jvmArgs, _classPath, _dir, _properties, _environment, true);
 152    }
 153   
 154  0 public SizedIterable<String> jvmArguments() { return _jvmArgs; }
 155   
 156  0 public JVMBuilder jvmArguments(Iterable<? extends String> jvmArgs) {
 157  0 return new JVMBuilder(_javaCommand, IterUtil.snapshot(jvmArgs), _classPath, _dir, _properties, _environment, true);
 158    }
 159   
 160    /** Due to overloading rules, this cannot be invoked via varargs with 0 arguments (the getter matches instead). */
 161  0 public JVMBuilder jvmArguments(String... jvmArgs) {
 162  0 return new JVMBuilder(_javaCommand, IterUtil.make(jvmArgs), _classPath, _dir, _properties, _environment, true);
 163    }
 164   
 165  0 public SizedIterable<File> classPath() { return _classPath; }
 166   
 167  0 public JVMBuilder classPath(Iterable<? extends File> classPath) {
 168  0 return new JVMBuilder(_javaCommand, _jvmArgs, attemptAbsoluteFiles(classPath), _dir,
 169    _properties, _environment, true);
 170    }
 171   
 172  0 public JVMBuilder classPath(String classPath) {
 173  0 return new JVMBuilder(_javaCommand, _jvmArgs, attemptAbsoluteFiles(IOUtil.parsePath(classPath)), _dir,
 174    _properties, _environment, true);
 175    }
 176   
 177    /** Due to overloading rules, this cannot be invoked via varargs with 0 arguments (the getter matches instead). */
 178  0 public JVMBuilder classPath(File... classPath) {
 179  0 return new JVMBuilder(_javaCommand, _jvmArgs, attemptAbsoluteFiles(IterUtil.asIterable(classPath)), _dir,
 180    _properties, _environment, true);
 181    }
 182   
 183  0 public File directory() { return _dir; }
 184   
 185  0 public JVMBuilder directory(File dir) {
 186  0 return new JVMBuilder(_javaCommand, _jvmArgs, _classPath, dir, _properties, _environment, true);
 187    }
 188   
 189  0 public JVMBuilder directory(String dir) {
 190  0 return new JVMBuilder(_javaCommand, _jvmArgs, _classPath, new File(dir), _properties, _environment, true);
 191    }
 192   
 193    /** Get an immutable view of the properties. */
 194  0 public Map<String, String> properties() { return CollectUtil.immutable(_properties); }
 195   
 196    /**
 197    * Return a mutable copy of the properties. Changes to the result do not affect this JVMBuilder,
 198    * but a modified map can be used to create another JVMBuilder.
 199    */
 200  12 public Map<String, String> propertiesCopy() { return snapshot(_properties); }
 201   
 202    /**
 203    * Produce a JVMBuilder setting the JVM properties to the given mapping (including keys that appear
 204    * only as defaults in the Properties object).
 205    */
 206  0 public JVMBuilder properties(Properties ps) {
 207  0 return new JVMBuilder(_javaCommand, _jvmArgs, _classPath, _dir, copyProps(ps), _environment, true);
 208    }
 209   
 210    /** Produce a JVMBuilder setting the JVM properties to the given mapping. */
 211  12 public JVMBuilder properties(Map<? extends String, ? extends String> ps) {
 212  12 return new JVMBuilder(_javaCommand, _jvmArgs, _classPath, _dir, snapshot(ps), _environment, true);
 213    }
 214   
 215    /** Produce a JVMBuilder including the given JVM property mapping, in addition to those currently set. */
 216  4 public JVMBuilder addProperty(String key, String value) {
 217  4 Map<String, String> newProps = propertiesCopy();
 218  4 newProps.put(key, value);
 219  4 return properties(newProps);
 220    }
 221   
 222    /**
 223    * Produce a JVMBuilder where, for each key in the given Properties object (including those designated in
 224    * the Properties object as defaults), if the property is undefined in this JVMBuilder, it will map to
 225    * the given value. All current property mappings remain identical in the result.
 226    */
 227  8 public JVMBuilder addDefaultProperties(Properties ps) { return addDefaultProperties(copyProps(ps)); }
 228   
 229    /**
 230    * Produce a JVMBuilder where, for each key in the given Map, if the property is undefined in this
 231    * JVMBuilder, it will map to the given value. All current property mappings remain identical in the result.
 232    */
 233  8 public JVMBuilder addDefaultProperties(Map<? extends String, ? extends String> ps) {
 234  0 if (_properties.keySet().containsAll(ps.keySet())) { return this; }
 235    else {
 236  8 Map<String, String> newProps = propertiesCopy();
 237  8 for (Map.Entry<? extends String, ? extends String> entry : ps.entrySet()) {
 238  16 if (!newProps.containsKey(entry.getKey())) { newProps.put(entry.getKey(), entry.getValue()); }
 239    }
 240  8 return properties(newProps);
 241    }
 242    }
 243   
 244    /**
 245    * If the current JVM properties do not contain {@code key}, produce a new JVMBuilder including the
 246    * mapping, in addition to those currently set. Otherwise, return {@code this}.
 247    */
 248  4 public JVMBuilder addDefaultProperty(String key, String value) {
 249  4 return _properties.containsKey(key) ? this : addProperty(key, value);
 250    }
 251   
 252    /** Get an immutable view of the environment, or {@code null} if the current process's environment is used. */
 253  0 public Map<String, String> environment() {
 254  0 return (_environment == null) ? null : CollectUtil.immutable(_environment);
 255    }
 256   
 257    /**
 258    * Get a mutable copy of the environment. If the environment is {@code null} (meaning the the current
 259    * process's environment should be used), the result is a copy of {@link System#getenv()}. Changes
 260    * to the result do not affect this JVMBuilder, but a modified environment map can be used to create
 261    * another JVMBuilder.
 262    */
 263  0 public Map<String, String> environmentCopy() {
 264  0 return snapshot((_environment == null) ? System.getenv() : _environment);
 265    }
 266   
 267    /**
 268    * Produce a JVMBuilder setting the environment to the given mapping (may be {@code null}, meaning
 269    * the current process's environment will be duplicated.
 270    */
 271  0 public JVMBuilder environment(Map<? extends String, ? extends String> env) {
 272  0 return new JVMBuilder(_javaCommand, _jvmArgs, _classPath, _dir, _properties,
 273  0 (_environment == null) ? null : snapshot(env), true);
 274    }
 275   
 276    /** Produce a JVMBuilder including the given environment mapping, in addition to those currently set. */
 277  0 public JVMBuilder addEnvironmentVar(String key, String value) {
 278  0 Map<String, String> newEnv = environmentCopy();
 279  0 newEnv.put(key, value);
 280  0 return environment(newEnv);
 281    }
 282   
 283    /**
 284    * Produce a JVMBuilder where, for each key in the given Map, if the environment variable is undefined in this
 285    * JVMBuilder, it will map to the given value. All current environment mappings remain identical in the result.
 286    */
 287  0 public JVMBuilder addDefaultEnvironmentVars(Map<? extends String, ? extends String> env) {
 288  0 if (_environment != null && _environment.keySet().containsAll(env.keySet())) { return this; }
 289    else {
 290  0 Map<String, String> newEnv = environmentCopy();
 291  0 for (Map.Entry<? extends String, ? extends String> entry : env.entrySet()) {
 292  0 if (!newEnv.containsKey(entry.getKey())) { newEnv.put(entry.getKey(), entry.getValue()); }
 293    }
 294  0 return environment(newEnv);
 295    }
 296    }
 297   
 298    /**
 299    * If the current environment mappings do not contain {@code key}, produce a new JVMBuilder including the
 300    * mapping, in addition to those currently set. Otherwise, return {@code this}.
 301    */
 302  0 public JVMBuilder AddDefaultEnvironmentVar(String key, String value) {
 303  0 if (_environment != null && _environment.containsKey(key)) { return this; }
 304  0 else { return addEnvironmentVar(key, value); }
 305    }
 306   
 307   
 308    /**
 309    * Start a JVM process via {@link Runtime#exec(String[], String[], File)}. Varargs shortcut for
 310    * {@link #start(String, Iterable)}.
 311    */
 312  3 public Process start(String mainClass, String... mainParams) throws IOException {
 313  3 return start(mainClass, IterUtil.asIterable(mainParams));
 314    }
 315   
 316    /**
 317    * Start a JVM process via {@link Runtime#exec(String[], String[], File)}. The array of command strings
 318    * contains, in order: the java command, the JVM args, {@code "-classpath"} followed by the class path, each
 319    * property using {@code "-D<key>=<value>"} notation, the name of the given main class, and the main
 320    * parameters.
 321    * @throws IOException Per {@link Runtime#exec(String[], String[], File)}.
 322    * @throws SecurityException Per {@link Runtime#exec(String[], String[], File)}.
 323    */
 324  11 public Process start(String mainClass, Iterable<? extends String> mainParams) throws IOException {
 325  11 List<String> commandL = new LinkedList<String>();
 326  11 commandL.add(_javaCommand);
 327  11 CollectUtil.addAll(commandL, _jvmArgs);
 328  11 commandL.add("-classpath");
 329  11 commandL.add(IOUtil.pathToString(_classPath));
 330  11 for (Map.Entry<String, String> prop : _properties.entrySet()) {
 331  20 commandL.add("-D" + prop.getKey() + "=" + prop.getValue());
 332    }
 333  11 commandL.add(mainClass);
 334  11 CollectUtil.addAll(commandL, mainParams);
 335  11 String[] command = IterUtil.toArray(commandL, String.class);
 336   
 337  11 String[] env;
 338  11 if (_environment == null) { env = null; }
 339    else {
 340  0 List<String> envL = new LinkedList<String>();
 341  0 for (Map.Entry<String, String> binding : _environment.entrySet()) {
 342  0 envL.add(binding.getKey() + "=" + binding.getValue());
 343    }
 344  0 env = IterUtil.toArray(envL, String.class);
 345    }
 346   
 347    // IMPORTANT: Do not leave this logging message uncommented, or setting debug to an RMILogSink won't work
 348    //debug.logValues("Starting JVM", new String[]{"command", "env", "dir"}, command, env, _dir);
 349  11 return Runtime.getRuntime().exec(command, env, _dir);
 350    }
 351   
 352  0 public Process value(String mainClass, Iterable<? extends String> mainParams) {
 353  0 try { return start(mainClass, mainParams); }
 354  0 catch (IOException e) { throw new WrappedException(e); }
 355    }
 356   
 357  3 private static String findJavaCommand(String command) {
 358  3 return findJavaCommand(new File(command));
 359    }
 360   
 361    /** Find the java executable command. */
 362  3 private static String findJavaCommand(File f) {
 363  0 if (IOUtil.attemptIsFile(f)) { return f.getPath(); }
 364  3 else if (IOUtil.attemptIsDirectory(f)) {
 365    // This logic originally came from Ant.
 366  3 f = IOUtil.attemptAbsoluteFile(f);
 367  3 String os = System.getProperty("os.name", "");
 368  3 File[] candidates = new File[]{ new File(f, "../bin"), new File(f, "bin"), f };
 369   
 370  3 if (!TextUtil.containsIgnoreCase(os, "netware")) { // based on comments from Ant's code
 371  3 if (TextUtil.containsIgnoreCase(os, "windows")) {
 372  0 for (File dir : candidates) {
 373  0 File result = new File(dir, "javaw.exe");
 374  0 if (IOUtil.attemptExists(result)) { return result.getPath(); }
 375  0 result = new File(dir, "java.exe");
 376  0 if (IOUtil.attemptExists(result)) { return result.getPath(); }
 377    }
 378    }
 379    else {
 380  3 for (File dir : candidates) {
 381  3 File result = new File(dir, "java");
 382  3 if (IOUtil.attemptExists(result)) { return result.getPath(); }
 383    }
 384    }
 385    }
 386    }
 387    // If nothing works, use the original file or command string unchanged
 388  0 return f.toString();
 389    }
 390   
 391   
 392    /**
 393    * Copy the give Properties into a properly-typed Map. Guarantees that all entries are strings
 394    * (an exception should occur in the Properties API otherwise). Includes both defined and default
 395    * values appearing in the Properties objecty (see {@link Properties#Properties(Properties)}).
 396    */
 397  8 private static Map<String, String> copyProps(Properties p) {
 398  8 Map<String, String> result = new HashMap<String, String>();
 399  8 @SuppressWarnings("unchecked") Enumeration<String> names = (Enumeration<String>) p.propertyNames();
 400  8 while (names.hasMoreElements()) {
 401  16 String name = names.nextElement();
 402  16 result.put(name, p.getProperty(name));
 403    }
 404  8 return result;
 405    }
 406   
 407    }