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$
+	}
+}