/******************************************************************************* * Copyright (c) 2010, 2013 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 *******************************************************************************/ package org.eclipse.jdt.internal.launching; import java.io.ByteArrayInputStream; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunchManager; import org.eclipse.debug.core.Launch; import org.eclipse.debug.core.model.IProcess; import org.eclipse.debug.core.model.IStreamsProxy; import org.eclipse.jdt.launching.AbstractVMInstallType; import org.eclipse.jdt.launching.IVMInstallType; import org.eclipse.jdt.launching.JavaRuntime; import org.eclipse.jdt.launching.VMStandin; import org.eclipse.osgi.util.NLS; /** * Searches for installed JREs on the Mac. */ public class MacInstalledJREs { /** The executable for 'java_home' */ private static final String JAVA_HOME_PLIST = "/usr/libexec/java_home"; //$NON-NLS-1$ /** The plist attribute describing the JRE home directory */ private static final String PLIST_JVM_HOME_PATH = "JVMHomePath"; //$NON-NLS-1$ /** The plist attribute describing the JRE name */ private static final String PLIST_JVM_NAME = "JVMName"; //$NON-NLS-1$ /** The plist attribute describing the JRE version */ private static final String PLIST_JVM_VERSION = "JVMVersion"; //$NON-NLS-1$ /** * The plist attribute describing the bundle id of the VM * @since 3.8 */ private static final String PLIST_JVM_BUNDLE_ID = "JVMBundleID"; //$NON-NLS-1$ public static final VMStandin[] NO_VMS = new VMStandin[0]; /** * Custom stand-in that allows us to provide a version * @since 3.7.0 */ public static class MacVMStandin extends VMStandin { String version = null; public MacVMStandin(IVMInstallType type, File location, String name, String version, String id) { super(type, id); setInstallLocation(location); setName(name); this.version = version; } @Override public String getJavaVersion() { return version; } } /** * Parses the XML output produced from "java_home -X" (see bug 325777), and return a collection * of descriptions of JRE installations. * * @param monitor the {@link IProgressMonitor} or <code>null</code> * @return array of {@link VMStandin}s installed in the OS * @exception CoreException if unable to parse the output or the executable does not exist */ public static VMStandin[] getInstalledJREs(IProgressMonitor monitor) throws CoreException { SubMonitor smonitor = SubMonitor.convert(monitor); try { // locate the "java_home" executable File java_home = new File(JAVA_HOME_PLIST); if (!java_home.exists()) { throw new CoreException(new Status(IStatus.WARNING, LaunchingPlugin.getUniqueIdentifier(), "The java_home executable does not exist")); //$NON-NLS-1$ } String[] cmdLine = new String[] {JAVA_HOME_PLIST, "-X"}; //$NON-NLS-1$ Process p = null; try { p = DebugPlugin.exec(cmdLine, null); IProcess process = DebugPlugin.newProcess(new Launch(null, ILaunchManager.RUN_MODE, null), p, "JRE Install Detection"); //$NON-NLS-1$ for (int i= 0; i < 600; i++) { // Wait no more than 30 seconds (600 * 50 milliseconds) if (process.isTerminated()) { break; } try { Thread.sleep(50); } catch (InterruptedException e) { // do nothing } } return parseJREInfo(process, monitor); } finally { if (p != null) { p.destroy(); } } } finally { if(!smonitor.isCanceled()) { smonitor.done(); } } } /** * Parses the output from 'java_home -X'. * * @param process process with output from 'java_home -X' * @param the {@link IProgressMonitor} or <code>null</code> * @return array JRE descriptions installed in the OS * @exception CoreException if unable to parse the output */ private static VMStandin[] parseJREInfo(IProcess process, IProgressMonitor monitor) throws CoreException { IStreamsProxy streamsProxy = process.getStreamsProxy(); String text = null; if (streamsProxy != null) { text = streamsProxy.getOutputStreamMonitor().getContents(); } if (text != null && text.length() > 0) { ByteArrayInputStream stream = new ByteArrayInputStream(text.getBytes()); return parseJREInfo(stream, monitor); } return NO_VMS; } /** * Parse {@link JREDescriptor}s from the given input stream. The stream is expected to be in the * XML properties format. * * @param monitor the {@link IProgressMonitor} or <code>null</code> * @param stream * @return the array of {@link VMStandin}s or an empty array never <code>null</code> * @since 3.8 */ public static VMStandin[] parseJREInfo(InputStream stream, IProgressMonitor monitor) { SubMonitor smonitor = SubMonitor.convert(monitor, LaunchingMessages.MacInstalledJREs_0, 10); try { Object result = new PListParser().parse(stream); if (result instanceof Object[]) { Object[] maps = (Object[]) result; smonitor.setWorkRemaining(maps.length); List<VMStandin> jres= new ArrayList<>(); AbstractVMInstallType mactype = (AbstractVMInstallType) JavaRuntime.getVMInstallType("org.eclipse.jdt.internal.launching.macosx.MacOSXType"); //$NON-NLS-1$ if(mactype != null) { for (int i = 0; i < maps.length; i++) { if(smonitor.isCanceled()) { ///stop processing and return what we found return jres.toArray(new VMStandin[jres.size()]); } Object object = maps[i]; if (object instanceof Map) { Map<?, ?> map = (Map<?, ?>) object; Object home = map.get(PLIST_JVM_HOME_PATH); Object name = map.get(PLIST_JVM_NAME); Object version = map.get(PLIST_JVM_VERSION); if (home instanceof String && name instanceof String && version instanceof String) { smonitor.setTaskName(NLS.bind(LaunchingMessages.MacInstalledJREs_1, new String[] {(String) name, (String) version})); String ver = (String) version; File loc = new File((String)home); //10.8.2+ can have more than one of the same VM, which will have the same name //augment it with the version to make it easier to distinguish StringBuffer namebuff = new StringBuffer(name.toString()); namebuff.append(" [").append(ver).append("]"); //$NON-NLS-1$//$NON-NLS-2$ MacVMStandin vm = new MacVMStandin(mactype, loc, namebuff.toString(), ver, computeId(map, ver)); vm.setJavadocLocation(mactype.getDefaultJavadocLocation(loc)); vm.setLibraryLocations(mactype.getDefaultLibraryLocations(loc)); vm.setVMArgs(mactype.getDefaultVMArguments(loc)); if (!jres.contains(vm)) { // remove duplicates jres.add(vm); } } } smonitor.worked(1); } } return jres.toArray(new VMStandin[jres.size()]); } } catch (CoreException ce) { LaunchingPlugin.log(ce); } finally { smonitor.done(); } return NO_VMS; } /** * Tries to compute the descriptor id using the {@link #PLIST_JVM_BUNDLE_ID}. If that is not defined * we fall back to using the version. * @param map the map to look up the VM bundle version in * @param version the current version - fall-back for no VM bundle id defined * @return the id to use for the {@link JREDescriptor} * @since 3.8 */ static String computeId(Map<?, ?> map, String version) { Object o = map.get(PLIST_JVM_BUNDLE_ID); if(o instanceof String) { return (String) o; } return version; } }