package nl.tudelft.simulation.dsol.interpreter; import java.io.IOException; import java.lang.reflect.AccessibleObject; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; import java.util.Properties; import java.util.Stack; import nl.tudelft.simulation.dsol.interpreter.classfile.ClassDescriptor; import nl.tudelft.simulation.dsol.interpreter.classfile.Constant; import nl.tudelft.simulation.dsol.interpreter.classfile.ExceptionEntry; import nl.tudelft.simulation.dsol.interpreter.classfile.LineNumber; import nl.tudelft.simulation.dsol.interpreter.classfile.MethodDescriptor; import nl.tudelft.simulation.dsol.interpreter.operations.ATHROW; import nl.tudelft.simulation.dsol.interpreter.operations.FactoryInterface; import nl.tudelft.simulation.dsol.interpreter.operations.InterpreterFactory; import nl.tudelft.simulation.dsol.interpreter.operations.InvokeOperation; import nl.tudelft.simulation.dsol.interpreter.operations.JumpOperation; import nl.tudelft.simulation.dsol.interpreter.operations.RET; import nl.tudelft.simulation.dsol.interpreter.operations.RETURN; import nl.tudelft.simulation.dsol.interpreter.operations.ReturnOperation; import nl.tudelft.simulation.dsol.interpreter.operations.VoidOperation; import nl.tudelft.simulation.dsol.interpreter.operations.WIDE; import nl.tudelft.simulation.dsol.interpreter.operations.custom.InterpreterOracleInterface; import nl.tudelft.simulation.language.io.URLResource; import nl.tudelft.simulation.language.reflection.ClassUtil; /** * The Java interpreter. *

* (c) copyright 2002-2014 Delft University of Technology.
* BSD-style license. See DSOL License. *

* @author Peter Jacobs * @author Alexander Verbraeck */ public final class Interpreter { /** the cache. */ private static final Map CACHE = new HashMap(); /** the interpreter factory class name. */ private static FactoryInterface interpreterFactory = null; /** DEBUG tells to print the bytecode statements to stdout */ public static boolean DEBUG = false; static { FactoryInterface factory = new InterpreterFactory(); try { Properties properties = new Properties(); properties.load(URLResource.getResourceAsStream("/interpreter.properties")); Class factoryClass = Class.forName(properties.getProperty("interpreter.operation.factory")); if (properties.getProperty("interpreter.operation.oracle") != null) { InterpreterOracleInterface oracle = (InterpreterOracleInterface) Class.forName( properties.getProperty("interpreter.operation.oracle")).newInstance(); factory = (FactoryInterface) factoryClass.getConstructor(new Class[]{InterpreterOracleInterface.class}) .newInstance(new Object[]{oracle}); } else { factory = (FactoryInterface) factoryClass.newInstance(); } } catch (Exception exception) { // we default exception.printStackTrace(); } Interpreter.setFactory(factory); } /** * sets the Interpreter factory. * @param factory the factory to use */ public static void setFactory(final FactoryInterface factory) { Interpreter.interpreterFactory = factory; } /** * @return interpreterFactory */ public static FactoryInterface getFactory() { return interpreterFactory; } /** * constructs a new Interpreter. */ private Interpreter() { // unreachable code } /** * creates a frame for a method. * @param object the object on which the method must be invoked * @param method the method or constructor * @param arguments the arguments * @return Frame the result * @throws ClassNotFoundException whenever the classpath is incomplete * @throws IOException on IOException */ public static Frame createFrame(final Object object, final AccessibleObject method, final Object[] arguments) throws ClassNotFoundException, IOException { Frame frame = null; Object[] args = new Object[0]; if (arguments != null) { args = arguments; } if (Interpreter.CACHE.containsKey(method)) { frame = (Frame) Interpreter.CACHE.get(method).clone(); } else { ClassDescriptor classDescriptor = null; if (method instanceof Method) { classDescriptor = ClassDescriptor.get(((Method) method).getDeclaringClass()); } else { classDescriptor = ClassDescriptor.get(((Constructor) method).getDeclaringClass()); } MethodDescriptor methodDescriptor = classDescriptor.getMethod(method); OperandStack operandStack = new OperandStack(methodDescriptor.getMaxStack()); LocalVariable[] localVariables = LocalVariable.newInstance(methodDescriptor.getLocalVariableTable()); frame = new Frame(classDescriptor.getConstantPool(), localVariables, methodDescriptor.getOperations(), operandStack, methodDescriptor); Interpreter.CACHE.put(method, frame); } // If method!=static put object on localVariableTable int modifiers = -1; int counter = 0; Class[] parameterTypes = null; if (method instanceof Method) { parameterTypes = ((Method) method).getParameterTypes(); modifiers = ((Method) method).getModifiers(); } else { parameterTypes = ((Constructor) method).getParameterTypes(); modifiers = ((Constructor) method).getModifiers(); } if (!Modifier.isStatic(modifiers)) { frame.getLocalVariables()[counter++].setValue(object); } // add the call parameters for the method to the stack for (int i = 0; i < args.length; i++) { frame.getLocalVariables()[counter++].setValue(arguments[i]); if ((parameterTypes[i].equals(double.class)) || (parameterTypes[i].equals(long.class))) { counter++; } } if (Interpreter.DEBUG) { AccessibleObject m = frame.getMethodDescriptor().getMethod(); String c = null; if (m instanceof Method) c = ((Method) m).getDeclaringClass().getSimpleName(); else c = ((Constructor) m).getDeclaringClass().getSimpleName(); System.out.println(" createFrame " + c + "." + frame.getMethodDescriptor().getName()); } return frame; } /** * throws an exception. * @param operation the aThrow operation to invoke * @param frame the frame to start with * @param frameStack the framestack * @return the frame and operationIndex to continue with... */ public static Frame aThrow(final Operation operation, final Frame frame, final Stack frameStack) { ((ATHROW) operation).execute(frame); ExceptionEntry exceptionEntry = (ExceptionEntry) frame.getOperandStack().pop(); if (exceptionEntry != null) { int operationIndex = frame.getMethodDescriptor().getOperationIndex(exceptionEntry.getHandler()); frame.setReturnPosition(operationIndex); return frame; } // no handler is found. The exception is forwarded... Throwable throwable = (Throwable) frame.getOperandStack().pop(); // First we destroy this frame frameStack.pop(); // We take the caller and push if (frameStack.isEmpty()) { throw new RuntimeException("\n----------------------\n" + throwable + ": " + frame.getLocalVariables()[0].getValue() + "." + frame.getMethodDescriptor().getName() + "\n----------------------"); } Frame newFrame = frameStack.peek(); ((ATHROW) operation).setBytePosition(newFrame.getMethodDescriptor().getBytePosition( newFrame.getReturnPosition())); newFrame.getOperandStack().push(throwable); return Interpreter.aThrow(operation, newFrame, frameStack); } /** * interprets the frameStack. * @param frameStack the frameStack of the interpreter * @return Object the return value of the invoked method */ public static Object interpret(final Stack frameStack) { Frame frame = frameStack.peek(); OperandStack operandStack = frame.getOperandStack(); Constant[] constantPool = frame.getConstantPool(); LocalVariable[] localVariables = frame.getLocalVariables(); MethodDescriptor methodDescriptor = frame.getMethodDescriptor(); int operationIndex = frame.getReturnPosition(); while (true) { Operation operation = frame.getOperations()[operationIndex]; if (DEBUG) { // find the line number int bytePos = 0; for (int i = 0; i < operationIndex; i++) { bytePos += frame.getOperations()[i].getByteLength(); } int line = 0; if (frame.getMethodDescriptor().getLineNumberTable().length == 1) line = frame.getMethodDescriptor().getLineNumberTable()[0].getLineNumber(); for (int i = 0; i < frame.getMethodDescriptor().getLineNumberTable().length - 1; i++) { LineNumber ln1 = frame.getMethodDescriptor().getLineNumberTable()[i]; LineNumber ln2 = frame.getMethodDescriptor().getLineNumberTable()[i + 1]; if (bytePos >= ln1.getStartByte() && bytePos < ln2.getStartByte()) line = ln1.getLineNumber(); } // System.out.print(bytePos + ":"); // for (LineNumber ln : frame.getMethodDescriptor().getLineNumberTable()) // System.out.print(" " + ln.getLineNumber() + "=" + ln.getStartByte()); // System.out.println(); String methodName; if (frame.getMethodDescriptor().getMethod() instanceof Method) methodName = ((Method) frame.getMethodDescriptor().getMethod()).getDeclaringClass().getSimpleName(); else methodName = ((Constructor) frame.getMethodDescriptor().getMethod()).getDeclaringClass() .getSimpleName(); System.out.println(operation.getClass().getSimpleName() + " @ " + methodName + "." + frame.getMethodDescriptor().getName() + "(" + line + ")"); } // WIDE is special. We need to get its target if (operation instanceof WIDE) { operation = ((WIDE) operation).getTarget(); } // ATHROW is special if (operation instanceof ATHROW) { frame = Interpreter.aThrow(operation, frame, frameStack); operandStack = frame.getOperandStack(); constantPool = frame.getConstantPool(); localVariables = frame.getLocalVariables(); methodDescriptor = frame.getMethodDescriptor(); operationIndex = frame.getReturnPosition(); continue; } // Void operations are executed and done if (operation instanceof VoidOperation) { ((VoidOperation) operation).execute(operandStack, constantPool, localVariables); operationIndex++; continue; } // Invoke operations are executed and done if (operation instanceof InvokeOperation) { Frame childFrame = null; try { childFrame = ((InvokeOperation) operation).execute(frame); } catch (Exception exception) { frame.getOperandStack().push(exception); frame = Interpreter.aThrow(new ATHROW(methodDescriptor.getBytePosition(operationIndex)), frame, frameStack); operandStack = frame.getOperandStack(); constantPool = frame.getConstantPool(); localVariables = frame.getLocalVariables(); methodDescriptor = frame.getMethodDescriptor(); operationIndex = frame.getReturnPosition(); continue; } operationIndex++; if (childFrame != null) { frame.setReturnPosition(operationIndex); if (frame.isPaused()) { return frame; } frame = childFrame; frameStack.push(frame); operandStack = frame.getOperandStack(); constantPool = frame.getConstantPool(); localVariables = frame.getLocalVariables(); methodDescriptor = frame.getMethodDescriptor(); operationIndex = 0; } continue; } // What to do with jumps if (operation instanceof JumpOperation) { int offset = ((JumpOperation) operation).execute(operandStack, constantPool, localVariables); int bytePosition = offset; if (!(operation instanceof RET)) { bytePosition = bytePosition + methodDescriptor.getBytePosition(operationIndex); } operationIndex = methodDescriptor.getOperationIndex(bytePosition); continue; } // Return operations are executed and returned if (operation instanceof ReturnOperation) { Object result = ((ReturnOperation) operation).execute(frame); // We destroy this frame frameStack.pop(); // We take the caller and push if (frameStack.isEmpty()) { return result; } frame = frameStack.peek(); operandStack = frame.getOperandStack(); constantPool = frame.getConstantPool(); localVariables = frame.getLocalVariables(); methodDescriptor = frame.getMethodDescriptor(); operationIndex = frame.getReturnPosition(); if (!(operation instanceof RETURN)) { operandStack.push(result); } } } } /** * interprets the invocation of a method on an object. * @param object the object on which the method must be invoked * @param methodName the methodName * @param arguments the arguments * @param argumentTypes the classes of the arguments * @return Object the result */ public static Object invoke(final Object object, final String methodName, final Object[] arguments, final Class[] argumentTypes) { try { AccessibleObject method = null; if (!methodName.equals("")) { if (object instanceof Class) { method = ClassUtil.resolveMethod((Class) object, methodName, argumentTypes); } else { method = ClassUtil.resolveMethod(object.getClass(), methodName, argumentTypes); } } else { method = object.getClass().getDeclaredConstructor(argumentTypes); } return Interpreter.invoke(object, method, arguments); } catch (Exception exception) { throw new InterpreterException(exception); } } /** * interprets the invocation of a method on an object. * @param object the object on which the method must be invoked * @param method the method * @param arguments the arguments * @return Object the result */ public static Object invoke(final Object object, final AccessibleObject method, final Object[] arguments) { if (DEBUG) { String methodName; if (method instanceof Method) methodName = ((Method) method).getName(); else methodName = ((Constructor) method).getName(); System.out.println(" invoke " + object.getClass().getSimpleName() + "." + methodName); } try { if (method instanceof Constructor && Modifier.isNative(((Constructor) method).getModifiers())) { return ((Constructor) method).newInstance(arguments); } if (method instanceof Method && Modifier.isNative(((Method) method).getModifiers())) { return ((Method) method).invoke(object, arguments); } Stack frameStack = new Stack(); frameStack.push(Interpreter.createFrame(object, method, arguments)); // Now we interpret return Interpreter.interpret(frameStack); } catch (Exception exception) { throw new InterpreterException(exception); } } }