| /******************************************************************************* |
| * Copyright (c) 2000, 2003 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Common Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/cpl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.tests.eval.target; |
| |
| import java.lang.reflect.*; |
| import java.io.*; |
| import java.util.*; |
| |
| /** |
| * A code snippet runner loads code snippet classes and global |
| * variable classes, and that run the code snippet classes. |
| * <p> |
| * When started, this runner first connects using TCP/IP to the provided port number. |
| * If a regular classpath directory is provided, it writes the class definitions it gets from the IDE |
| * to this directory (or to the bootclasspath directory if the class name starts with "java") and it |
| * lets the system class loader (or the bootstrap class loader if it is a "java" class) load |
| * the class. |
| * If the regular classpath directory is null, it uses a code snippet class loader to load the classes |
| * it gets from the IDE. |
| * <p> |
| * IMPORTANT NOTE: |
| * Using a code snippet class loader has the following limitation when the code snippet is ran: |
| * <ul> |
| * <li>The code snippet class can access only public classes, and public members or these classes. |
| * This is because the "runtime package" of the code snippet class is always different from |
| * the "runtime package" of the class it is trying to access since the class loaders are |
| * different. |
| * <li>The code snippet class cannot be defined in a "java.*" package. Only the bootstrap class |
| * loader can load such a class. |
| * </ul> |
| */ |
| public class CodeSnippetRunner { |
| public static CodeSnippetRunner theRunner; |
| static final String CODE_SNIPPET_CLASS_NAME = "org.eclipse.jdt.internal.eval.target.CodeSnippet"; |
| static final String RUN_METHOD_NAME = "run"; |
| static final String GET_RESULT_TYPE_METHOD_NAME = "getResultType"; |
| static final String GET_RESULT_VALUE_METHOD_NAME = "getResultValue"; |
| |
| IDEInterface ide; |
| String classPathDirectory; |
| String bootclassPathDirectory; |
| CodeSnippetClassLoader loader; |
| Class codeSnippetClass = null; |
| /** |
| * Creates a new code snippet runner. |
| */ |
| public CodeSnippetRunner(int portNumber, String classPathDirectory, String bootclassPathDirectory) { |
| this.ide = new IDEInterface(portNumber); |
| if (classPathDirectory != null) { |
| this.classPathDirectory = classPathDirectory; |
| if (bootclassPathDirectory != null) { |
| this.bootclassPathDirectory = bootclassPathDirectory; |
| } |
| } else { |
| this.loader = new CodeSnippetClassLoader(); |
| } |
| } |
| /** |
| * Returns the forward slash separated class name from the given class definition. |
| */ |
| private String className(byte[] classDefinition) { |
| // NB: The following code was copied from org.eclipse.jdt.internal.compiler.cfmt, |
| // thus it is highly dependent on the class file format. |
| int readOffset = 10; |
| try { |
| int constantPoolCount = u2At(8, classDefinition); |
| int[] constantPoolOffsets = new int[constantPoolCount]; |
| for (int i = 1; i < constantPoolCount; i++) { |
| int tag = u1At(readOffset, classDefinition); |
| switch (tag) { |
| case 1 : // Utf8Tag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += u2At(readOffset + 1, classDefinition); |
| readOffset += 3; // ConstantUtf8.fixedSize |
| break; |
| case 3 : // IntegerTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantInteger.fixedSize |
| break; |
| case 4 : // FloatTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantFloat.fixedSize |
| break; |
| case 5 : // LongTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 9; // ConstantLong.fixedSize |
| i++; |
| break; |
| case 6 : // DoubleTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 9; // ConstantDouble.fixedSize |
| i++; |
| break; |
| case 7 : // ClassTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 3; // ConstantClass.fixedSize |
| break; |
| case 8 : // StringTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 3; // ConstantString.fixedSize |
| break; |
| case 9 : // FieldRefTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantFieldRef.fixedSize |
| break; |
| case 10 : // MethodRefTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantMethodRef.fixedSize |
| break; |
| case 11 : // InterfaceMethodRefTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantInterfaceMethodRef.fixedSize |
| break; |
| case 12 : // NameAndTypeTag |
| constantPoolOffsets[i] = readOffset; |
| readOffset += 5; // ConstantNameAndType.fixedSize |
| } |
| } |
| // Skip access flags |
| readOffset += 2; |
| |
| // Read the classname, use exception handlers to catch bad format |
| int constantPoolIndex = u2At(readOffset, classDefinition); |
| int utf8Offset = constantPoolOffsets[u2At(constantPoolOffsets[constantPoolIndex] + 1, classDefinition)]; |
| char[] className = utf8At(utf8Offset + 3, u2At(utf8Offset + 1, classDefinition), classDefinition); |
| return new String(className); |
| } catch (ArrayIndexOutOfBoundsException e) { |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| /** |
| * Creates a new instance of the given class. It is |
| * assumed that it is a subclass of CodeSnippet. |
| */ |
| Object createCodeSnippet(Class snippetClass) { |
| Object object = null; |
| try { |
| object = snippetClass.newInstance(); |
| } catch (InstantiationException e) { |
| e.printStackTrace(); |
| this.ide.sendResult(void.class, null); |
| return null; |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); |
| this.ide.sendResult(void.class, null); |
| return null; |
| } |
| return object; |
| } |
| /** |
| * Whether this code snippet runner is currently running. |
| */ |
| public boolean isRunning() { |
| return this.ide.isConnected(); |
| } |
| /** |
| * Starts a new CodeSnippetRunner that will serve code snippets from the IDE. |
| * It waits for a connection on the given evaluation port number. |
| * <p> |
| * Usage: java org.eclipse.jdt.tests.eval.target.CodeSnippetRunner -evalport <portNumber> [-options] [<mainClassName>] [<arguments>] |
| * where options include: |
| * -cscp <codeSnippetClasspath> the the classpath directory for the code snippet classes. |
| * that are not defined in a "java.*" package. |
| * -csbp <codeSnippetBootClasspath> the bootclasspath directory for the code snippet classes |
| * that are defined in a "java.*" package. |
| * <p> |
| * The mainClassName and its arguments are optional: when not present only the server will start |
| * and run until the VM is shut down, when present the server will start, the main class will run |
| * but the server will exit when the main class has finished running. |
| */ |
| public static void main(String[] args) { |
| int length = args.length; |
| if (length < 2 || !args[0].toLowerCase().equals("-evalport")) { |
| printUsage(); |
| return; |
| } else { |
| int evalPort = Integer.parseInt(args[1]); |
| String classPath = null; |
| String bootPath = null; |
| int mainClass = -1; |
| for (int i = 2; i < length; i++) { |
| String arg = args[i]; |
| if (arg.startsWith("-")) { |
| if (arg.toLowerCase().equals("-cscp")) { |
| if (++i < length) { |
| classPath = args[i]; |
| } else { |
| printUsage(); |
| return; |
| } |
| } else if (arg.toLowerCase().equals("-csbp")) { |
| if (++i < length) { |
| bootPath = args[i]; |
| } else { |
| printUsage(); |
| return; |
| } |
| } |
| } else { |
| mainClass = i; |
| break; |
| } |
| } |
| theRunner = new CodeSnippetRunner(evalPort, classPath, bootPath); |
| if (mainClass == -1) { |
| theRunner.start(); |
| } else { |
| Thread server = new Thread() { |
| public void run() { |
| theRunner.start(); |
| } |
| }; |
| server.setDaemon(true); |
| server.start(); |
| int mainArgsLength = length-mainClass-1; |
| String[] mainArgs = new String[mainArgsLength]; |
| System.arraycopy(args, mainClass+1, mainArgs, 0, mainArgsLength); |
| try { |
| Class clazz = Class.forName(args[mainClass]); |
| Method mainMethod = clazz.getMethod("main", new Class[] {String[].class}); |
| mainMethod.invoke(null, new String[][] {mainArgs}); |
| } catch (ClassNotFoundException e) { |
| e.printStackTrace(); |
| } catch (NoSuchMethodException e) { |
| e.printStackTrace(); |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); |
| } catch (InvocationTargetException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| } |
| private static void printUsage() { |
| System.out.println("Usage: java org.eclipse.jdt.tests.eval.target.CodeSnippetRunner -evalport <portNumber> [-options] [<mainClassName>] [<arguments>]"); |
| System.out.println("where options include:"); |
| System.out.println("-cscp <codeSnippetClasspath> the the classpath directory for the code snippet classes."); |
| System.out.println("that are not defined in a \"java.*\" package."); |
| System.out.println("-csbp <codeSnippetBootClasspath> the bootclasspath directory for the code snippet classes"); |
| System.out.println("that are defined in a \"java.*\" package."); |
| } |
| /** |
| * Loads the given class definitions. The way these class definitions are loaded is described |
| * in the CodeSnippetRunner constructor. |
| * The class definitions are code snippet classes and/or global variable classes. |
| * Code snippet classes are assumed be direct or indirect subclasses of CodeSnippet and implement |
| * only the run()V method. |
| * They are instanciated and run. |
| * Global variable classes are assumed to be direct subclasses of CodeSnippet. Their fields are assumed |
| * to be static. The value of each field is sent back to the IDE. |
| */ |
| void processClasses(boolean mustRun, byte[][] classDefinitions) { |
| // store the class definitions (either in the code snippet class loader or on disk) |
| String[] newClasses = new String[classDefinitions.length]; |
| for (int i = 0; i < classDefinitions.length; i++) { |
| byte[] classDefinition = classDefinitions[i]; |
| String classFileName = className(classDefinition); |
| String className = classFileName.replace('/', '.'); |
| if (this.loader != null) { |
| this.loader.storeClassDefinition(className, classDefinition); |
| } else { |
| writeClassOnDisk(classFileName, classDefinition); |
| } |
| newClasses[i] = className; |
| } |
| |
| // load the classes and collect code snippet classes |
| Vector codeSnippetClasses = new Vector(); |
| for (int i = 0; i < newClasses.length; i++) { |
| String className = newClasses[i]; |
| Class clazz = null; |
| if (this.loader != null) { |
| clazz = this.loader.loadIfNeeded(className); |
| if (clazz == null) { |
| System.err.println("Could not find class definition for " + className); |
| break; |
| } |
| } else { |
| // use the system class loader |
| try { |
| clazz = Class.forName(className); |
| } catch (ClassNotFoundException e) { |
| e.printStackTrace(); // should never happen since we just wrote it on disk |
| this.ide.sendResult(void.class, null); |
| break; |
| } |
| } |
| |
| Class superclass = clazz.getSuperclass(); |
| Method[] methods = clazz.getDeclaredMethods(); |
| if (this.codeSnippetClass == null) { |
| if (superclass.equals(Object.class) && clazz.getName().equals(CODE_SNIPPET_CLASS_NAME)) { |
| // The CodeSnippet class is being deployed |
| this.codeSnippetClass = clazz; |
| } else { |
| System.out.println("Expecting CodeSnippet class to be deployed first"); |
| } |
| } else if (superclass.equals(this.codeSnippetClass)) { |
| // It may be a code snippet class with no global variable |
| if (methods.length == 1 && methods[0].getName().equals(RUN_METHOD_NAME)) { |
| codeSnippetClasses.addElement(clazz); |
| } |
| // Evaluate global variables and send result back |
| Field[] fields = clazz.getDeclaredFields(); |
| for (int j = 0; j < fields.length; j++) { |
| Field field = fields[j]; |
| if (Modifier.isPublic(field.getModifiers())) { |
| try { |
| this.ide.sendResult(field.getType(), field.get(null)); |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); // Cannot happen because the field is public |
| this.ide.sendResult(void.class, null); |
| break; |
| } |
| } |
| } |
| } else if (this.codeSnippetClass.equals(superclass.getSuperclass()) && methods.length == 1 && methods[0].getName().equals("run")) { |
| // It is a code snippet class with a global variable superclass |
| codeSnippetClasses.addElement(clazz); |
| } |
| } |
| |
| // run the code snippet classes |
| if (codeSnippetClasses.size() != 0 && mustRun) { |
| for (Enumeration e = codeSnippetClasses.elements(); e.hasMoreElements();) { |
| Object codeSnippet = this.createCodeSnippet((Class) e.nextElement()); |
| if (codeSnippet != null) { |
| this.runCodeSnippet(codeSnippet); |
| } |
| } |
| } |
| } |
| /** |
| * Runs the given code snippet in a new thread and send the result back to the IDE. |
| */ |
| void runCodeSnippet(final Object snippet) { |
| Thread thread = new Thread() { |
| public void run() { |
| try { |
| try { |
| Method runMethod = codeSnippetClass.getMethod(RUN_METHOD_NAME, new Class[] {}); |
| runMethod.invoke(snippet, new Object[] {}); |
| } finally { |
| Method getResultTypeMethod = codeSnippetClass.getMethod(GET_RESULT_TYPE_METHOD_NAME, new Class[] {}); |
| Class resultType = (Class)getResultTypeMethod.invoke(snippet, new Object[] {}); |
| Method getResultValueMethod = codeSnippetClass.getMethod(GET_RESULT_VALUE_METHOD_NAME, new Class[] {}); |
| Object resultValue = getResultValueMethod.invoke(snippet, new Object[] {}); |
| CodeSnippetRunner.this.ide.sendResult(resultType, resultValue); |
| } |
| } catch (NoSuchMethodException e) { |
| e.printStackTrace(); |
| } catch (IllegalAccessException e) { |
| e.printStackTrace(); |
| } catch (IllegalArgumentException e) { |
| System.out.println("codeSnippetClass = " + codeSnippetClass.getName()); |
| System.out.println("snippet.class = " + snippet.getClass().getName()); |
| Class superclass = snippet.getClass().getSuperclass(); |
| System.out.println("snippet.superclass = " + (superclass == null ? "null" : superclass.getName())); |
| e.printStackTrace(); |
| } catch (InvocationTargetException e) { |
| e.getTargetException().printStackTrace(); |
| } |
| } |
| }; |
| thread.setDaemon(true); |
| thread.start(); |
| } |
| /** |
| * Starts this code snippet runner in a different thread. |
| */ |
| public void start() { |
| Thread thread = new Thread("Code snippet runner") { |
| public void run() { |
| try { |
| ide.connect(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| while (ide.isConnected()) { |
| try { |
| processClasses(ide.getRunFlag(), ide.getNextClasses()); |
| } catch (Error e) { |
| ide.sendResult(void.class, null); |
| e.printStackTrace(); |
| } catch (RuntimeException e) { |
| ide.sendResult(void.class, null); |
| e.printStackTrace(); |
| } |
| } |
| }; |
| }; |
| thread.start(); |
| } |
| /** |
| * Stops this code snippet runner. |
| */ |
| public void stop() { |
| this.ide.disconnect(); |
| } |
| private int u1At(int position, byte[] bytes) { |
| return bytes[position] & 0xFF; |
| } |
| private int u2At(int position, byte[] bytes) { |
| return ((bytes[position++] & 0xFF) << 8) + (bytes[position] & 0xFF); |
| } |
| private char[] utf8At(int readOffset, int bytesAvailable, byte[] bytes) { |
| int x, y, z; |
| int length = bytesAvailable; |
| char outputBuf[] = new char[bytesAvailable]; |
| int outputPos = 0; |
| while (length != 0) { |
| x = bytes[readOffset++] & 0xFF; |
| length--; |
| if ((0x80 & x) != 0) { |
| y = bytes[readOffset++] & 0xFF; |
| length--; |
| if ((x & 0x20) != 0) { |
| z = bytes[readOffset++] & 0xFF; |
| length--; |
| x = ((x & 0x1F) << 12) + ((y & 0x3F) << 6) + (z & 0x3F); |
| } else { |
| x = ((x & 0x1F) << 6) + (y & 0x3F); |
| } |
| } |
| outputBuf[outputPos++] = (char) x; |
| } |
| |
| if (outputPos != bytesAvailable) { |
| System.arraycopy(outputBuf, 0, (outputBuf = new char[outputPos]), 0, outputPos); |
| } |
| return outputBuf; |
| } |
| /** |
| * Writes the given class definition on disk. The give name is the forward slash separated |
| * fully qualified name of the class. |
| */ |
| private void writeClassOnDisk(String className, byte[] classDefinition) { |
| try { |
| String fileName = className.replace('/', File.separatorChar) + ".class"; |
| File classFile = new File( |
| (this.bootclassPathDirectory != null && |
| (className.startsWith("java") || className.replace('/', '.').equals(CODE_SNIPPET_CLASS_NAME))) ? |
| this.bootclassPathDirectory : |
| this.classPathDirectory, fileName); |
| File parent = new File(classFile.getParent()); |
| parent.mkdirs(); |
| if (!parent.exists()) { |
| throw new IOException("Could not create directory " + parent.getPath()); |
| } |
| FileOutputStream out = null; |
| try { |
| out = new FileOutputStream(classFile); |
| out.write(classDefinition); |
| } finally { |
| if (out != null) { |
| out.close(); |
| } |
| } |
| } catch (IOException e) { |
| e.printStackTrace(); |
| } |
| } |
| } |