| /******************************************************************************* |
| * Copyright (c) 2000, 2020 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * 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.JavaCore; |
| 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> |
| */ |
| public abstract class LocalVMLauncher implements RuntimeConstants { |
| |
| private static final boolean PWR_DEBUG = Boolean.getBoolean("ot.debug.pwr"); |
| |
| static final String[] env = System.getenv().entrySet().stream() |
| .filter(e -> !"JAVA_TOOL_OPTIONS".equals(e.getKey())) |
| .map(e -> e.getKey() + "=" + e.getValue()) |
| .toArray(String[]::new); |
| |
| /** |
| * 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 List<LocalVirtualMachine> runningVMs = new ArrayList<>(); // 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(); |
| } |
| //{ObjectTeams: see https://bugs.eclipse.org/bugs/show_bug.cgi?id=413850 |
| // final String osName = System.getProperty("os.name"); |
| // if (osName.startsWith("Mac")) { |
| // return new MacVMLauncher(); |
| // } |
| // SH} |
| String javaVersion = System.getProperty("java.version"); |
| boolean isJrt = javaVersion != null && javaVersion.length() > 0 && JavaCore.compareJavaVersions(javaVersion, "9") >= 0; |
| File file = new File(Util.getJREDirectory() + (isJrt ? "/lib/jrt-fs.jar" : "/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(); |
| if (PWR_DEBUG) System.out.println("commandline: "+String.join(" ", commandLine)); |
| vmProcess= Runtime.getRuntime().exec(commandLine, env); |
| } 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 |
| List<LocalVirtualMachine> actuallyRunning = new ArrayList<>(); |
| for (LocalVirtualMachine vm : this.runningVMs) { |
| if (vm.isRunning()) |
| actuallyRunning.add(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] = actuallyRunning.get(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.add(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; |
| } |
| } |