package edu.princeton.toy.lang; import java.util.ArrayList; import java.util.List; import javax.swing.event.*; /** * TVirtualMachine is an object that encapsulates the concept of the Toy Machine. This includes a * program counter, set of registers, memory array, and a set of I/O buffers. * * @author btsang * @version 7.1 */ public class TVirtualMachine { /** * The size of the memory array. */ public static final int MEM_COUNT = 0x100; /** * The number of registers. */ public static final int REGISTER_COUNT = 0x10; /** * The address on which the Virtual Machine begins execution. */ public static final TWord PC_START = TWord.getWord((short)0x10); /** * The address on which the Virtual Machine begins execution. */ public static final short PC_START_VALUE = PC_START.getValue(); private List changeListeners; private TWord programCtr; private TWord mem[]; private TWord registers[]; private TWordBuffer unconsumedStdin; private TWordBuffer consumedStdin; private TWordBuffer stdout; private StringBuffer stderr; private TExceptionHandler exceptionHandler; private boolean needsInput; private boolean done; private Runner runner; /** * Constructs a completely blank TVirtualMachine. */ public TVirtualMachine() { changeListeners = new ArrayList(); mem = new TWord[MEM_COUNT]; registers = new TWord[REGISTER_COUNT]; unconsumedStdin = new TWordBuffer(); consumedStdin = new TWordBuffer(); stdout = new TWordBuffer(); stderr = new StringBuffer(); exceptionHandler = TExceptionHandler.PRUDISH_EXCEPTION_HANDLER; needsInput = false; done = false; runner = new Runner(); reset(); } /** * Override the finalize() method to also interrupt the Runner. */ public void finalize() throws Throwable { runner.interrupted = true; super.finalize(); } /** * Adds a listener to monitor changes in the state of this machine. * * @param listener The listener to add to this virtualMachine. A null value will cause a * NullPointerException. */ public synchronized void addChangeListener(ChangeListener listener) { if (listener == null) throw new NullPointerException(); changeListeners.add(listener); } /** * Removes a listener from this machine. Nothing will happen if no match was found. * * @param listener The listener to remove from this virtualMachine. A null value will cause a * NullPointerException. */ public synchronized void removeChangeListener(ChangeListener listener) { if (listener == null) throw new NullPointerException(); changeListeners.remove(listener); } /** * Returns true iff an error has been encountered. * * @return True iff an exception has been raised but not ignored. */ public boolean hasEncounteredError() { return stderr.length() > 0; } /** * Returns wheter or not the GUI should continue stepping. * * @return True iff the Virtual Machine has encountered an error or has reached a halt * statement. */ public boolean isDone() { return done; } /** * Returns wheter or not the GUI should prompt the user for input. * * @return True iff the stdin buffer is empty and the VM has an executed a command to read from * stdin. */ public boolean needsInput() { return needsInput; } /** * A convenience method for determining wheter or not uninitialied values should be * distinguished from a simple zero. If any of the uninitialized class of exceptions is * ignored by the exception handler, then this method will return false. Otherwise, true will * be returned. * * @return True iff any of the uninitialized class of exceptions is ignored by the exception * handler. */ public boolean getDistinguishUninitialized() { for (int ctr = 0; ctr < TExceptionType.TYPES.length; ctr++) { TExceptionType type = TExceptionType.TYPES[ctr]; if (type.getFamily() == TExceptionType.UNINITIALIZED_FAMILY && !exceptionHandler.getWillThrow(type)) { return false; } } return true; } /** * Returns the exception handler. * * @return the exception handler. */ public TExceptionHandler getExceptionHandler() { return exceptionHandler; } /** * Sets the exception handler. * * @param exceptionHandler The exception handler. A null value will result in a NullPointerException. */ public void setExceptionHandler(TExceptionHandler exceptionHandler) { if (exceptionHandler == null) throw new NullPointerException(); this.exceptionHandler = exceptionHandler; } /** * Returns the program counter. * * @return the program counter. */ public TWord getProgramCtr() { return programCtr; } /** * Change the programCtr. * * @param programCtr The new value of the programCtr. A null value will result in a * NullPointerException. */ public synchronized void setProgramCtr(TWord programCtr) { if (programCtr == this.programCtr) return; this.programCtr = programCtr; done = false; fireStateChanged(); } /** * Get the value of a particular register. * * @param index The index of the requested register. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @return The value of the requested register. */ public TWord getRegister(int index) { return registers[index]; } /** * Sets the value of a particular register. * * @param index The index of the register to be changed. An invalid value will result in an * ArrayIndexOutOfBoundsException. * @param word The new value of the register. A null value will result in a * NullPointerException. */ public synchronized void setRegister(int index, TWord word) { if (word == null) throw new NullPointerException(); if (registers[index] == word) return; registers[index] = word; fireStateChanged(); } /** * Returns the current instruction. * * @return Returns the current instruction. Null is returned if pc is out of bounds and if * the exception handler does not ignore 'pc out of bounds' errors. * @see #getMem(int) * @see #getProgramCtr() */ public TWord getCurrentInstruction() { return mem[programCtr.getValue() & 0xFF]; } /** * Copies all of the virtual machine's memory onto the given TWord array. * * @param array The array on which to copy the virtual machine's memory. If the array is null, * a NullPointerException will be thrown, if the array is too small, an * ArrayIndexOutOfBoundsException will be thrown. */ public void getMem(TWord array[]) { if (array.length < MEM_COUNT) throw new ArrayIndexOutOfBoundsException(); for (int ctr = 0; ctr < MEM_COUNT; ctr++) array[ctr] = mem[ctr]; } /** * Returns the value at the requested memory memory. * * @param index The address of the requested value. An invalid value will result in * an ArrayIndexOutOfBoundsException. * @return The value at the requested memory address. */ public TWord getMem(int index) { return mem[index]; } /** * Sets the value of the specified memory memory. * * @param index The address of the value to be changed. An invalid value will result in * an ArrayIndexOutOfBoundsException. * @param word The new value of the memory memory. A null value will result in a * NullPointerException. */ public synchronized void setMem(int index, TWord word) { if (word == null) throw new NullPointerException(); if (mem[index] == word) return; mem[index] = word; fireStateChanged(); } /** * Initializes all of memory with an array of size exactly MEM_COUNT containing no null * values. * * @param words The new contents of the memory array. A null value will result in a * NullPointerException, and an incorrect length will result in an IllegalArgumentException. */ public synchronized void initMem(TWord words[]) { if (words.length != MEM_COUNT) throw new IllegalArgumentException(); for (int ctr = 0; ctr < MEM_COUNT; ctr++) { if (words[ctr] == null) throw new NullPointerException(); mem[ctr] = words[ctr]; } fireStateChanged(); } /** * Returns the TWordBuffer containing all of the words which have been read already. This is * only a copy of the internal TWordBuffer. * * @return The TWordBuffer containing all of the words which have been read already. */ public TWordBuffer getConsumedStdin() { return (TWordBuffer)consumedStdin.clone(); } /** * Copies all of the words which have been read already to the provided TWordBuffer. * * @param buffer The buffer to copy the data to. The buffer will be cleared before the copy. * @return The same TWordBuffer that was passed as an argument. */ public TWordBuffer getConsumedStdin(TWordBuffer buffer) { buffer.clear(); buffer.add(consumedStdin); return buffer; } /** * Returns the TWordBuffer containing all of the words which have not been read yet. This is * only a copy of the internal TWordBuffer. * * @return The TWordBuffer containing all of the words which have not been read yet. */ public TWordBuffer getUnconsumedStdin() { return (TWordBuffer)unconsumedStdin.clone(); } /** * Copies all of the words which have not been read yet to the provided TWordBuffer. * * @param buffer The buffer to copy the data to. The buffer will be cleared before the copy. * @return The same TWordBuffer that was passed as an argument. */ public TWordBuffer getUnconsumedStdin(TWordBuffer buffer) { buffer.clear(); buffer.add(unconsumedStdin); return buffer; } /** * Clears the consumedStdin and sets unconsumedStdin to contain the values provided. Note that * the TWordBuffer provided will not be used as the internal TWordBuffer, nor will it be * modified during the duplication process. * * @param unconsumedStdin The new values for the unconsumed stdin. A null implies that no * changes should be made to the current value. */ public synchronized void setStdin(TWordBuffer unconsumedStdin) { consumedStdin.clear(); setStdin(consumedStdin, unconsumedStdin); } /** * Sets the consumedStdin and unconsumedStdin to contain the values provided. Note that * the TWordBuffers provided will not be used as the internal TWordBuffers, nor will they be * modified during the duplication process. * * @param consumedStdin The new values for the consumed stdin. A null implies that no * changes should be made to the current value. * @param unconsumedStdin The new values for the unconsumed stdin. A null implies that no * changes should be made to the current value. */ public synchronized void setStdin(TWordBuffer consumedStdin, TWordBuffer unconsumedStdin) { if (consumedStdin != null && consumedStdin != this.consumedStdin) { this.consumedStdin.clear(); this.consumedStdin.add(consumedStdin); } if (unconsumedStdin != null && unconsumedStdin != this.unconsumedStdin) { this.unconsumedStdin.clear(); this.unconsumedStdin.add(unconsumedStdin); } if (needsInput && unconsumedStdin.getSize() > 0) needsInput = false; fireStateChanged(); } /** * Returns the TWordBuffer containing all of the words which have been outputted by the virtual * machine. This is only a copy of the internal TWordBuffer. * * @return The TWordBuffer containing all of the words which have not been read yet. */ public TWordBuffer getStdout() { return (TWordBuffer)stdout.clone(); } /** * Copies all of the words which have been outputted by the virtual machine to the provided * TWordBuffer. * * @param buffer The buffer to copy the data to. The buffer will be cleared before the copy. * @return The same TWordBuffer that was passed as an argument. */ public TWordBuffer getStdout(TWordBuffer buffer) { buffer.clear(); buffer.add(stdout); return buffer; } /** * Returns the standard error stream. * * @return the standard error stream. */ public String getStderr() { return stderr.toString(); } /** * Returns a memory dump of the toy machine, which could be parsed by Visual X-TOY as another * program. * * @return A memory dump of the toy machine. */ public String getMemDump() { StringBuffer buffer = new StringBuffer(); boolean printedPreviousLine = false; for (short ctr = 0x0; ctr < MEM_COUNT; ctr++) { if (mem[ctr].isInitialized()) { buffer.append(TWord.HEX_PAIRS[ctr]); buffer.append(": "); buffer.append(mem[ctr].toHexString(false)); buffer.append('\n'); printedPreviousLine = true; } else { if (printedPreviousLine) { buffer.append('\n'); printedPreviousLine = false; } } } // Crop off that last newline if appropriate int length = buffer.length(); if (!printedPreviousLine && length > 0) buffer.setLength(length - 1); return buffer.toString(); } /** * Returns a core dump of the toy machine. The virtual machine will used the exception handler * to decide wheter or not to distinguish uninitialized values from a simple zero. * * @return getCoreDump(getDistinguishUninitialized()). */ public String getCoreDump() { return getCoreDump(getDistinguishUninitialized()); } /** * Returns a core dump of the toy machine. * * @param distinguishUninitialized If true, uninitialized memory memorys and registers will * show ???? instead of 0000. * @return A core dump of the toy machine. */ public String getCoreDump(boolean distinguishUninitialized) { StringBuffer buffer = new StringBuffer(); // Dump the programCtr buffer.append("// State:\n"); buffer.append("PC: "); buffer.append(programCtr); buffer.append('\n'); buffer.append("IR: "); TWord ir = mem[programCtr.getValue() & 0xFF]; buffer.append(ir.toHexString(distinguishUninitialized)); buffer.append(" ("); buffer.append(ir.toPseudoCodeString(distinguishUninitialized)); buffer.append(")\n\n"); // Dump registers buffer.append("// Registers:\n"); for (short ctr = 0x0; ctr < REGISTER_COUNT; ctr++) { buffer.append("R["); buffer.append(TWord.HEX_DIGITS[ctr]); buffer.append("]: "); buffer.append(registers[ctr].toString(distinguishUninitialized)); buffer.append('\n'); } buffer.append('\n'); // Dump mem, use an ellipsis (...) whenever necessary boolean printedPreviousLine = true; buffer.append("// Memory:\n"); for (short ctr = 0x0; ctr < MEM_COUNT; ctr++) { if (mem[ctr].isInitialized()) { buffer.append("M["); buffer.append(TWord.HEX_PAIRS[ctr]); buffer.append("]: "); if (ctr < PC_START_VALUE) { buffer.append(mem[ctr].toString(false)); buffer.append('\n'); } else { buffer.append(mem[ctr].toHexString(false)); buffer.append(" ("); buffer.append(mem[ctr].toPseudoCodeString(false)); buffer.append(")\n"); } printedPreviousLine = true; } else { if (printedPreviousLine) { buffer.append("...\n"); printedPreviousLine = false; } } } return buffer.toString(); } /** * Wipes out the memory and registers, resets the programCtr to PC_START, and flushes the * streams. */ public synchronized void reset() { programCtr = PC_START; for (int ctr = 0; ctr < MEM_COUNT; ctr++) mem[ctr] = TWord.UNINITIALIZED_VALUE; registers[0] = TWord.ZERO; for (int ctr = 1; ctr < REGISTER_COUNT; ctr++) registers[ctr] = TWord.UNINITIALIZED_VALUE; unconsumedStdin.clear(); consumedStdin.clear(); stdout.clear(); stderr.setLength(0); needsInput = false; done = false; fireStateChanged(); } /** * Fires a state changed event to all the listeners. */ protected void fireStateChanged() { if (!changeListeners.isEmpty()) { ChangeEvent e = new ChangeEvent(this); Object array[] = changeListeners.toArray(); for (int ctr = 0; ctr < array.length; ctr++) ((ChangeListener)array[ctr]).stateChanged(e); } } /** * Will cause the runner (started by the run method) to stop. * * @see #isRunning() * @see #run(TVirtualMachine.ExecutionController) */ public void interrupt() { runner.interrupted = true; } /** * Returns wheter or not the runner (started by the run method) is running. * * @return True iff the runner is running. * @see #interrupt() * @see #run(TVirtualMachine.ExecutionController) */ public boolean isRunning() { return runner.isRunning; } /** * Executes the command which programCtr points to and increments programCtr if necessary one * time, dispatching ChangeEvents to all the listeners afterwards. * * @return The number of times actually stepped. 0 will be returned if any of isDone(), * needsInput(), or isRunning() is true, otherwise, 1 will be returned. */ public synchronized int step() { if (done || needsInput) return 0; if (!runner.isRunning) { stepImpl(); fireStateChanged(); if (needsInput) return 0; else return 1; } else { return 0; } } /** * Executes the command which programCtr points to and increments programCtr if necessary n * times, dispatching ChangeEvents to all the listeners afterwards. Note that a halt command, * needsInputFlag, interrupt() call, or exception could all prematurely stop the stepping. * * @param controller The implementation of ExecutionController which will recieve notification * of the completion of a batch of steps and control the size of each batch. A null value will * result in a NullPointerException. * @return True iff a runner was actually started. A runner will not be started if n is 0, or * if any of isDone(), needsInput(), or isRunning() is true. * * @see #interrupt() * @see #isRunning() */ public synchronized boolean run(ExecutionController controller) { if (controller == null) throw new NullPointerException(); if (done || needsInput || runner.isRunning) { return false; } else { runner.start(controller); fireStateChanged(); return true; } } /** * Executes the command which programCtr points to and increments programCtr if necessary.. */ private void stepImpl() { TWord currentInstruction; byte op, d, s, t; short imm; TWord oldProgramCtr = programCtr.initializedValue(); try { // Fetch and parse currentInstruction = mem[programCtr.getValue() & 0xFF]; if (!currentInstruction.isInitialized()) exceptionHandler.raise(TExceptionType.COMMAND_UNINITIALIZED); op = currentInstruction.getOp(); d = currentInstruction.getD(); s = currentInstruction.getS(); t = currentInstruction.getT(); imm = currentInstruction.getImm(); // Increment programCtr = TWord.add(programCtr, TWord.ONE, null); // Handle Stdin if (imm == 0xFF && op == 0x8 || (registers[t].getValue() & 0xFF) == 0xFF && op == 0xA) { // Do we have enough data in our stdin buffer? if (unconsumedStdin.getSize() > 0) { // Yes we do, so put it in mem[FF] mem[0xFF] = unconsumedStdin.pop(); consumedStdin.add(mem[0xFF]); } else { // No we don't, turn on the flag that we need input and return needsInput = true; programCtr = oldProgramCtr; return; } } // Execute (and handle stdin and stdout) switch (op) { // halt case 0x0: programCtr = oldProgramCtr; done = true; break; // R[d] <- R[s] + R[t] case 0x1: // We could be trying to assign a value to R[0], but don't throw an error when // our command is 1000 (that's a nop) if (d == 0 && !(s == 0 && t == 0)) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.add(registers[s], registers[t], exceptionHandler); break; // R[d] <- R[s] - R[t] case 0x2: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.subtract(registers[s], registers[t], exceptionHandler); break; // R[d] <- R[s] & R[t] case 0x3: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.and(registers[s], registers[t], exceptionHandler); break; // R[d] <- R[s] ^ R[t] case 0x4: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.xor(registers[s], registers[t], exceptionHandler); break; // R[d] <- R[s] << t case 0x5: //we could be trying to assign a value to R[0] if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); //put R[s] << t in R[d] registers[d] = TWord.leftShift(registers[s], registers[t], exceptionHandler); break; // R[d] <- R[s] >> t case 0x6: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.rightShift(registers[s], registers[t], exceptionHandler); break; // R[d] <- addr case 0x7: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = TWord.getWord(imm); break; // R[d] <- M[addr] case 0x8: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); if (!mem[imm].isInitialized()) exceptionHandler.raise(TExceptionType.MEMORY_UNINITIALIZED); registers[d] = mem[imm].initializedValue(); break; // M[addr] <- R[d] case 0x9: if (!registers[d].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); mem[imm] = registers[d].initializedValue(); break; // R[d] <- M[R[t]] case 0xA: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); if (!registers[t].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if (registers[t].getValue() < 0 || registers[t].getValue() > MEM_COUNT) exceptionHandler.raise(TExceptionType.MEM_OUT_OF_BOUNDS); if (!mem[registers[t].getValue() & 0xFF].isInitialized()) exceptionHandler.raise(TExceptionType.MEMORY_UNINITIALIZED); registers[d] = mem[registers[t].getValue() & 0xFF].initializedValue(); break; // M[R[t]] <- R[d] case 0xB: if (!registers[t].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if (registers[t].getValue() < 0 || registers[t].getValue() >= MEM_COUNT) exceptionHandler.raise(TExceptionType.MEM_OUT_OF_BOUNDS); if (!registers[d].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); mem[registers[t].getValue() & 0xFF] = registers[d].initializedValue(); break; // if (R[d] == 0) PC <- addr case 0xC: if (!registers[d].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if (registers[d].getValue() == 0) programCtr = TWord.getWord(imm); break; // if (R[d] > 0) PC <- addr case 0xD: if (!registers[d].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); if (registers[d].getValue() > 0) programCtr = TWord.getWord(imm); break; // PC <- R[d] case 0xE: if (!registers[d].isInitialized()) exceptionHandler.raise(TExceptionType.REGISTER_UNINITIALIZED); programCtr = registers[d].initializedValue(); break; // R[d] <- PC; PC <- addr case 0xF: if (d == 0) exceptionHandler.raise(TExceptionType.REGISTER_OUT_OF_BOUNDS); registers[d] = programCtr.initializedValue(); programCtr = TWord.getWord(imm); break; } // Handle stdout if (imm == 0xFF && op == 0x9 || (registers[t].getValue() & 0xFF) == 0xFF && op == 0xB) { stdout.add(mem[0xFF]); } // Restore the zeroth register to 0 registers[0] = TWord.ZERO; // Check the pc if (programCtr.getValue() <= 0 || programCtr.getValue() > MEM_COUNT) { exceptionHandler.raise(TExceptionType.PC_OUT_OF_BOUNDS); programCtr = TWord.getWord((short)(programCtr.getValue() & 0xFF)); } } catch (TException e) { programCtr = oldProgramCtr; stderr.append("A runtime error has occurred at address "); stderr.append(TWord.HEX_PAIRS[programCtr.getValue() & 0xFF]); stderr.append(":\n"); stderr.append(e.getType().getDescription()); done = true; } } /** * ExecutionController is an interface that classes which wish to recieve notifications of a * ToyVirtualMachine's termination should implement. * * @author btsang * @version 7.1 * @see TVirtualMachine#run(TVirtualMachine.ExecutionController) */ public abstract interface ExecutionController { /** * This function is called by the TVirtualMachine's runner when it has completed the * previous batch of steps. * * @param virtualMachine The virtual machine whose status is being reported. * @param n The number of steps taken since the last update. * @param elapsedTime The number of milliseconds between the lastUpdate and the present. * @param willStop Wheter or not the TVirtualMachine will stop after this status update. * @return The number of additional steps to take before fireing more ChangeEvents and * producing another status update. If this is 0, the virtualMachine will stop. If this * is negative, a stack trace will be written to stderr and the virtualMachine will stop. */ public abstract int statusUpdate(TVirtualMachine virtualMachine, int n, int elapsedTime, boolean willStop); /** * This function is called by the TVirtualMachine's runner after it has recieved an order * for a batch of steps to be run. The next status update will not occur until after n * * getClockPeriod() milliseconds, where n was the number of steps taken. If the preferred * clock period is too fast, the next status update will occur as soon the batch of steps * is completed. * * @return The preferred number of milliseconds each step should take. */ public abstract int getClockPeriod(); } /** * Runner is a simple implementation of Runnable for the Thread stated by the run() method * of the TVM. * * @author btsang * @version 7.1 */ protected class Runner implements Runnable { protected boolean isRunning; protected boolean interrupted; private ExecutionController controller; private Thread thread; protected Runner() { isRunning = false; } /** * Causes the runner to start. This should only be called by a Thread which has * synchronized the TVirtualMachine and has checked that the Runner is not already running. * * @param controller The implementation of ExecutionController which will recieve * notification of the completion of a batch of steps and control the size of each batch. */ public void start(ExecutionController controller) { isRunning = true; thread = new Thread(this); this.controller = controller; thread.start(); } /** * Implement Runnable to run the TVirtualMachine. */ public void run() { synchronized (TVirtualMachine.this) { interrupted = false; TVirtualMachine virtualMachine = TVirtualMachine.this; ExecutionController controller = this.controller; boolean willStop = done || needsInput || interrupted; int n = controller.statusUpdate(virtualMachine, 0, 0, willStop); int clockPeriod; long startTime = System.currentTimeMillis(); while (n > 0 && !willStop) { clockPeriod = controller.getClockPeriod(); int ctr = 0; while (ctr < n && !done && !needsInput && !interrupted) { stepImpl(); ctr++; } if (ctr > 0 && needsInput) ctr--; try { long stopSleepTime = startTime + ctr * clockPeriod; // We force a sleep of at least 100 ms after every interation (unless // we were interrupted). int remainingTime = Math.max( 100, (int)(stopSleepTime - System.currentTimeMillis()) ); while (remainingTime > 10 && !interrupted) { Thread.sleep(Math.min(100, remainingTime)); remainingTime = (int)(stopSleepTime - System.currentTimeMillis()); } } catch (InterruptedException e) { interrupted = true; e.printStackTrace(); } long endTime = System.currentTimeMillis(); fireStateChanged(); willStop = done || needsInput || interrupted; n = controller.statusUpdate( virtualMachine, ctr, (int)(endTime - startTime), willStop ); startTime = endTime; } if (n < 0) (new IllegalArgumentException()).printStackTrace(); isRunning = false; fireStateChanged(); thread = null; } } } }