Bug 325777 - Eclipse JRE/JDK detection on Mac OS X should detect .jdk bundles
diff --git a/org.eclipse.jdt.debug.tests/testresources/plist.xml b/org.eclipse.jdt.debug.tests/testresources/plist.xml
new file mode 100644
index 0000000..bb77364
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/testresources/plist.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<array>
+ <dict>
+ <key>JVMArch</key>
+ <string>i386</string>
+ <key>JVMBundleID</key>
+ <string>com.apple.javajdk15</string>
+ <key>JVMEnabled</key>
+ <true/>
+ <key>JVMHomePath</key>
+ <string>/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home</string>
+ <key>JVMIsBuiltIn</key>
+ <true/>
+ <key>JVMName</key>
+ <string>J2SE 5.0</string>
+ <key>JVMPlatformVersion</key>
+ <string>1.5</string>
+ <key>JVMVersion</key>
+ <string>1.5.0_24</string>
+ <key>test</key>
+ <false/>
+ <key>testint</key>
+ <integer>42</integer>
+ </dict>
+ <dict>
+ <key>JVMArch</key>
+ <string>x86_64</string>
+ <key>JVMBundleID</key>
+ <string>com.apple.javajdk16</string>
+ <key>JVMEnabled</key>
+ <true/>
+ <key>JVMHomePath</key>
+ <string>/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home</string>
+ <key>JVMIsBuiltIn</key>
+ <true/>
+ <key>JVMName</key>
+ <string>Java SE 6</string>
+ <key>JVMPlatformVersion</key>
+ <string>1.6</string>
+ <key>JVMVersion</key>
+ <string>1.6.0_20</string>
+ </dict>
+ <dict>
+ <key>JVMArch</key>
+ <string>x86_64</string>
+ <key>JVMBundleID</key>
+ <string>com.apple.javajdk15</string>
+ <key>JVMEnabled</key>
+ <true/>
+ <key>JVMHomePath</key>
+ <string>/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home</string>
+ <key>JVMIsBuiltIn</key>
+ <true/>
+ <key>JVMName</key>
+ <string>J2SE 5.0</string>
+ <key>JVMPlatformVersion</key>
+ <string>1.5</string>
+ <key>JVMVersion</key>
+ <string>1.5.0_24</string>
+ </dict>
+</array>
+</plist>
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
index 33c739a..ed17e79 100644
--- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AutomatedSuite.java
@@ -79,6 +79,7 @@
import org.eclipse.jdt.debug.tests.launching.LaunchTests;
import org.eclipse.jdt.debug.tests.launching.LaunchesTests;
import org.eclipse.jdt.debug.tests.launching.MigrationDelegateTests;
+import org.eclipse.jdt.debug.tests.launching.PListParserTests;
import org.eclipse.jdt.debug.tests.launching.ProjectClasspathVariableTests;
import org.eclipse.jdt.debug.tests.launching.TabGroupWrapperTests;
import org.eclipse.jdt.debug.tests.refactoring.MoveCompilationUnitTests;
@@ -142,6 +143,7 @@
addTest(new TestSuite(ConfigurationEncodingTests.class));
addTest(new TestSuite(LaunchConfigurationManagerTests.class));
addTest(new TestSuite(ProjectClasspathVariableTests.class));
+ addTest(new TestSuite(PListParserTests.class));
//Breakpoints tests
addTest(new TestSuite(DeferredBreakpointTests.class));
diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/PListParserTests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/PListParserTests.java
new file mode 100644
index 0000000..38848d2
--- /dev/null
+++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/launching/PListParserTests.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.debug.tests.launching;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.HashMap;
+
+import org.eclipse.core.runtime.Path;
+import org.eclipse.jdt.debug.testplugin.JavaTestPlugin;
+import org.eclipse.jdt.debug.tests.AbstractDebugTest;
+import org.eclipse.jdt.internal.launching.PListParser;
+
+/**
+ * Tests the PList Parser.
+ */
+public class PListParserTests extends AbstractDebugTest {
+
+ /**
+ * Constructs a test
+ *
+ * @param name test name
+ */
+ public PListParserTests(String name) {
+ super(name);
+ }
+
+ /**
+ * Tests parsing of a sample installed JREs plist from the Mac.
+ *
+ * @throws Exception
+ */
+ public void testParseJREs() throws Exception {
+ File file = JavaTestPlugin.getDefault().getFileInPlugin(new Path("testresources/plist.xml"));
+ assertNotNull(file);
+ assertEquals(true, file.exists());
+ Object obj = new PListParser().parse(new FileInputStream(file));
+ if (obj instanceof Object[]) {
+ Object[] jres = (Object[]) obj;
+ assertEquals("Should be 3 entries in the array", 3, jres.length);
+ // the first map
+ HashMap map = new HashMap();
+ map.put("JVMArch", "i386");
+ map.put("JVMBundleID", "com.apple.javajdk15");
+ map.put("JVMEnabled", Boolean.TRUE);
+ map.put("JVMHomePath", "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home");
+ map.put("JVMIsBuiltIn", Boolean.TRUE);
+ map.put("JVMName", "J2SE 5.0");
+ map.put("JVMPlatformVersion", "1.5");
+ map.put("JVMVersion", "1.5.0_24");
+ map.put("test", Boolean.FALSE);
+ map.put("testint", new Integer(42));
+ assertEquals("Incorrect values parsed", map, jres[0]);
+
+ map = new HashMap();
+ map.put("JVMArch", "x86_64");
+ map.put("JVMBundleID", "com.apple.javajdk16");
+ map.put("JVMEnabled", Boolean.TRUE);
+ map.put("JVMHomePath", "/System/Library/Frameworks/JavaVM.framework/Versions/1.6.0/Home");
+ map.put("JVMIsBuiltIn", Boolean.TRUE);
+ map.put("JVMName", "Java SE 6");
+ map.put("JVMPlatformVersion", "1.6");
+ map.put("JVMVersion", "1.6.0_20");
+ assertEquals("Incorrect values parsed", map, jres[1]);
+
+ map = new HashMap();
+ map.put("JVMArch", "x86_64");
+ map.put("JVMBundleID", "com.apple.javajdk15");
+ map.put("JVMEnabled", Boolean.TRUE);
+ map.put("JVMHomePath", "/System/Library/Frameworks/JavaVM.framework/Versions/1.5.0/Home");
+ map.put("JVMIsBuiltIn", Boolean.TRUE);
+ map.put("JVMName", "J2SE 5.0");
+ map.put("JVMPlatformVersion", "1.5");
+ map.put("JVMVersion", "1.5.0_24");
+ assertEquals("Incorrect values parsed", map, jres[2]);
+ } else {
+ assertTrue("Top level object should be an array", false);
+ }
+ }
+
+}
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java
index 1114c21..0f0710e 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/InstalledJREsBlock.java
@@ -770,7 +770,7 @@
private void doMacSearch() {
final List added = new ArrayList();
IRunnableWithProgress r = new IRunnableWithProgress() {
- public void run(IProgressMonitor monitor) {
+ public void run(IProgressMonitor monitor) throws InvocationTargetException {
Set exists = new HashSet();
Iterator iterator = fVMs.iterator();
while (iterator.hasNext()) {
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties
index 26325c0..999d6bb 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/JREMessages.properties
@@ -109,8 +109,8 @@
JREsEnvironmentLabelProvider_0={0} [{1}]
JREsEnvironmentLabelProvider_1=perfect match
MacVMSearch_0=Searching for installed JREs
-MacVMSearch_1=JVM {0} (MacOS X Default)
-MacVMSearch_2=JVM {0}
+MacVMSearch_1={0} (MacOS X Default)
+MacVMSearch_2={0}
EEVMPage_0=Add EE VM
EEVMPage_1=&Definition File:
EEVMPage_2=F&ile...
diff --git a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/MacVMSearch.java b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/MacVMSearch.java
index d4cc69b..c3f2796 100644
--- a/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/MacVMSearch.java
+++ b/org.eclipse.jdt.debug.ui/ui/org/eclipse/jdt/internal/debug/ui/jres/MacVMSearch.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * Copyright (c) 2007, 2010 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
@@ -11,14 +11,19 @@
package org.eclipse.jdt.internal.debug.ui.jres;
import java.io.File;
-import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
+import org.eclipse.jdt.internal.debug.ui.JDIDebugUIPlugin;
+import org.eclipse.jdt.internal.launching.MacInstalledJREs;
+import org.eclipse.jdt.internal.launching.MacInstalledJREs.JREDescriptor;
import org.eclipse.jdt.launching.AbstractVMInstallType;
import org.eclipse.jdt.launching.IVMInstallType;
import org.eclipse.jdt.launching.JavaRuntime;
@@ -28,13 +33,6 @@
* Searches for installed JREs on the MAC, in known location.
*/
public class MacVMSearch {
-
- /** The OS keeps all the JVM versions in this directory */
- private static final String JVM_VERSION_LOC= "/System/Library/Frameworks/JavaVM.framework/Versions/"; //$NON-NLS-1$
- /** The name of a Unix link to MacOS X's default VM */
- private static final String CURRENT_JVM= "CurrentJDK"; //$NON-NLS-1$
- /** The root of a JVM */
- private static final String JVM_ROOT= "Home"; //$NON-NLS-1$
/**
* Returns an array of {@link VMStandin}s found at the standard Mac OS location
@@ -43,53 +41,48 @@
* @return a listing of {@link VMStandin}s at the standard Mac OS location or an empty listing
*/
public VMStandin[] search(IProgressMonitor monitor) {
- List vms = new ArrayList();
+ JREDescriptor[] descriptors = null;
+ try {
+ descriptors = new MacInstalledJREs().getInstalledJREs();
+ } catch (CoreException e) {
+ JDIDebugUIPlugin.log(e.getStatus());
+ return new VMStandin[0];
+ }
+ SubMonitor localmonitor = SubMonitor.convert(monitor, JREMessages.MacVMSearch_0, descriptors.length);
IVMInstallType macVMType = JavaRuntime.getVMInstallType(InstalledJREsBlock.MACOSX_VM_TYPE_ID);
+ List vms = new ArrayList();
if (macVMType != null) {
- // find all installed VMs
- File versionDir= new File(JVM_VERSION_LOC);
- if (versionDir.exists() && versionDir.isDirectory()) {
- File currentJDK= new File(versionDir, CURRENT_JVM);
+ for (int i = 0; i < descriptors.length; i++) {
+ JREDescriptor descriptor = descriptors[i];
+ String name = descriptor.getName();
+ IPath path= new Path(descriptor.getHome().getAbsolutePath());
+ String id= path.segment(path.segmentCount() - 2); // ID is the second last segment in the install path (e.g. 1.5.0)
try {
- currentJDK= currentJDK.getCanonicalFile();
- } catch (IOException ex) {
- // NeedWork
- }
- File[] versions= versionDir.listFiles();
- SubMonitor localmonitor = SubMonitor.convert(monitor, JREMessages.MacVMSearch_0, versions.length);
- try {
- for (int i= 0; i < versions.length; i++) {
- String version= versions[i].getName();
- File home= new File(versions[i], JVM_ROOT);
- if (home.exists()) {
- boolean isDefault= currentJDK.equals(versions[i]);
- if (!CURRENT_JVM.equals(version)) {
- VMStandin vm= new VMStandin(macVMType, version);
- vm.setInstallLocation(home);
- String format= isDefault ? JREMessages.MacVMSearch_1 : JREMessages.MacVMSearch_2;
- vm.setName(MessageFormat.format(format, new Object[] { version } ));
- vm.setLibraryLocations(macVMType.getDefaultLibraryLocations(home));
- URL doc= ((AbstractVMInstallType)macVMType).getDefaultJavadocLocation(home);
- if (doc != null) {
- vm.setJavadocLocation(doc);
- }
- String arguments = ((AbstractVMInstallType)macVMType).getDefaultVMArguments(home);
- if (arguments != null) {
- vm.setVMArgs(arguments);
- }
- vms.add(vm);
- }
+ File home= descriptor.getHome();
+ if (home.exists()) {
+ boolean isDefault= i == 0;
+ VMStandin vm= new VMStandin(macVMType, id);
+ vm.setInstallLocation(home);
+ String format= isDefault ? JREMessages.MacVMSearch_1 : JREMessages.MacVMSearch_2;
+ vm.setName(MessageFormat.format(format, new Object[] { name } ));
+ vm.setLibraryLocations(macVMType.getDefaultLibraryLocations(home));
+ URL doc= ((AbstractVMInstallType)macVMType).getDefaultJavadocLocation(home);
+ if (doc != null) {
+ vm.setJavadocLocation(doc);
}
- if(localmonitor.isCanceled()) {
- break;
+ String arguments = ((AbstractVMInstallType)macVMType).getDefaultVMArguments(home);
+ if (arguments != null) {
+ vm.setVMArgs(arguments);
}
- localmonitor.worked(1);
+ vms.add(vm);
}
+ if(localmonitor.isCanceled()) {
+ break;
+ }
+ localmonitor.worked(1);
}
finally {
- if(localmonitor != null) {
- localmonitor.done();
- }
+ localmonitor.done();
}
}
}
diff --git a/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXLauncherMessages.properties b/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXLauncherMessages.properties
index 0dbf90c..eac71b9 100644
--- a/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXLauncherMessages.properties
+++ b/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXLauncherMessages.properties
@@ -10,8 +10,8 @@
# Ralf Ebert ralf@ralfebert.de - fix for #307109
###############################################################################
-MacOSXVMType.jvmName= JVM {0}
-MacOSXVMType.jvmDefaultName= JVM {0} (MacOS X Default)
+MacOSXVMType.jvmName= {0}
+MacOSXVMType.jvmDefaultName= {0} (MacOS X Default)
MacOSXVMType.error.notRoot=Not a JDK Root; MacOS X executable was not found
MacOSXVMType.name=MacOS X VM
MacOSXVMType.error.jvmDirectoryNotFound=Java VM default location "{0}" not found, JVMs were not scanned.
diff --git a/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXVMInstallType.java b/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXVMInstallType.java
index 0148213..a7f8066 100644
--- a/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXVMInstallType.java
+++ b/org.eclipse.jdt.launching.macosx/macosx/org/eclipse/jdt/internal/launching/macosx/MacOSXVMInstallType.java
@@ -25,6 +25,8 @@
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.internal.launching.LaunchingPlugin;
import org.eclipse.jdt.internal.launching.LibraryInfo;
+import org.eclipse.jdt.internal.launching.MacInstalledJREs;
+import org.eclipse.jdt.internal.launching.MacInstalledJREs.JREDescriptor;
import org.eclipse.jdt.internal.launching.StandardVMType;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.JavaRuntime;
@@ -67,7 +69,6 @@
private static final String JAVADOC_LOC= "/Developer/Documentation/Java/Reference/"; //$NON-NLS-1$
/** The doc for 1.4.1 is kept in a sub directory of the above. */
private static final String JAVADOC_SUBDIR= "/doc/api"; //$NON-NLS-1$
-
public String getName() {
return MacOSXLaunchingPlugin.getString("MacOSXVMType.name"); //$NON-NLS-1$
@@ -81,6 +82,55 @@
* @see IVMInstallType#detectInstallLocation()
*/
public File detectInstallLocation() {
+ try {
+ // find all installed VMs
+ File defaultLocation= null;
+ JREDescriptor[] jres= new MacInstalledJREs().getInstalledJREs();
+ for (int i= 0; i < jres.length; i++) {
+ JREDescriptor descripor = jres[i];
+ String name = jres[i].getName();
+ File home= descripor.getHome();
+ IPath path= new Path(home.getAbsolutePath());
+ String id= path.segment(path.segmentCount() - 2); // ID is the second last segment in the install path (e.g. 1.5.0)
+ if (home.exists()) {
+ boolean isDefault= i == 0;
+ IVMInstall install= findVMInstall(id);
+ if (install == null) {
+ VMStandin vm= new VMStandin(this, id);
+ vm.setInstallLocation(home);
+ String format= MacOSXLaunchingPlugin.getString(isDefault
+ ? "MacOSXVMType.jvmDefaultName" //$NON-NLS-1$
+ : "MacOSXVMType.jvmName"); //$NON-NLS-1$
+ vm.setName(MessageFormat.format(format, new Object[] { name } ));
+ vm.setLibraryLocations(getDefaultLibraryLocations(home));
+ vm.setJavadocLocation(getDefaultJavadocLocation(home));
+
+ install= vm.convertToRealVM();
+ }
+ if (isDefault) {
+ defaultLocation= home;
+ try {
+ JavaRuntime.setDefaultVMInstall(install, null);
+ } catch (CoreException e) {
+ LaunchingPlugin.log(e);
+ }
+ }
+ }
+ }
+ return defaultLocation;
+ } catch (CoreException e) {
+ MacOSXLaunchingPlugin.getDefault().getLog().log(e.getStatus());
+ return detectInstallLocationOld();
+ }
+ }
+
+ /**
+ * The proper way to find installed JREs is to parse the XML output produced from "java_home -X"
+ * (see bug 325777). However, if that fails, revert to the hard coded search.
+ *
+ * @return file that points to the default JRE install
+ */
+ private File detectInstallLocationOld() {
String javaVMName= System.getProperty("java.vm.name"); //$NON-NLS-1$
if (javaVMName == null) {
@@ -95,8 +145,8 @@
// find all installed VMs
File defaultLocation= null;
- File[] versions= getAllVersions();
- File currentJDK= getCurrentJDK();
+ File[] versions= getAllVersionsOld();
+ File currentJDK= getCurrentJDKOld();
for (int i= 0; i < versions.length; i++) {
File versionFile= versions[i];
String version= versionFile.getName();
@@ -128,8 +178,14 @@
}
return defaultLocation;
}
-
- private File[] getAllVersions() {
+
+ /**
+ * The proper way to find installed JREs is to parse the XML output produced from "java_home -X"
+ * (see bug 325777). However, if that fails, revert to the hard coded search.
+ *
+ * @return array of files that point to JRE install directories
+ */
+ private File[] getAllVersionsOld() {
File[] versionFiles= JVM_VERSIONS_FOLDER.listFiles();
for (int i= 0; i < versionFiles.length; i++) {
versionFiles[i]= resolveSymbolicLinks(versionFiles[i]);
@@ -137,7 +193,13 @@
return versionFiles;
}
- private File getCurrentJDK() {
+ /**
+ * The proper way to find the default JRE is to parse the XML output produced from "java_home -X"
+ * and take the first entry in the list. However, if that fails, revert to the hard coded search.
+ *
+ * @return a file that points to the default JRE install directory
+ */
+ private File getCurrentJDKOld() {
return resolveSymbolicLinks(new File(JVM_VERSIONS_FOLDER, CURRENT_JDK));
}
@@ -255,4 +317,5 @@
protected String getVMVersion(File javaHome, File javaExecutable) {
return super.getVMVersion(javaHome, javaExecutable);
}
+
}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/MacInstalledJREs.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/MacInstalledJREs.java
new file mode 100644
index 0000000..4ba5b40
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/MacInstalledJREs.java
@@ -0,0 +1,190 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+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;
+
+/**
+ * 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$
+
+ /**
+ * Describes an installed JRE on MacOS
+ */
+ public class JREDescriptor {
+
+ String fName;
+ File fHome;
+ String fVersion;
+
+ /**
+ * Constructs a new JRE descriptor
+ *
+ * @param home Home directory of the JRE
+ * @param name JRE name
+ * @param version JRE version
+ */
+ public JREDescriptor(File home, String name, String version) {
+ fHome = home;
+ fName = name;
+ fVersion = version;
+ }
+
+ /**
+ * Returns the home installation directory for this JRE.
+ *
+ * @return home directory
+ */
+ public File getHome() {
+ return fHome;
+ }
+
+ /**
+ * Returns the name of the JRE.
+ *
+ * @return JRE name
+ */
+ public String getName() {
+ return fName;
+ }
+
+ /**
+ * Returns the version of the JRE.
+ *
+ * @return JRE version
+ */
+ public String getVersion() {
+ return fVersion;
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof JREDescriptor) {
+ JREDescriptor jre = (JREDescriptor) obj;
+ return jre.fHome.equals(fHome) && jre.fName.equals(fName) && jre.fVersion.equals(fVersion);
+ }
+ return false;
+ }
+
+ public int hashCode() {
+ return fHome.hashCode() + fName.hashCode() + fVersion.hashCode();
+ }
+ }
+
+ /**
+ * Parses the XML output produced from "java_home -X" (see bug 325777), and return a collection
+ * of descriptions of JRE installations.
+ *
+ * @return array of JRE descriptions installed in the OS
+ * @exception CoreException if unable to parse the output or the executable does not exist
+ */
+ public JREDescriptor[] getInstalledJREs() throws CoreException {
+ // 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 ms)
+ if (process.isTerminated()) {
+ break;
+ }
+ try {
+ Thread.sleep(50);
+ } catch (InterruptedException e) {
+ // do nothing
+ }
+ }
+ return parseJREInfo(process);
+ } finally {
+ if (p != null) {
+ p.destroy();
+ }
+ }
+ }
+
+ /**
+ * Parses the output from 'java_home -X'.
+ *
+ * @param process process with output from 'java_home -X'
+ * @return array JRE descriptions installed in the OS
+ * @exception CoreException if unable to parse the output
+ */
+ private JREDescriptor[] parseJREInfo(IProcess process) 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());
+ Object result = new PListParser().parse(stream);
+ if (result instanceof Object[]) {
+ Object[] maps = (Object[]) result;
+ List jres= new ArrayList();
+ for (int i = 0; i < maps.length; i++) {
+ 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) {
+ JREDescriptor descriptor = new JREDescriptor(new File((String)home), (String)name, (String)version);
+ if (!jres.contains(descriptor)) { // remove duplicates
+ jres.add(descriptor);
+ }
+ } else {
+ unexpectedFormat();
+ }
+ } else {
+ unexpectedFormat();
+ }
+ }
+ return (JREDescriptor[]) jres.toArray(new JREDescriptor[jres.size()]);
+ }
+ unexpectedFormat();
+ }
+ unexpectedFormat();
+ return null; // previous line will throw an exception
+ }
+
+ private void unexpectedFormat() throws CoreException {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), "Output from java_home not in expected format")); //$NON-NLS-1$
+ }
+}
diff --git a/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/PListParser.java b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/PListParser.java
new file mode 100644
index 0000000..eb683c6
--- /dev/null
+++ b/org.eclipse.jdt.launching/launching/org/eclipse/jdt/internal/launching/PListParser.java
@@ -0,0 +1,237 @@
+/*******************************************************************************
+ * Copyright (c) 2010 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.BufferedInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.Text;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parses an XML property list into its associated objects.
+ */
+public class PListParser {
+
+ /**
+ * Constants for XML element names and attributes
+ */
+ private static final String PLIST_ELEMENT = "plist"; //$NON-NLS-1$
+ private static final String KEY_ELEMENT = "key"; //$NON-NLS-1$
+ private static final String DICT_ELEMENT = "dict"; //$NON-NLS-1$
+ private static final String ARRAY_ELEMENT = "array"; //$NON-NLS-1$
+ private static final String TRUE_ELEMENT = "true"; //$NON-NLS-1$
+ private static final String FALSE_ELEMENT = "false"; //$NON-NLS-1$
+ private static final String INT_ELEMENT = "integer"; //$NON-NLS-1$
+ private static final String STRING_ELEMENT = "string"; //$NON-NLS-1$
+
+ /**
+ * Parses the given input stream which corresponds to an XML plist. See the DTD
+ * here: http://www.apple.com/DTDs/PropertyList-1.0.dtd
+ *
+ * @param stream XML plist input stream
+ * @return Object(s) in the stream
+ * @throws CoreException
+ */
+ public Object parse(InputStream stream) throws CoreException {
+ try {
+ stream = new BufferedInputStream(stream);
+ return parseXML(stream);
+ } catch (FileNotFoundException e) {
+ abort(e);
+ } catch (SAXException e) {
+ abort(e);
+ } catch (ParserConfigurationException e) {
+ abort(e);
+ } catch (IOException e) {
+ abort(e);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ abort(e);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Return a LaunchConfigurationInfo object initialized from XML contained in
+ * the specified stream. Simply pass out any exceptions encountered so that
+ * caller can deal with them. This is important since caller may need access to the
+ * actual exception.
+ */
+ private Object parseXML(InputStream stream) throws CoreException, ParserConfigurationException, IOException, SAXException {
+ Element root = null;
+ DocumentBuilder parser = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ parser.setErrorHandler(new DefaultHandler());
+ root = parser.parse(new InputSource(stream)).getDocumentElement();
+ if (!root.getNodeName().equalsIgnoreCase(PLIST_ELEMENT)) {
+ throw getInvalidFormatException();
+ }
+ NodeList list = root.getChildNodes();
+ Node node = null;
+ Element element = null;
+ for (int i = 0; i < list.getLength(); ++i) {
+ node = list.item(i);
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ element = (Element) node;
+ return parseObject(element);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Returns an invalid format exception
+ *
+ * @return an invalid format exception
+ */
+ private CoreException getInvalidFormatException() {
+ return new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, "Invalid plist XML", null)); //$NON-NLS-1$
+ }
+
+ /**
+ * Parses and returns an object from the given root element, possibly <code>null</code>.
+ *
+ * @param element the root node from the XML document
+ * @return parsed object or <code>null</code>
+ * @throws CoreException
+ */
+ private Object parseObject(Element element) throws CoreException {
+ String nodeName = element.getNodeName();
+ if (nodeName.equalsIgnoreCase(ARRAY_ELEMENT)) {
+ return parseArray(element);
+ } else if (nodeName.equalsIgnoreCase(DICT_ELEMENT)) {
+ return parseDictionary(element);
+ } else if (nodeName.equalsIgnoreCase(KEY_ELEMENT)) {
+ return getText(element);
+ } else if (nodeName.equalsIgnoreCase(TRUE_ELEMENT)) {
+ return Boolean.TRUE;
+ } else if (nodeName.equalsIgnoreCase(FALSE_ELEMENT)) {
+ return Boolean.FALSE;
+ } else if(nodeName.equalsIgnoreCase(INT_ELEMENT)) {
+ try {
+ return new Integer(Integer.parseInt(getText(element)));
+ } catch (NumberFormatException e) {
+ abort(e);
+ }
+ } else if (nodeName.equalsIgnoreCase(STRING_ELEMENT)) {
+ return getText(element);
+ } else if (nodeName.equalsIgnoreCase(KEY_ELEMENT)) {
+ return getText(element);
+ } else {
+ // TODO: unsupported
+ }
+ return null;
+ }
+
+ /**
+ * Parses and returns an array from the given root element, possibly empty.
+ *
+ * @param root the root array node from the XML document
+ * @return parsed array or <code>null</code>
+ * @throws CoreException
+ */
+ private Object[] parseArray(Element root) throws CoreException {
+ List collection = new ArrayList();
+ NodeList list = root.getChildNodes();
+ Node node = null;
+ Element element = null;
+ for (int i = 0; i < list.getLength(); ++i) {
+ node = list.item(i);
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ element = (Element) node;
+ Object obj = parseObject(element);
+ if (obj != null) {
+ collection.add(obj);
+ }
+ }
+ }
+ return collection.toArray();
+ }
+
+ /**
+ * Parses and returns a map from the given dictionary element, possibly empty.
+ *
+ * @param root the root dictionary node from the XML document
+ * @return parsed map or <code>null</code>
+ * @throws CoreException
+ */
+ private Map parseDictionary(Element root) throws CoreException {
+ Map dict = new HashMap();
+ NodeList list = root.getChildNodes();
+ Node node = null;
+ Element element = null;
+ String nodeName = null;
+ String key = null;
+ for (int i = 0; i < list.getLength(); ++i) {
+ node = list.item(i);
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.ELEMENT_NODE) {
+ element = (Element) node;
+ nodeName = element.getNodeName();
+ if (nodeName.equalsIgnoreCase(KEY_ELEMENT)) {
+ key = getText(element);
+ } else {
+ dict.put(key, parseObject(element));
+ }
+ }
+ }
+ return dict;
+ }
+
+ /**
+ * Returns the value of the first child text node from the given element,
+ * or <code>null</code>.
+ *
+ * @param root
+ * @return its text or <code>null</code> if none
+ */
+ private String getText(Element root) {
+ NodeList list = root.getChildNodes();
+ Node node = null;
+ for (int i = 0; i < list.getLength(); ++i) {
+ node = list.item(i);
+ short nodeType = node.getNodeType();
+ if (nodeType == Node.TEXT_NODE) {
+ return ((Text) node).getNodeValue();
+ }
+ }
+ return null;
+ }
+
+ private void abort(Throwable t) throws CoreException {
+ throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.ID_PLUGIN, "Exception occurred parsing property list", t)); //$NON-NLS-1$
+ }
+}