| /******************************************************************************* |
| * Copyright (c) 2000, 2014 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Nina Rinskaya |
| * Fix for https://bugs.eclipse.org/bugs/show_bug.cgi?id=172820. |
| *******************************************************************************/ |
| package org.eclipse.jdt.core.tests.runtime; |
| |
| import java.io.*; |
| import java.util.*; |
| |
| import org.eclipse.jdt.core.tests.util.Util; |
| |
| /** |
| * The root of the VM launchers that launch VMs on the same machine. |
| * <p> |
| * A local VM launcher has the following limitations: |
| * <ul> |
| * <li>It can only retrieve the running virtual machines that it has launched. So if |
| * a client is using 2 instances of <code>LocalVMLauncher</code>, each of these |
| * instances will be able to retrieve only a part of the running VMs. |
| * </ul> |
| */ |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| public abstract class LocalVMLauncher implements RuntimeConstants { |
| |
| /** |
| * Whether the target has a file system and thus whether it supports writing |
| * class files to disk. See org.eclipse.jdt.core.tests.eval.target.CodeSnippetRunner for more |
| * information. |
| */ |
| public static final boolean TARGET_HAS_FILE_SYSTEM = true; |
| public static final String REGULAR_CLASSPATH_DIRECTORY = "regularPath"; |
| public static final String BOOT_CLASSPATH_DIRECTORY = "bootPath"; |
| |
| protected String[] bootPath; |
| protected String[] classPath; |
| protected int debugPort = -1; |
| protected int evalPort = -1; |
| protected String evalTargetPath; |
| protected String[] programArguments; |
| protected String programClass; |
| protected Vector runningVMs = new Vector(); // a Vector of LocalVirtualMachine |
| protected String[] vmArguments; |
| protected String vmPath; |
| |
| /** |
| * Returns a launcher that will launch the same kind of VM that is currently running |
| */ |
| public static LocalVMLauncher getLauncher() { |
| final String vmName = System.getProperty("java.vm.name"); |
| if ("J9".equals(vmName)) { |
| return new J9VMLauncher(); |
| } |
| if (vmName != null && vmName.indexOf("JRockit") != -1) { |
| return new JRockitVMLauncher(); |
| } |
| final String osName = System.getProperty("os.name"); |
| if (osName.startsWith("Mac")) { |
| return new MacVMLauncher(); |
| } |
| File file = null; |
| String javaVersion = System.getProperty("java.version"); |
| if (javaVersion != null && javaVersion.length() > 0 && javaVersion.charAt(0) == '9') { |
| file = new File(Util.getJREDirectory() + "/jrt-fs.jar"); |
| } else { |
| new File(Util.getJREDirectory() + "/lib/rt.jar"); |
| } |
| if (file.exists()) { |
| return new StandardVMLauncher(); |
| } |
| if ("IBM J9SE VM".equals(vmName)) { |
| return new SideCarJ9VMLauncher(); |
| } |
| if ("DRLVM".equals(vmName)) { |
| return new DRLVMLauncher(); |
| } |
| return new SideCarVMLauncher(); |
| } |
| /** |
| * Builds the actual class path that is going to be passed to the VM. |
| */ |
| protected String buildClassPath() { |
| StringBuffer classPathString = new StringBuffer(); |
| char pathSeparator = File.pathSeparatorChar; |
| |
| // Add jar support if in evaluation mode |
| if (this.evalPort != -1) { |
| classPathString.append(new File(this.evalTargetPath, SUPPORT_ZIP_FILE_NAME).getPath()); |
| classPathString.append(pathSeparator); |
| } |
| |
| // Add class path given by client |
| if (this.classPath != null) { |
| int length = this.classPath.length; |
| for (int i = 0; i < length; i++){ |
| classPathString.append(this.classPath[i]); |
| classPathString.append(pathSeparator); |
| } |
| } |
| |
| // Add regular classpath directory if needed |
| if (this.evalPort != -1 && TARGET_HAS_FILE_SYSTEM) { |
| classPathString.append(this.evalTargetPath); |
| classPathString.append(File.separatorChar); |
| classPathString.append(REGULAR_CLASSPATH_DIRECTORY); |
| } |
| |
| return classPathString.toString(); |
| } |
| /** |
| * Launches the VM by exec'ing the command line and returns the resulting Process. |
| */ |
| protected Process execCommandLine() throws TargetException { |
| // Check that the VM path has been specified |
| if (this.vmPath == null) { |
| throw new TargetException("Path to the VM has not been specified"); |
| } |
| |
| // Check that the program class has been specified if not in evaluation mode |
| if ((this.programClass == null) && (this.evalPort == -1)) { |
| throw new TargetException("Program class has not been specified"); |
| } |
| |
| // Launch VM |
| Process vmProcess= null; |
| try { |
| // Use Runtime.exec(String[]) with tokens because Runtime.exec(String) with commandLineString |
| // does not properly handle spaces in arguments on Unix/Linux platforms. |
| String[] commandLine = getCommandLine(); |
| |
| // DEBUG |
| /*for (int i = 0; i < commandLine.length; i++) { |
| System.out.print(commandLine[i] + " "); |
| } |
| System.out.println(); |
| */ |
| System.out.println("Command line: "); |
| for (String string : commandLine) { |
| System.out.println(string); |
| } |
| vmProcess= Runtime.getRuntime().exec(commandLine); |
| } catch (IOException e) { |
| throw new TargetException("Error launching VM at " + this.vmPath); |
| } |
| return vmProcess; |
| } |
| /** |
| * Returns the boot class path used when a VM is launched. |
| */ |
| public String[] getBootClassPath() { |
| return this.bootPath; |
| } |
| /** |
| * Returns the class path used when a VM is launched. |
| */ |
| public String[] getClassPath() { |
| return this.classPath; |
| } |
| /** |
| * Returns the command line which will be used to launch the VM. |
| * The segments are in the following order: |
| * <p><ul> |
| * <li> VM path, |
| * <li> VM arguments, |
| * <li> the class path, |
| * <li> the program class |
| * <li> the program arguments |
| * </ul> |
| */ |
| public abstract String[] getCommandLine(); |
| /** |
| * Returns the debug port, or -1 if debug mode is disabled. |
| * The default is -1. |
| */ |
| public int getDebugPort() { |
| return this.debugPort; |
| } |
| /** |
| * Returns the evaluation port for evaluation support. |
| * The default is -1, indicating no evaluation support. |
| * |
| * @see #setEvalPort(int) |
| */ |
| public int getEvalPort() { |
| return this.evalPort; |
| } |
| /** |
| * Returns the evaluation target path for evaluation support. |
| * |
| * @see #setEvalTargetPath(String) |
| */ |
| public String getEvalTargetPath() { |
| return this.evalTargetPath; |
| } |
| /** |
| * Returns the arguments passed to the program class. |
| * Returns null if the VM is being launched for evaluation support only. |
| */ |
| public String[] getProgramArguments() { |
| if (this.evalPort != -1) { |
| return null; |
| } |
| return this.programArguments; |
| } |
| /** |
| * Returns the dot-separated, fully qualified name of the class to run. |
| * It must implement main(String[] args). |
| * Returns null if the VM is being launched for evaluation support only. |
| */ |
| public String getProgramClass() { |
| if (this.evalPort != -1) { |
| return null; |
| } |
| return this.programClass; |
| } |
| /** |
| * Returns all the target VMs that are running at this launcher's target |
| * address. |
| * Note that these target VMs may or may not have been launched by this |
| * launcher. |
| * Note also that if the list of running VMs doesn't change on the target, |
| * two calls to this method return VMs that are equal. |
| * |
| * @return the list of running target VMs |
| */ |
| public LocalVirtualMachine[] getRunningVirtualMachines() { |
| // Select the VMs that are actually running |
| Vector actuallyRunning = new Vector(); |
| Enumeration en = this.runningVMs.elements(); |
| while (en.hasMoreElements()) { |
| LocalVirtualMachine vm = (LocalVirtualMachine)en.nextElement(); |
| if (vm.isRunning()) |
| actuallyRunning.addElement(vm); |
| } |
| this.runningVMs = actuallyRunning; |
| |
| // Return the running VMs |
| int size = actuallyRunning.size(); |
| LocalVirtualMachine[] result = new LocalVirtualMachine[size]; |
| for (int i=0; i<size; i++) |
| result[i] = (LocalVirtualMachine)actuallyRunning.elementAt(i); |
| return result; |
| } |
| /** |
| * Returns the address of the target where this launcher runs the target VMs. The format |
| * of this address is transport specific. |
| * For example, a VM launcher using a TCP/IP transport returns target addresses looking like: |
| * <code>"localhost:2010"</code>, or <code>"joe.ibm.com"</code>. |
| * |
| * @return transport specific address of the target |
| */ |
| public String getTargetAddress() { |
| return "localhost"; |
| } |
| /** |
| * Returns the VM-specific arguments. This does not include: |
| * <p><ul> |
| * <li>the VM path |
| * <li>the class path or the boot class path |
| * <li>the program class or program arguments |
| * </ul> |
| */ |
| public String[] getVMArguments() { |
| return this.vmArguments; |
| } |
| /** |
| * Returns the path on disk of the VM to launch. |
| */ |
| public String getVMPath() { |
| return this.vmPath; |
| } |
| /** |
| * Initializes this context's target path by copying the jar file for the code snippet support |
| * and by creating the 2 directories that will contain the code snippet classes (see TARGET_HAS_FILE_SYSTEM). |
| * Add the code snipport root class to the boot path directory so that code snippets can be run in |
| * java.* packages |
| * |
| * @throws TargetException if the path could not be initialized with the code snippet support |
| */ |
| protected void initTargetPath() throws TargetException { |
| // create directories |
| File directory = new File(this.evalTargetPath); |
| directory.mkdirs(); |
| if (!directory.exists()) { |
| throw new TargetException("Could not create directory " + this.evalTargetPath); |
| } |
| if (TARGET_HAS_FILE_SYSTEM) { |
| File classesDirectory = new File(directory, REGULAR_CLASSPATH_DIRECTORY); |
| classesDirectory.mkdir(); |
| if (!classesDirectory.exists()) { |
| throw new TargetException("Could not create directory " + classesDirectory.getPath()); |
| } |
| File bootDirectory = new File(directory, BOOT_CLASSPATH_DIRECTORY); |
| bootDirectory.mkdir(); |
| if (!bootDirectory.exists()) { |
| throw new TargetException("Could not create directory " + bootDirectory.getPath()); |
| } |
| /* |
| // add the code snippet root class to the boot path directory |
| InputStream in = null; |
| try { |
| in = EvaluationContext.class.getResourceAsStream("/" + SUPPORT_ZIP_FILE_NAME); |
| ZipInputStream zip = new ZipInputStream(in); |
| String rootClassFileName = ROOT_FULL_CLASS_NAME.replace('.', '/') + ".class"; |
| while (true) { |
| ZipEntry entry = zip.getNextEntry(); |
| if (entry.getName().equals(rootClassFileName)) { |
| // read root class file contents |
| int size = (int)entry.getSize(); |
| byte[] buffer = new byte[size]; |
| int totalRead = 0; |
| int read = 0; |
| while (totalRead < size) { |
| read = zip.read(buffer, totalRead, size - totalRead); |
| if (read != -1) { |
| totalRead += read; |
| } |
| } |
| // write root class file contents |
| FileOutputStream out = null; |
| try { |
| File rootFile = new File(bootDirectory, rootClassFileName.replace('/', File.separatorChar)); |
| File parent = new File(rootFile.getParent()); |
| parent.mkdirs(); |
| out = new FileOutputStream(rootFile); |
| out.write(buffer); |
| out.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| if (out != null) { |
| try { |
| out.close(); |
| } catch (IOException e2) { |
| } |
| } |
| } |
| break; |
| } |
| } |
| in.close(); |
| } catch (IOException e) { |
| e.printStackTrace(); |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e2) { |
| } |
| } |
| }*/ |
| } |
| |
| // copy jar file |
| InputStream in = null; |
| FileOutputStream out = null; |
| try { |
| in = getClass().getResourceAsStream("/" + SUPPORT_ZIP_FILE_NAME); |
| if (in == null) { |
| throw new TargetException("Could not find resource /" + SUPPORT_ZIP_FILE_NAME); |
| } |
| int bufferLength = 1024; |
| byte[] buffer = new byte[bufferLength]; |
| File file = new File(directory, SUPPORT_ZIP_FILE_NAME); |
| out = new FileOutputStream(file); |
| int read = 0; |
| while (read != -1) { |
| read = in.read(buffer, 0, bufferLength); |
| if (read != -1) { |
| out.write(buffer, 0, read); |
| } |
| } |
| } catch (IOException e) { |
| throw new TargetException("IOException while copying " + SUPPORT_ZIP_FILE_NAME + ": " + e.getMessage()); |
| } finally { |
| if (in != null) { |
| try { |
| in.close(); |
| } catch (IOException e) { |
| } |
| } |
| if (out != null) { |
| try { |
| out.close(); |
| } catch (IOException e) { |
| } |
| } |
| } |
| } |
| /** |
| * Launches a new target VM with the registered arguments. |
| * This operation returns once a new target VM has been launched. |
| * |
| * @exception TargetException if the target VM could not be launched. |
| */ |
| public LocalVirtualMachine launch() throws TargetException { |
| // evaluation mode |
| if (this.evalTargetPath != null) { |
| // init target path |
| initTargetPath(); |
| } |
| |
| // launch VM |
| LocalVirtualMachine vm; |
| Process p = execCommandLine(); |
| vm = new LocalVirtualMachine(p, this.debugPort, this.evalTargetPath); |
| |
| // TBD: Start reading VM stdout and stderr right away otherwise this may prevent the connection |
| // from happening. |
| |
| // add VM to list of known running VMs |
| this.runningVMs.addElement(vm); |
| return vm; |
| } |
| /** |
| * Sets the boot class path used when a VM is launched. |
| */ |
| public void setBootClassPath(java.lang.String[] bootClassPath) { |
| this.bootPath = bootClassPath; |
| } |
| /** |
| * Sets the class path used when a VM is launched. |
| */ |
| public void setClassPath(String[] classPath) { |
| this.classPath = classPath; |
| } |
| /** |
| * Sets the debug port to use for debug support. |
| * Specify -1 to disable debug mode. |
| */ |
| public void setDebugPort(int debugPort) { |
| this.debugPort = debugPort; |
| } |
| /** |
| * Sets the evaluation port to use for evaluation support. |
| * Setting the port enables evaluation support. |
| * Specify null to disable evaluation support. |
| */ |
| public void setEvalPort(int evalPort) { |
| this.evalPort = evalPort; |
| } |
| /** |
| * Sets the evaluation target path to use for evaluation support. |
| */ |
| public void setEvalTargetPath(String evalTargetPath) { |
| this.evalTargetPath = evalTargetPath; |
| } |
| /** |
| * Sets the arguments passed to the program class. |
| * This is ignored if the VM is being launched for evaluation support only. |
| */ |
| public void setProgramArguments(String[] args) { |
| this.programArguments = args; |
| } |
| /** |
| * Sets the dot-separated, fully qualified name of the class to run. |
| * It must implement main(String[] args). |
| * This is ignored if the VM is being launched for evaluation support only. |
| */ |
| public void setProgramClass(String programClass) { |
| this.programClass = programClass; |
| } |
| /** |
| * Sets the VM-specific arguments. This does not include: |
| * <p><ul> |
| * <li>the VM path |
| * <li>the class path or the boot class path |
| * <li>the program class or program arguments |
| * </ul> |
| */ |
| public void setVMArguments(String[] args) { |
| this.vmArguments = args; |
| } |
| /** |
| * Sets the path on disk of the VM to launch. |
| */ |
| public void setVMPath(String vmPath) { |
| this.vmPath = vmPath; |
| } |
| } |