Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: 429e63c6e33f55be99318189754d75ad7a91b21a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*******************************************************************************
 * Copyright (c) 2010, 2012 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.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$
	/**
	 * 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$
	
	static final JREDescriptor[] NO_DESCRIPTORS = new JREDescriptor[0];
	
	/**
	 * Describes an installed JRE on MacOS
	 */
	public class JREDescriptor {
		String fName;
		File fHome;
		String fVersion;
		String fId;
		
		/**
		 * Constructs a new JRE descriptor 
		 * 
		 * @param home Home directory of the JRE
		 * @param name JRE name
		 * @param version JRE version
		 * @param id the computed id of the JRE from the plist output
		 */
		public JREDescriptor(File home, String name, String version, String id) {
			fHome = home;
			fName = name;
			fVersion = version;
			fId = id;
		}
		
		/**
		 * 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;
		}
		
		/**
		 * returns the computed id of the descriptor
		 * 
		 * @return the descriptor id
		 * @since 3.8
		 */
		public String getId() {
			return fId;
		}
		
		@Override
		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) && fId.equals(jre.fId);
			}
			return false;
		}
		
		@Override
		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 milliseconds)
				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());
			return parseJREInfo(stream);
		}
		return NO_DESCRIPTORS;
	}
	
	/**
	 * Parse {@link JREDescriptor}s from the given input stream. The stream is expected to be in the 
	 * XML properties format.
	 * 
	 * @param stream
	 * @return the array of {@link JREDescriptor}s or an empty array never <code>null</code>
	 * @since 3.8
	 */
	public JREDescriptor[] parseJREInfo(InputStream stream) {
		try {
			Object result = new PListParser().parse(stream);
			if (result instanceof Object[]) {
				Object[] maps = (Object[]) result;
				List<JREDescriptor> jres= new ArrayList<JREDescriptor>();
				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) {
							String ver = (String) version;
							JREDescriptor descriptor = new JREDescriptor(new File((String)home), (String)name, (String)version, computeId(map, ver));
							if (!jres.contains(descriptor)) { // remove duplicates
								jres.add(descriptor);	
							}
						} 
					} 
				}
				return jres.toArray(new JREDescriptor[jres.size()]);
			}
		} catch (CoreException ce) {
			LaunchingPlugin.log(ce);
		}
		return NO_DESCRIPTORS;
	}
	
	/**
	 * 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
	 */
	String computeId(Map<?, ?> map, String version) {
		Object o = map.get(PLIST_JVM_BUNDLE_ID);
		if(o instanceof String) {
			return (String) o;
		}
		return version;
 	}
}

Back to the top