Clover coverage report - DrJava Test Coverage (drjava-20120422-r5456)
Coverage timestamp: Sun Apr 22 2012 03:13:25 CDT
file stats: LOC: 258   Methods: 12
NCLOC: 127   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
AbstractMasterJVM.java 60% 71.2% 91.7% 72.7%
coverage coverage
 1    /*BEGIN_COPYRIGHT_BLOCK
 2    *
 3    * Copyright (c) 2001-2010, JavaPLT group at Rice University (drjava@rice.edu)
 4    * All rights reserved.
 5    *
 6    * Redistribution and use in source and binary forms, with or without
 7    * modification, are permitted provided that the following conditions are met:
 8    * * Redistributions of source code must retain the above copyright
 9    * notice, this list of conditions and the following disclaimer.
 10    * * Redistributions in binary form must reproduce the above copyright
 11    * notice, this list of conditions and the following disclaimer in the
 12    * documentation and/or other materials provided with the distribution.
 13    * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
 14    * names of its contributors may be used to endorse or promote products
 15    * derived from this software without specific prior written permission.
 16    *
 17    * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 18    * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 19    * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 20    * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 21    * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 22    * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 23    * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 24    * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 25    * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 26    * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 27    * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 28    *
 29    * This software is Open Source Initiative approved Open Source Software.
 30    * Open Source Initative Approved is a trademark of the Open Source Initiative.
 31    *
 32    * This file is part of DrJava. Download the current version of this project
 33    * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
 34    *
 35    * END_COPYRIGHT_BLOCK*/
 36   
 37    package edu.rice.cs.util.newjvm;
 38   
 39    import java.rmi.NoSuchObjectException;
 40    import java.rmi.RemoteException;
 41    import java.rmi.server.UnicastRemoteObject;
 42    import java.io.Serializable;
 43    import java.util.Map;
 44   
 45    import edu.rice.cs.util.UnexpectedException;
 46    import edu.rice.cs.plt.collect.CollectUtil;
 47    import edu.rice.cs.plt.concurrent.ConcurrentUtil;
 48    import edu.rice.cs.plt.concurrent.JVMBuilder;
 49    import edu.rice.cs.plt.concurrent.StateMonitor;
 50    import edu.rice.cs.plt.lambda.LazyThunk;
 51    import edu.rice.cs.plt.lambda.Runnable1;
 52    import edu.rice.cs.plt.lambda.Thunk;
 53    import edu.rice.cs.plt.lambda.WrappedException;
 54    import edu.rice.cs.plt.reflect.ReflectException;
 55    import edu.rice.cs.plt.reflect.ReflectUtil;
 56   
 57    import static edu.rice.cs.plt.debug.DebugUtil.debug;
 58    import static edu.rice.cs.plt.debug.DebugUtil.error;
 59   
 60    /**
 61    * An abstract class implementing the logic to invoke and control, via RMI, a second Java virtual
 62    * machine. This class is used by subclassing it. (See package documentation for more details.)
 63    * The state-changing methods of this class consistently block until a precondition for the state
 64    * change is satisfied — for example, {@link #quitSlave} cannot complete until a slave is
 65    * running. Only one thread may change the state at a time. Thus, clients should be careful
 66    * to only invoke state-changing methods when they are guaranteed to succeed (only invoking
 67    * {@code quitSlave()}, for example, when it is known to have been matched by a successful
 68    * {@code invokeSlave} invocation).
 69    *
 70    * @version $Id: AbstractMasterJVM.java 5246 2010-05-07 19:10:44Z mgricken $
 71    */
 72    public abstract class AbstractMasterJVM implements MasterRemote {
 73   
 74    /**
 75    * Synchronization strategy: compare-and-swap guarantees that only one thread enters a STARTING, or
 76    * QUITTING, or DISPOSED state. After that, the only state transitions out of STARTING/QUITTING occur
 77    * in the same thread (or a single designated worker thread); all other threads must wait until the
 78    * transition to FRESH or RUNNING.
 79    */
 80    private enum State { FRESH, STARTING, RUNNING, QUITTING, DISPOSED };
 81   
 82    /** Loads an instance of the given AbstractSlaveJVM class. Invoked in the slave JVM. */
 83    private static class SlaveFactory implements Thunk<AbstractSlaveJVM>, Serializable {
 84    private final String _className;
 85  161 public SlaveFactory(String className) { _className = className; }
 86  187 public AbstractSlaveJVM value() {
 87  187 try { return (AbstractSlaveJVM) ReflectUtil.getStaticField(_className, "ONLY"); }
 88    catch (ReflectException e) {
 89  0 try { return (AbstractSlaveJVM) ReflectUtil.loadObject(_className); }
 90  0 catch (ReflectException e2) { throw new WrappedException(e2); }
 91    }
 92    }
 93    }
 94   
 95    private final StateMonitor<State> _monitor;
 96    private final SlaveFactory _slaveFactory;
 97    private final LazyThunk<MasterRemote> _masterStub;
 98    /** The slave JVM remote stub (non-null when the state is RUNNING). */
 99    private volatile SlaveRemote _slave;
 100   
 101    /**
 102    * Set up the master JVM object. Does not start a slave JVM.
 103    * @param slaveClassName The fully-qualified class name of the class to start up in the second JVM. Must be a
 104    * subclass of {@link AbstractSlaveJVM}.
 105    */
 106  161 protected AbstractMasterJVM(String slaveClassName) {
 107  161 _monitor = new StateMonitor<State>(State.FRESH);
 108  161 _slaveFactory = new SlaveFactory(slaveClassName);
 109  161 _masterStub = new LazyThunk<MasterRemote>(new Thunk<MasterRemote>() {
 110  161 public MasterRemote value() {
 111  161 try { return (MasterRemote) UnicastRemoteObject.exportObject(AbstractMasterJVM.this, 0); }
 112    catch (RemoteException re) {
 113  0 error.log(re);
 114  0 throw new UnexpectedException(re);
 115    }
 116    }
 117    });
 118  161 _slave = null;
 119    // Make sure RMI doesn't use an IP address that might change
 120  161 System.setProperty("java.rmi.server.hostname", "127.0.0.1");
 121    }
 122   
 123    /**
 124    * Callback for when the slave JVM has connected, and the bidirectional communications link has been
 125    * established. Provides access to the newly-created slave JVM.
 126    */
 127    protected abstract void handleSlaveConnected(SlaveRemote newSlave);
 128   
 129    /**
 130    * Callback for when the slave JVM has quit.
 131    * @param status The exit code returned by the slave JVM.
 132    */
 133    protected abstract void handleSlaveQuit(int status);
 134   
 135    /**
 136    * Callback for when the slave JVM fails to either run or respond to {@link SlaveRemote#start}.
 137    * @param e Exception that occurred during startup.
 138    */
 139    protected abstract void handleSlaveWontStart(Exception e);
 140   
 141    /**
 142    * Creates and starts the slave JVM. If the the slave is currently running, waits until it completes.
 143    * Also waits until the new process has started up and calls one of {@link #handleSlaveConnected}
 144    * or {@link #handleSlaveWontStart} before returning.
 145    * @param jvmBuilder JVMBuilder to use in starting the remote process.
 146    * @throws IllegalStateException If this object has been disposed.
 147    */
 148  187 protected final void invokeSlave(JVMBuilder jvmBuilder) {
 149  187 transition(State.FRESH, State.STARTING);
 150   
 151    // update jvmBuilder with any special properties
 152  187 Map<String, String> props = ConcurrentUtil.getPropertiesAsMap("plt.", "drjava.", "edu.rice.cs.");
 153  187 if (!props.containsKey("plt.log.working.dir") && // Set plt.log.working.dir, in case the working dir changes
 154    (props.containsKey("plt.debug.log") || props.containsKey("plt.error.log") ||
 155    props.containsKey("plt.log.factory"))) {
 156  187 props.put("plt.log.working.dir", System.getProperty("user.dir", ""));
 157    }
 158    // include props, but shadow them with any definitions in jvmBuilder
 159  187 final JVMBuilder tweakedJVMBuilder = jvmBuilder.properties(CollectUtil.union(props, jvmBuilder.properties()));
 160   
 161  187 SlaveRemote newSlave = null;
 162  187 try {
 163  187 debug.logStart("invoking remote JVM process");
 164  187 newSlave =
 165    (SlaveRemote) ConcurrentUtil.exportInProcess(_slaveFactory, tweakedJVMBuilder, new Runnable1<Process>() {
 166  163 public void run(Process p) {
 167  164 debug.log("Remote JVM quit");
 168  164 _monitor.set(State.FRESH);
 169    //debug.log("Entered state " + State.FRESH);
 170  164 debug.logStart("handleSlaveQuit");
 171  164 handleSlaveQuit(p.exitValue());
 172  28 debug.logEnd("handleSlaveQuit");
 173    }
 174    });
 175  187 debug.logEnd("invoking remote JVM process");
 176    }
 177    catch (Exception e) {
 178  0 debug.log(e);
 179  0 debug.logEnd("invoking remote JVM process (failed)");
 180  0 _monitor.set(State.FRESH);
 181    //debug.log("Entered state " + State.FRESH);
 182  0 handleSlaveWontStart(e);
 183    }
 184   
 185  187 if (newSlave != null) {
 186  187 try { newSlave.start(_masterStub.value()); }
 187    catch (RemoteException e) {
 188  0 debug.log(e);
 189  0 attemptQuit(newSlave);
 190  0 _monitor.set(State.FRESH);
 191    //debug.log("Entered state " + State.FRESH);
 192  0 handleSlaveWontStart(e);
 193  0 return;
 194    }
 195   
 196  187 handleSlaveConnected(newSlave);
 197  187 _slave = newSlave;
 198  187 _monitor.set(State.RUNNING);
 199    //debug.log("Entered state " + State.RUNNING);
 200    }
 201    }
 202   
 203    /**
 204    * Quits slave JVM. If a slave is not currently started and running, blocks until that state is reached.
 205    * @throws IllegalStateException If this object has been disposed.
 206    */
 207  184 protected final void quitSlave() {
 208  184 transition(State.RUNNING, State.QUITTING);
 209  184 attemptQuit(_slave);
 210  184 _slave = null;
 211  184 _monitor.set(State.FRESH);
 212    //debug.log("Entered state " + State.FRESH);
 213    }
 214   
 215    /** Make a best attempt to invoke {@code slave.quit()}. Log an error if it fails. */
 216  184 private static void attemptQuit(SlaveRemote slave) {
 217  184 try { slave.quit(); }
 218  0 catch (RemoteException e) { error.log("Unable to complete slave.quit()", e); }
 219    }
 220   
 221    /**
 222    * Free the resources required for this object to respond to RMI invocations (useful for applications -- such as
 223    * testing -- that produce a large number of MasterJVMs as a program runs). Requires the slave to have
 224    * quit; blocks until that occurs. After an object has been disposed, it is no longer useful.
 225    */
 226  159 protected void dispose() {
 227  159 transition(State.FRESH, State.DISPOSED);
 228  159 if (_masterStub.isResolved()) {
 229  159 try { UnicastRemoteObject.unexportObject(this, true); }
 230  0 catch (NoSuchObjectException e) { error.log(e); }
 231    }
 232    }
 233   
 234    /**
 235    * Make a thread-safe state transition. Blocks until the {@code from} state is reached and this
 236    * thread is successful in performing the transition (only one thread can do so at a time). Throws
 237    * an IllegalStateException if the DISPOSED state is reached first, since there is never a transition
 238    * out of the disposed state (the alternative is to block permanently).
 239    */
 240  530 private void transition(State from, State to) {
 241  530 State s = _monitor.value();
 242    // watch all state transitions until from->to is successful or the DISPOSED state is reached
 243  530 while (!(s.equals(from) && _monitor.compareAndSet(from, to))) {
 244  0 if (s.equals(State.DISPOSED)) { throw new IllegalStateException("In disposed state"); }
 245  5 debug.log("Waiting for transition from " + s + " to " + from);
 246  5 try { s = _monitor.ensureNotState(s); }
 247  0 catch (InterruptedException e) { throw new UnexpectedException(e); }
 248    }
 249    //debug.log("Entered state " + to);
 250    }
 251   
 252  0 protected boolean isDisposed() { return _monitor.value().equals(State.DISPOSED); }
 253   
 254    /** No-op to prove that the master is still alive. */
 255  235 public void checkStillAlive() { }
 256   
 257    }
 258