1 /*******************************************************************************
2  * Copyright (c) 2000, 2020 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Frits Jalvingh - Contribution for Bug 459831 - [launching] Support attaching
14  *     	external annotations to a JRE container
15  *******************************************************************************/
16 package org.eclipse.jdt.launching;
17 
18 
19 import java.io.BufferedInputStream;
20 import java.io.ByteArrayInputStream;
21 import java.io.File;
22 import java.io.FileInputStream;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.io.StringReader;
26 import java.net.MalformedURLException;
27 import java.net.URL;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.HashMap;
33 import java.util.HashSet;
34 import java.util.Hashtable;
35 import java.util.Iterator;
36 import java.util.List;
37 import java.util.Map;
38 import java.util.Objects;
39 import java.util.Set;
40 import java.util.stream.Collectors;
41 
42 import javax.xml.parsers.DocumentBuilder;
43 
44 import org.eclipse.core.resources.IProject;
45 import org.eclipse.core.resources.IResource;
46 import org.eclipse.core.resources.IWorkspaceRoot;
47 import org.eclipse.core.resources.ResourcesPlugin;
48 import org.eclipse.core.runtime.CoreException;
49 import org.eclipse.core.runtime.IConfigurationElement;
50 import org.eclipse.core.runtime.IExtensionPoint;
51 import org.eclipse.core.runtime.IPath;
52 import org.eclipse.core.runtime.IProgressMonitor;
53 import org.eclipse.core.runtime.IStatus;
54 import org.eclipse.core.runtime.ListenerList;
55 import org.eclipse.core.runtime.MultiStatus;
56 import org.eclipse.core.runtime.Path;
57 import org.eclipse.core.runtime.Platform;
58 import org.eclipse.core.runtime.Preferences;
59 import org.eclipse.core.runtime.Status;
60 import org.eclipse.core.runtime.preferences.BundleDefaultsScope;
61 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
62 import org.eclipse.core.runtime.preferences.InstanceScope;
63 import org.eclipse.core.variables.IStringVariableManager;
64 import org.eclipse.core.variables.VariablesPlugin;
65 import org.eclipse.debug.core.ILaunchConfiguration;
66 import org.eclipse.debug.core.sourcelookup.ISourceContainer;
67 import org.eclipse.jdt.core.IAccessRule;
68 import org.eclipse.jdt.core.IClasspathAttribute;
69 import org.eclipse.jdt.core.IClasspathContainer;
70 import org.eclipse.jdt.core.IClasspathEntry;
71 import org.eclipse.jdt.core.IJavaModel;
72 import org.eclipse.jdt.core.IJavaProject;
73 import org.eclipse.jdt.core.IModuleDescription;
74 import org.eclipse.jdt.core.IPackageFragmentRoot;
75 import org.eclipse.jdt.core.JavaCore;
76 import org.eclipse.jdt.core.JavaModelException;
77 import org.eclipse.jdt.internal.core.JavaProject;
78 import org.eclipse.jdt.internal.launching.CompositeId;
79 import org.eclipse.jdt.internal.launching.DefaultEntryResolver;
80 import org.eclipse.jdt.internal.launching.DefaultProjectClasspathEntry;
81 import org.eclipse.jdt.internal.launching.EEVMInstall;
82 import org.eclipse.jdt.internal.launching.EEVMType;
83 import org.eclipse.jdt.internal.launching.JREContainerInitializer;
84 import org.eclipse.jdt.internal.launching.JavaSourceLookupUtil;
85 import org.eclipse.jdt.internal.launching.LaunchingMessages;
86 import org.eclipse.jdt.internal.launching.LaunchingPlugin;
87 import org.eclipse.jdt.internal.launching.RuntimeClasspathEntry;
88 import org.eclipse.jdt.internal.launching.RuntimeClasspathEntryResolver;
89 import org.eclipse.jdt.internal.launching.RuntimeClasspathProvider;
90 import org.eclipse.jdt.internal.launching.SocketAttachConnector;
91 import org.eclipse.jdt.internal.launching.StandardVMType;
92 import org.eclipse.jdt.internal.launching.VMDefinitionsContainer;
93 import org.eclipse.jdt.internal.launching.VMListener;
94 import org.eclipse.jdt.internal.launching.VariableClasspathEntry;
95 import org.eclipse.jdt.internal.launching.environments.EnvironmentsManager;
96 import org.eclipse.jdt.launching.environments.ExecutionEnvironmentDescription;
97 import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
98 import org.eclipse.jdt.launching.environments.IExecutionEnvironmentsManager;
99 import org.eclipse.osgi.util.NLS;
100 import org.osgi.service.prefs.BackingStoreException;
101 import org.w3c.dom.Element;
102 import org.w3c.dom.Node;
103 import org.w3c.dom.NodeList;
104 import org.xml.sax.InputSource;
105 import org.xml.sax.SAXException;
106 
107 /**
108  * The central access point for launching support. This class manages
109  * the registered VM types contributed through the
110  * <code>"org.eclipse.jdt.launching.vmType"</code> extension point.
111  * As well, this class provides VM install change notification,
112  * and computes class paths and source lookup paths for launch
113  * configurations.
114  * <p>
115  * This class provides static methods only.
116  * </p>
117  * @noinstantiate This class is not intended to be instantiated by clients.
118  */
119 public final class JavaRuntime {
120 
121 	/**
122 	 * Classpath variable name used for the default JRE's library
123 	 * (value <code>"JRE_LIB"</code>).
124 	 */
125 	public static final String JRELIB_VARIABLE= "JRE_LIB"; //$NON-NLS-1$
126 
127 	/**
128 	 * Classpath variable name used for the default JRE's library source
129 	 * (value <code>"JRE_SRC"</code>).
130 	 */
131 	public static final String JRESRC_VARIABLE= "JRE_SRC"; //$NON-NLS-1$
132 
133 	/**
134 	 * Classpath variable name used for the default JRE's library source root
135 	 * (value <code>"JRE_SRCROOT"</code>).
136 	 */
137 	public static final String JRESRCROOT_VARIABLE= "JRE_SRCROOT"; //$NON-NLS-1$
138 
139 	/**
140 	 * Simple identifier constant (value <code>"runtimeClasspathEntryResolvers"</code>) for the
141 	 * runtime classpath entry resolvers extension point.
142 	 *
143 	 * @since 2.0
144 	 */
145 	public static final String EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRY_RESOLVERS= "runtimeClasspathEntryResolvers";	 //$NON-NLS-1$
146 
147 	/**
148 	 * Simple identifier constant (value <code>"classpathProviders"</code>) for the
149 	 * runtime classpath providers extension point.
150 	 *
151 	 * @since 2.0
152 	 */
153 	public static final String EXTENSION_POINT_RUNTIME_CLASSPATH_PROVIDERS= "classpathProviders";	 //$NON-NLS-1$
154 
155 	/**
156 	 * Simple identifier constant (value <code>"executionEnvironments"</code>) for the
157 	 * execution environments extension point.
158 	 *
159 	 * @since 3.2
160 	 */
161 	public static final String EXTENSION_POINT_EXECUTION_ENVIRONMENTS= "executionEnvironments";	 //$NON-NLS-1$
162 
163 	/**
164 	 * Simple identifier constant (value <code>"vmInstalls"</code>) for the
165 	 * VM installs extension point.
166 	 *
167 	 * @since 3.2
168 	 */
169 	public static final String EXTENSION_POINT_VM_INSTALLS = "vmInstalls";	 //$NON-NLS-1$
170 
171 	/**
172 	 * Simple identifier constant (value <code>"libraryLocationResolvers"</code>) for the
173 	 * Library Resolvers extension point
174 	 *
175 	 * @since 3.7
176 	 */
177 	public static final String EXTENSION_POINT_LIBRARY_LOCATION_RESOLVERS = "libraryLocationResolvers"; //$NON-NLS-1$
178 
179 	/**
180 	 * Classpath container used for a project's JRE
181 	 * (value <code>"org.eclipse.jdt.launching.JRE_CONTAINER"</code>). A
182 	 * container is resolved in the context of a specific Java project, to one
183 	 * or more system libraries contained in a JRE. The container can have zero
184 	 * or two path segments following the container name. When no segments
185 	 * follow the container name, the workspace default JRE is used to build a
186 	 * project. Otherwise the segments identify a specific JRE used to build a
187 	 * project:
188 	 * <ol>
189 	 * <li>VM Install Type Identifier - identifies the type of JRE used to build the
190 	 * 	project. For example, the standard VM.</li>
191 	 * <li>VM Install Name - a user defined name that identifies that a specific VM
192 	 * 	of the above kind. For example, <code>IBM 1.3.1</code>. This information is
193 	 *  shared in a projects classpath file, so teams must agree on JRE naming
194 	 * 	conventions.</li>
195 	 * </ol>
196 	 * <p>
197 	 * Since 3.2, the path may also identify an execution environment as follows:
198 	 * <ol>
199 	 * <li>Execution environment extension point name
200 	 * (value <code>executionEnvironments</code>)</li>
201 	 * <li>Identifier of a contributed execution environment</li>
202 	 * </ol>
203 	 * @since 2.0
204 	 */
205 	public static final String JRE_CONTAINER = LaunchingPlugin.getUniqueIdentifier() + ".JRE_CONTAINER"; //$NON-NLS-1$
206 
207 	/**
208 	 * Marker type identifier for JRE container problems.
209 	 *
210 	 * @since 3.6
211 	 */
212 	public static final String JRE_CONTAINER_MARKER = LaunchingPlugin.getUniqueIdentifier() + ".jreContainerMarker"; //$NON-NLS-1$
213 
214 	/**
215 	 * Marker type identifier for JRE compiler compliance problems.
216 	 *
217 	 * @since 3.11
218 	 */
219 	public static final String JRE_COMPILER_COMPLIANCE_MARKER = LaunchingPlugin.getUniqueIdentifier() + ".jreCompilerComplianceMarker"; //$NON-NLS-1$
220 
221 	/**
222 	 * A status code indicating that a JRE could not be resolved for a project.
223 	 * When a JRE cannot be resolved for a project by this plug-in's container
224 	 * initializer, an exception is thrown with this status code. A status handler
225 	 * may be registered for this status code. The <code>source</code> object provided
226 	 * to the status handler is the Java project for which the path could not be
227 	 * resolved. The status handler must return an <code>IVMInstall</code> or <code>null</code>.
228 	 * The container resolver will re-set the project's classpath if required.
229 	 *
230 	 * @since 2.0
231 	 */
232 	public static final int ERR_UNABLE_TO_RESOLVE_JRE = 160;
233 
234 	/**
235 	 * Preference key for launch/connect timeout. VM Runners should honor this timeout
236 	 * value when attempting to launch and connect to a debuggable VM. The value is
237 	 * an int, indicating a number of milliseconds.
238 	 *
239 	 * @since 2.0
240 	 */
241 	public static final String PREF_CONNECT_TIMEOUT = LaunchingPlugin.getUniqueIdentifier() + ".PREF_CONNECT_TIMEOUT"; //$NON-NLS-1$
242 
243 	/**
244 	 * Preference key for the String of XML that defines all installed VMs.
245 	 *
246 	 * @since 2.1
247 	 */
248 	public static final String PREF_VM_XML = LaunchingPlugin.getUniqueIdentifier() + ".PREF_VM_XML"; //$NON-NLS-1$
249 
250 	/**
251 	 * Preference key for the problem severity when an execution environment is bound to a project's build path for which there is no strictly
252 	 * compatible JRE available in the workspace. Value is one of {@link JavaCore#ERROR}, {@link JavaCore#WARNING}, {@link JavaCore#INFO}, or
253 	 * {@link JavaCore#IGNORE}
254 	 *
255 	 * @since 3.5
256 	 */
257 	public static final String PREF_STRICTLY_COMPATIBLE_JRE_NOT_AVAILABLE = LaunchingPlugin.getUniqueIdentifier() + ".PREF_STRICTLY_COMPATIBLE_JRE_NOT_AVAILABLE"; //$NON-NLS-1$
258 
259 	/**
260 	 * Preference key for the problem severity when an compiler compliance that is set does not match the used JRE. Value is one of
261 	 * {@link JavaCore#ERROR}, {@link JavaCore#WARNING}, {@link JavaCore#INFO}, or {@link JavaCore#IGNORE}
262 	 * <p>
263 	 * This preference will not be applicable if the JRE used is 9 or above and {@link JavaCore#COMPILER_RELEASE} option is enabled.
264 	 * </p>
265 	 *
266 	 * @since 3.10
267 	 */
268 	public static final String PREF_COMPILER_COMPLIANCE_DOES_NOT_MATCH_JRE = LaunchingPlugin.getUniqueIdentifier() + ".PREF_COMPILER_COMPLIANCE_DOES_NOT_MATCH_JRE"; //$NON-NLS-1$
269 
270 	/**
271 	 * Unique identifier constant (value <code>"org.eclipse.jdt.launching"</code>)
272 	 * for the Java launching plug-in.
273 	 *
274 	 * This preference will not be applicable if the JRE used is 9 or above and {@link JavaCore#COMPILER_RELEASE} option is enabled.
275 	 * @since 3.5
276 	 */
277 	public static final String ID_PLUGIN = LaunchingPlugin.ID_PLUGIN;
278 
279 	/**
280 	 * Default launch/connect timeout (milliseconds).
281 	 *
282 	 * @since 2.0
283 	 */
284 	public static final int DEF_CONNECT_TIMEOUT = 20000;
285 
286 	/**
287 	 * Attribute key for a process property. The class
288 	 * <code>org.eclipse.debug.core.model.IProcess</code> allows attaching
289 	 * String properties to processes.
290 	 * The value of this attribute is the command line a process
291 	 * was launched with. Implementers of <code>IVMRunner</code> should use
292 	 * this attribute key to attach the command lines to the processes they create.
293 	 *
294 	 * @deprecated - use <code>IProcess.ATTR_CMDLINE</code>
295 	 */
296 	@Deprecated
297 	public final static String ATTR_CMDLINE= LaunchingPlugin.getUniqueIdentifier() + ".launcher.cmdLine"; //$NON-NLS-1$
298 
299 	/**
300 	 * Boolean preference controlling whether only exported entries should even be included when the runtime classpath is computed
301 	 * @since 3.7
302 	 */
303 	public static final String PREF_ONLY_INCLUDE_EXPORTED_CLASSPATH_ENTRIES = ID_PLUGIN + ".only_include_exported_classpath_entries"; //$NON-NLS-1$
304 
305 	/**
306 	 * Attribute key for a classpath attribute referencing a
307 	 * list of shared libraries that should appear on the
308 	 * <code>-Djava.library.path</code> system property.
309 	 * <p>
310 	 * The factory methods <code>newLibraryPathsAttribute(String[])</code>
311 	 * and <code>getLibraryPaths(IClasspathAttribute)</code> should be used to
312 	 * encode and decode the attribute value.
313 	 * </p>
314 	 * <p>
315 	 * Each string is used to create an <code>IPath</code> using the constructor
316 	 * <code>Path(String)</code>, and may contain <code>IStringVariable</code>'s.
317 	 * Variable substitution is performed on the string prior to constructing
318 	 * a path from the string.
319 	 * If the resulting <code>IPath</code> is a relative path, it is interpreted
320 	 * as relative to the workspace location. If the path is absolute, it is
321 	 * interpreted as an absolute path in the local file system.
322 	 * </p>
323 	 * @since 3.1
324 	 * @see org.eclipse.jdt.core.IClasspathAttribute
325 	 */
326 	public static final String CLASSPATH_ATTR_LIBRARY_PATH_ENTRY =  LaunchingPlugin.getUniqueIdentifier() + ".CLASSPATH_ATTR_LIBRARY_PATH_ENTRY"; //$NON-NLS-1$
327 
328 	// lock for VM initialization
329 	private static Object fgVMLock = new Object();
330 	private static boolean fgInitializingVMs = false;
331 
332 	private static HashSet<Object> fgVMTypes = null;
333 	private static String fgDefaultVMId = null;
334 	private static String fgDefaultVMConnectorId = null;
335 
336 	/**
337 	 * Resolvers keyed by variable name, container id,
338 	 * and runtime classpath entry id.
339 	 */
340 	private static Map<String, IRuntimeClasspathEntryResolver> fgVariableResolvers = null;
341 	private static Map<String, IRuntimeClasspathEntryResolver> fgContainerResolvers = null;
342 	private static Map<String, RuntimeClasspathEntryResolver> fgRuntimeClasspathEntryResolvers = null;
343 
344 	/**
345 	 * Path providers keyed by id
346 	 */
347 	private static Map<String, RuntimeClasspathProvider> fgPathProviders = null;
348 
349 	/**
350 	 * Default classpath and source path providers.
351 	 */
352 	private static IRuntimeClasspathProvider fgDefaultClasspathProvider = new StandardClasspathProvider();
353 	private static IRuntimeClasspathProvider fgDefaultSourcePathProvider = new StandardSourcePathProvider();
354 
355 	/**
356 	 * VM change listeners
357 	 */
358 	private static ListenerList<IVMInstallChangedListener> fgVMListeners = new ListenerList<>();
359 
360 	/**
361 	 * Cache of already resolved projects in container entries. Used to avoid
362 	 * cycles in project dependencies when resolving classpath container entries.
363 	 * Counters used to know when entering/exiting to clear cache
364 	 */
365 	private static ThreadLocal<List<IJavaProject>> fgProjects = new ThreadLocal<>(); // Lists
366 	private static ThreadLocal<Integer> fgEntryCount = new ThreadLocal<>(); // Integers
367 
368     /**
369      *  Set of IDs of VMs contributed via vmInstalls extension point.
370      */
371     private static Set<String> fgContributedVMs = new HashSet<>();
372 
373 	/**
374 	 * This class contains only static methods, and is not intended
375 	 * to be instantiated.
376 	 */
JavaRuntime()377 	private JavaRuntime() {
378 	}
379 
380 	/**
381 	 * Initializes VM type extensions.
382 	 */
initializeVMTypeExtensions()383 	private static void initializeVMTypeExtensions() {
384 		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.ID_PLUGIN, "vmInstallTypes"); //$NON-NLS-1$
385 		if(extensionPoint != null) {
386 			IConfigurationElement[] configs = extensionPoint.getConfigurationElements();
387 			MultiStatus status = new MultiStatus(LaunchingPlugin.getUniqueIdentifier(), IStatus.OK, "Exceptions occurred", null);  //$NON-NLS-1$
388 			fgVMTypes = new HashSet<>();
389 			for (int i= 0; i < configs.length; i++) {
390 				try {
391 					fgVMTypes.add(configs[i].createExecutableExtension("class")); //$NON-NLS-1$
392 				}
393 				catch (CoreException e) {status.add(e.getStatus());}
394 			}
395 			if (!status.isOK()) {
396 				//only happens on a CoreException
397 				LaunchingPlugin.log(status);
398 			}
399 		}
400 		else {
401 			LaunchingPlugin.log(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), "VM Install extension point not found", null)); //$NON-NLS-1$
402 		}
403 	}
404 
405 	/**
406 	 * Returns the VM assigned to build the given Java project.
407 	 * The project must exist. The VM assigned to a project is
408 	 * determined from its build path.
409 	 *
410 	 * @param project the project to retrieve the VM from
411 	 * @return the VM instance that is assigned to build the given Java project
412 	 * 		   Returns <code>null</code> if no VM is referenced on the project's build path.
413 	 * @throws CoreException if unable to determine the project's VM install
414 	 */
getVMInstall(IJavaProject project)415 	public static IVMInstall getVMInstall(IJavaProject project) throws CoreException {
416 		// check the classpath
417 		IVMInstall vm = null;
418 		IClasspathEntry[] classpath = project.getRawClasspath();
419 		IRuntimeClasspathEntryResolver resolver = null;
420 		IClasspathEntry entry = null;
421 		for (int i = 0; i < classpath.length; i++) {
422 			entry = classpath[i];
423 			switch (entry.getEntryKind()) {
424 				case IClasspathEntry.CPE_VARIABLE:
425 					resolver = getVariableResolver(entry.getPath().segment(0));
426 					if (resolver != null) {
427 						vm = resolver.resolveVMInstall(entry);
428 					}
429 					break;
430 				case IClasspathEntry.CPE_CONTAINER:
431 					resolver = getContainerResolver(entry.getPath().segment(0));
432 					if (resolver != null) {
433 						vm = resolver.resolveVMInstall(entry);
434 					}
435 					break;
436 			}
437 			if (vm != null) {
438 				return vm;
439 			}
440 		}
441 		return null;
442 	}
443 
444 	/**
445 	 * Returns the VM install type with the given unique id.
446 	 * @param id the VM install type unique id
447 	 * @return	The VM install type for the given id, or <code>null</code> if no
448 	 * 			VM install type with the given id is registered.
449 	 */
getVMInstallType(String id)450 	public static IVMInstallType getVMInstallType(String id) {
451 		IVMInstallType[] vmTypes= getVMInstallTypes();
452 		for (int i= 0; i < vmTypes.length; i++) {
453 			if (vmTypes[i].getId().equals(id)) {
454 				return vmTypes[i];
455 			}
456 		}
457 		return null;
458 	}
459 
460 	/**
461 	 * Sets a VM as the system-wide default VM, and notifies registered VM install
462 	 * change listeners of the change.
463 	 *
464 	 * @param vm	The VM to make the default. May be <code>null</code> to clear
465 	 * 				the default.
466 	 * @param monitor progress monitor or <code>null</code>
467 	 * @throws CoreException if trying to set the default VM install encounters problems
468 	 */
setDefaultVMInstall(IVMInstall vm, IProgressMonitor monitor)469 	public static void setDefaultVMInstall(IVMInstall vm, IProgressMonitor monitor) throws CoreException {
470 		setDefaultVMInstall(vm, monitor, true);
471 	}
472 
473 	/**
474 	 * Sets a VM as the system-wide default VM, and notifies registered VM install
475 	 * change listeners of the change.
476 	 *
477 	 * @param vm	The VM to make the default. May be <code>null</code> to clear
478 	 * 				the default.
479 	 * @param monitor progress monitor or <code>null</code>
480 	 * @param savePreference If <code>true</code>, update workbench preferences to reflect
481 	 * 		   				  the new default VM.
482 	 * @throws CoreException if trying to set the default VM install encounters problems
483 	 * @since 2.1
484 	 */
setDefaultVMInstall(IVMInstall vm, IProgressMonitor monitor, boolean savePreference)485 	public static void setDefaultVMInstall(IVMInstall vm, IProgressMonitor monitor, boolean savePreference) throws CoreException {
486 		IVMInstall previous = null;
487 		if (fgDefaultVMId != null) {
488 			previous = getVMFromCompositeId(fgDefaultVMId);
489 		}
490 		fgDefaultVMId= getCompositeIdFromVM(vm);
491 		if (savePreference) {
492 			saveVMConfiguration();
493 		}
494 		IVMInstall current = null;
495 		if (fgDefaultVMId != null) {
496 			current = getVMFromCompositeId(fgDefaultVMId);
497 		}
498 		if (previous != current) {
499 			notifyDefaultVMChanged(previous, current);
500 		}
501 	}
502 
503 	/**
504 	 * Sets a VM connector as the system-wide default VM. This setting is persisted when
505 	 * saveVMConfiguration is called.
506 	 * @param	connector The connector to make the default. May be <code>null</code> to clear
507 	 * 				the default.
508 	 * @param monitor The progress monitor to use
509 	 * @since 2.0
510 	 * @throws CoreException Thrown if saving the new default setting fails
511 	 */
setDefaultVMConnector(IVMConnector connector, IProgressMonitor monitor)512 	public static void setDefaultVMConnector(IVMConnector connector, IProgressMonitor monitor) throws CoreException {
513 		fgDefaultVMConnectorId= connector.getIdentifier();
514 		saveVMConfiguration();
515 	}
516 
517 	/**
518 	 * Return the default VM set with <code>setDefaultVM()</code>.
519 	 * @return	Returns the default VM. May return <code>null</code> when no default
520 	 * 			VM was set or when the default VM has been disposed.
521 	 */
getDefaultVMInstall()522 	public static IVMInstall getDefaultVMInstall() {
523 
524 		IVMInstall install= getVMFromCompositeId(getDefaultVMId());
525 		if (install != null) {
526 			File location = install.getInstallLocation();
527 			if (location != null) {
528 				if (location.exists()) {
529 					return install;
530 				}
531 			}
532 		}
533 		// if the default JRE goes missing, re-detect
534 		if (install != null) {
535 			install.getVMInstallType().disposeVMInstall(install.getId());
536 		}
537 		synchronized (fgVMLock) {
538 			fgDefaultVMId = null;
539 			fgVMTypes = null;
540 			initializeVMs();
541 		}
542 		return getVMFromCompositeId(getDefaultVMId());
543 	}
544 
545 	/**
546 	 * Return the default VM connector.
547 	 * @return	Returns the default VM connector.
548 	 * @since 2.0
549 	 */
getDefaultVMConnector()550 	public static IVMConnector getDefaultVMConnector() {
551 		String id = getDefaultVMConnectorId();
552 		IVMConnector connector = null;
553 		if (id != null) {
554 			connector = getVMConnector(id);
555 		}
556 		if (connector == null) {
557 			connector = new SocketAttachConnector();
558 		}
559 		return connector;
560 	}
561 
562 	/**
563 	 * Returns the list of registered VM types. VM types are registered via
564 	 * <code>"org.eclipse.jdt.launching.vmTypes"</code> extension point.
565 	 * Returns an empty list if there are no registered VM types.
566 	 *
567 	 * @return the list of registered VM types
568 	 */
getVMInstallTypes()569 	public static IVMInstallType[] getVMInstallTypes() {
570 		initializeVMs();
571 		return fgVMTypes.toArray(new IVMInstallType[fgVMTypes.size()]);
572 	}
573 
574 	/**
575 	 * Returns the default VM id determined during the initialization of the VM types
576 	 * @return the id of the default VM
577 	 */
getDefaultVMId()578 	private static String getDefaultVMId() {
579 		initializeVMs();
580 		return fgDefaultVMId;
581 	}
582 
583 	/**
584 	 * Returns the default VM connector id determined during the initialization of the VM types
585 	 * @return the id of the default VM connector
586 	 */
getDefaultVMConnectorId()587 	private static String getDefaultVMConnectorId() {
588 		initializeVMs();
589 		return fgDefaultVMConnectorId;
590 	}
591 
592 	/**
593 	 * Returns a String that uniquely identifies the specified VM across all VM types.
594 	 *
595 	 * @param vm the instance of IVMInstallType to be identified
596 	 * @return the unique identifier for the specified VM
597 	 *
598 	 * @since 2.1
599 	 */
getCompositeIdFromVM(IVMInstall vm)600 	public static String getCompositeIdFromVM(IVMInstall vm) {
601 		if (vm == null) {
602 			return null;
603 		}
604 		IVMInstallType vmType = vm.getVMInstallType();
605 		String typeID = vmType.getId();
606 		CompositeId id = new CompositeId(new String[] { typeID, vm.getId() });
607 		return id.toString();
608 	}
609 
610 	/**
611 	 * Return the VM corresponding to the specified composite Id.  The id uniquely
612 	 * identifies a VM across all VM types.
613 	 *
614 	 * @param idString the composite id that specifies an instance of IVMInstall
615 	 * @return the VM corresponding to the specified composite Id.
616 	 *
617 	 * @since 2.1
618 	 */
getVMFromCompositeId(String idString)619 	public static IVMInstall getVMFromCompositeId(String idString) {
620 		if (idString == null || idString.length() == 0) {
621 			return null;
622 		}
623 		CompositeId id= CompositeId.fromString(idString);
624 		if (id.getPartCount() == 2) {
625 			IVMInstallType vmType= getVMInstallType(id.get(0));
626 			if (vmType != null) {
627 				return vmType.findVMInstall(id.get(1));
628 			}
629 		}
630 		return null;
631 	}
632 
633 	/**
634 	 * Returns a new runtime classpath entry for the given expression that
635 	 * may contain string substitution variable references. The resulting expression
636 	 * refers to an archive (jar or directory) containing class files.
637 	 *
638 	 * @param expression an expression that resolves to the location of an archive
639 	 * @return runtime classpath entry
640 	 * @since 3.0
641 	 */
newStringVariableClasspathEntry(String expression)642 	public static IRuntimeClasspathEntry newStringVariableClasspathEntry(String expression) {
643 		return new VariableClasspathEntry(expression);
644 	}
645 
646 	/**
647 	 * Returns a new runtime classpath entry containing the default classpath
648 	 * for the specified Java project.
649 	 *
650 	 * @param project Java project
651 	 * @return runtime classpath entry
652 	 * @since 3.0
653 	 */
newDefaultProjectClasspathEntry(IJavaProject project)654 	public static IRuntimeClasspathEntry newDefaultProjectClasspathEntry(IJavaProject project) {
655 		return new DefaultProjectClasspathEntry(project);
656 	}
657 
658 	/**
659 	 * Returns a new runtime classpath entry for the given project.
660 	 *
661 	 * @param project Java project
662 	 * @return runtime classpath entry
663 	 * @since 2.0
664 	 */
newProjectRuntimeClasspathEntry(IJavaProject project)665 	public static IRuntimeClasspathEntry newProjectRuntimeClasspathEntry(IJavaProject project) {
666 		return newRuntimeClasspathEntry(JavaCore.newProjectEntry(project.getProject().getFullPath()));
667 	}
668 
669 	/**
670 	 * Returns a new runtime classpath entry for the given project.
671 	 *
672 	 * @param project
673 	 *            Java project
674 	 * @param classpathProperty
675 	 *            the type of entry - one of <code>USER_CLASSES</code>, <code>BOOTSTRAP_CLASSES</code>,<code>STANDARD_CLASSES</code>,
676 	 *            <code>MODULE_PATH</code> or <code>CLASS_PATH</code>
677 	 * @return runtime classpath entry
678 	 * @since 3.10
679 	 */
newProjectRuntimeClasspathEntry(IJavaProject project, int classpathProperty)680 	public static IRuntimeClasspathEntry newProjectRuntimeClasspathEntry(IJavaProject project, int classpathProperty) {
681 		return newRuntimeClasspathEntry(JavaCore.newProjectEntry(project.getProject().getFullPath()), classpathProperty);
682 	}
683 
684 
685 	/**
686 	 * Returns a new runtime classpath entry for the given archive.
687 	 *
688 	 * @param resource archive resource
689 	 * @return runtime classpath entry
690 	 * @since 2.0
691 	 */
newArchiveRuntimeClasspathEntry(IResource resource)692 	public static IRuntimeClasspathEntry newArchiveRuntimeClasspathEntry(IResource resource) {
693 		return newRuntimeClasspathEntry(JavaCore.newLibraryEntry(resource.getFullPath(), null, null));
694 	}
695 
696 	/**
697 	 * Returns a new runtime classpath entry for the given archive(possibly external).
698 	 *
699 	 * @param path
700 	 *            absolute path to an archive
701 	 * @param classpathProperty
702 	 *            the type of entry - one of <code>USER_CLASSES</code>, <code>BOOTSTRAP_CLASSES</code>,<code>STANDARD_CLASSES</code>,
703 	 *            <code>MODULE_PATH</code> or <code>CLASS_PATH</code>
704 	 * @return runtime classpath entry
705 	 * @since 3.10
706 	 */
newArchiveRuntimeClasspathEntry(IPath path, int classpathProperty)707 	public static IRuntimeClasspathEntry newArchiveRuntimeClasspathEntry(IPath path, int classpathProperty) {
708 		return newRuntimeClasspathEntry(JavaCore.newLibraryEntry(path, null, null), classpathProperty);
709 	}
710 
711 	/**
712 	 * Returns a new runtime classpath entry for the given archive(possibly external).
713 	 *
714 	 * @param path
715 	 *            absolute path to an archive
716 	 * @param classpathProperty
717 	 *            the type of entry - one of <code>USER_CLASSES</code>, <code>BOOTSTRAP_CLASSES</code>,<code>STANDARD_CLASSES</code>,
718 	 *            <code>MODULE_PATH</code>, <code>CLASS_PATH</code> or <code>PATCH_MODULE</code>
719 	 * @param javaProject
720 	 *            the javaProject to be returned by {@link IRuntimeClasspathEntry#getJavaProject()}, required for PATCH_MODULE
721 	 * @return runtime classpath entry
722 	 * @since 3.10
723 	 */
newArchiveRuntimeClasspathEntry(IPath path, int classpathProperty, IJavaProject javaProject)724 	public static IRuntimeClasspathEntry newArchiveRuntimeClasspathEntry(IPath path, int classpathProperty, IJavaProject javaProject) {
725 		RuntimeClasspathEntry entry = new RuntimeClasspathEntry(JavaCore.newLibraryEntry(path, null, null), classpathProperty);
726 		entry.setJavaProject(javaProject);
727 		return entry;
728 	}
729 
730 	/**
731 	 * Returns a new runtime classpath entry for the given archive (possibly
732 	 * external).
733 	 *
734 	 * @param path absolute path to an archive
735 	 * @return runtime classpath entry
736 	 * @since 2.0
737 	 */
newArchiveRuntimeClasspathEntry(IPath path)738 	public static IRuntimeClasspathEntry newArchiveRuntimeClasspathEntry(IPath path) {
739 		return newRuntimeClasspathEntry(JavaCore.newLibraryEntry(path, null, null));
740 	}
741 
742 	/**
743 	 * Returns a new runtime classpath entry for the given archive (possibly external).
744 	 *
745 	 * @param path
746 	 *            absolute path to an archive
747 	 * @param sourceAttachmentPath
748 	 *            the absolute path of the corresponding source archive or folder, or <code>null</code> if none. Note, since 3.0, an empty path is
749 	 *            allowed to denote no source attachment. and will be automatically converted to <code>null</code>. Since 3.4, this path can also
750 	 *            denote a path external to the workspace.
751 	 * @param sourceAttachmentRootPath
752 	 *            the location of the root of the source files within the source archive or folder or <code>null</code> if this location should be
753 	 *            automatically detected.
754 	 * @param accessRules
755 	 *            the possibly empty list of access rules for this entry
756 	 * @param extraAttributes
757 	 *            the possibly empty list of extra attributes to persist with this entry
758 	 * @param isExported
759 	 *            indicates whether this entry is contributed to dependent projects in addition to the output location
760 	 * @return runtime classpath entry
761 	 * @since 3.10
762 	 */
newArchiveRuntimeClasspathEntry(IPath path, IPath sourceAttachmentPath, IPath sourceAttachmentRootPath, IAccessRule[] accessRules, IClasspathAttribute[] extraAttributes, boolean isExported)763 	public static IRuntimeClasspathEntry newArchiveRuntimeClasspathEntry(IPath path, IPath sourceAttachmentPath, IPath sourceAttachmentRootPath, IAccessRule[] accessRules, IClasspathAttribute[] extraAttributes, boolean isExported) {
764 		return newRuntimeClasspathEntry(JavaCore.newLibraryEntry(path, sourceAttachmentPath, sourceAttachmentRootPath, accessRules, extraAttributes, isExported));
765 	}
766 
767 	/**
768 	 * Returns a new runtime classpath entry for the classpath
769 	 * variable with the given path.
770 	 *
771 	 * @param path variable path; first segment is the name of the variable;
772 	 * 	trailing segments are appended to the resolved variable value
773 	 * @return runtime classpath entry
774 	 * @since 2.0
775 	 */
newVariableRuntimeClasspathEntry(IPath path)776 	public static IRuntimeClasspathEntry newVariableRuntimeClasspathEntry(IPath path) {
777 		return newRuntimeClasspathEntry(JavaCore.newVariableEntry(path, null, null));
778 	}
779 
780 	/**
781 	 * Returns a runtime classpath entry for the given container path with the given
782 	 * classpath property.
783 	 *
784 	 * @param path container path
785 	 * @param classpathProperty the type of entry - one of <code>USER_CLASSES</code>,
786 	 * 	<code>BOOTSTRAP_CLASSES</code>,<code>STANDARD_CLASSES</code>, <code>MODULE_PATH</code>
787 	 *  or <code>CLASS_PATH</code>
788 	 * @return runtime classpath entry
789 	 * @exception CoreException if unable to construct a runtime classpath entry
790 	 * @since 2.0
791 	 */
newRuntimeContainerClasspathEntry(IPath path, int classpathProperty)792 	public static IRuntimeClasspathEntry newRuntimeContainerClasspathEntry(IPath path, int classpathProperty) throws CoreException {
793 		return newRuntimeContainerClasspathEntry(path, classpathProperty, null);
794 	}
795 
796 	/**
797 	 * Returns a runtime classpath entry for the given container path with the given
798 	 * classpath property to be resolved in the context of the given Java project.
799 	 *
800 	 * @param path container path
801 	 * @param classpathProperty the type of entry - one of <code>USER_CLASSES</code>,
802 	 * 	<code>BOOTSTRAP_CLASSES</code>, or <code>STANDARD_CLASSES</code>
803 	 * @param project Java project context used for resolution, or <code>null</code>
804 	 *  if to be resolved in the context of the launch configuration this entry
805 	 *  is referenced in
806 	 * @return runtime classpath entry
807 	 * @exception CoreException if unable to construct a runtime classpath entry
808 	 * @since 3.0
809 	 */
newRuntimeContainerClasspathEntry(IPath path, int classpathProperty, IJavaProject project)810 	public static IRuntimeClasspathEntry newRuntimeContainerClasspathEntry(IPath path, int classpathProperty, IJavaProject project) throws CoreException {
811 		RuntimeClasspathEntry entry = new RuntimeClasspathEntry(JavaCore.newContainerEntry(path), classpathProperty);
812 		entry.setJavaProject(project);
813 		return entry;
814 	}
815 
816 	/**
817 	 * Returns a runtime classpath entry constructed from the given memento.
818 	 *
819 	 * @param memento a memento for a runtime classpath entry
820 	 * @return runtime classpath entry
821 	 * @exception CoreException if unable to construct a runtime classpath entry
822 	 * @since 2.0
823 	 */
newRuntimeClasspathEntry(String memento)824 	public static IRuntimeClasspathEntry newRuntimeClasspathEntry(String memento) throws CoreException {
825 		try {
826 			Element root = null;
827 			DocumentBuilder parser = LaunchingPlugin.getParser();
828 			StringReader reader = new StringReader(memento);
829 			InputSource source = new InputSource(reader);
830 			root = parser.parse(source).getDocumentElement();
831 
832 			String id = root.getAttribute("id"); //$NON-NLS-1$
833 			if (id == null || id.length() == 0) {
834 				// assume an old format
835 				return new RuntimeClasspathEntry(root);
836 			}
837 			// get the extension & create a new one
838 			IRuntimeClasspathEntry2 entry = LaunchingPlugin.getDefault().newRuntimeClasspathEntry(id);
839 			NodeList list = root.getChildNodes();
840 			Node node = null;
841 			Element element = null;
842 			for (int i = 0; i < list.getLength(); i++) {
843 				node = list.item(i);
844 				if (node.getNodeType() == Node.ELEMENT_NODE) {
845 					element = (Element)node;
846 					if ("memento".equals(element.getNodeName())) { //$NON-NLS-1$
847 						entry.initializeFrom(element);
848 					}
849 				}
850 			}
851 			return entry;
852 		} catch (SAXException e) {
853 			abort(LaunchingMessages.JavaRuntime_32, e);
854 		} catch (IOException e) {
855 			abort(LaunchingMessages.JavaRuntime_32, e);
856 		}
857 		return null;
858 	}
859 
860 	/**
861 	 * Returns a runtime classpath entry that corresponds to the given
862 	 * classpath entry. The classpath entry may not be of type <code>CPE_SOURCE</code>
863 	 * or <code>CPE_CONTAINER</code>.
864 	 *
865 	 * @param entry a classpath entry
866 	 * @return runtime classpath entry
867 	 * @since 2.0
868 	 */
newRuntimeClasspathEntry(IClasspathEntry entry)869 	private static IRuntimeClasspathEntry newRuntimeClasspathEntry(IClasspathEntry entry) {
870 		return new RuntimeClasspathEntry(entry);
871 	}
872 
873 	/**
874 	 * Returns a runtime classpath entry that corresponds to the given classpath entry. The classpath entry may not be of type <code>CPE_SOURCE</code>
875 	 * or <code>CPE_CONTAINER</code>.
876 	 *
877 	 * @param entry
878 	 *            a classpath entry
879 	 * @return runtime classpath entry
880 	 * @since 2.0
881 	 */
newRuntimeClasspathEntry(IClasspathEntry entry, int classPathProperty)882 	private static IRuntimeClasspathEntry newRuntimeClasspathEntry(IClasspathEntry entry, int classPathProperty) {
883 		return new RuntimeClasspathEntry(entry, classPathProperty);
884 	}
885 
886 	/**
887 	 * Computes and returns the default unresolved runtime classpath for the given project.
888 	 *
889 	 * @param project
890 	 *            the {@link IJavaProject} to compute the unresolved runtime classpath for
891 	 * @return runtime classpath entries
892 	 * @exception CoreException
893 	 *                if unable to compute the runtime classpath
894 	 * @see IRuntimeClasspathEntry
895 	 * @since 2.0
896 	 */
computeUnresolvedRuntimeClasspath(IJavaProject project)897 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(IJavaProject project) throws CoreException {
898 		return computeUnresolvedRuntimeClasspath(project, false);
899 	}
900 
901 	/**
902 	 * Computes and returns the default unresolved runtime classpath for the given project.
903 	 *
904 	 * @param project
905 	 *            the {@link IJavaProject} to compute the unresolved runtime classpath for
906 	 * @param excludeTestCode
907 	 *            if true, output folders corresponding to test sources and test dependencies are excluded
908 	 * @return runtime classpath entries
909 	 * @exception CoreException
910 	 *                if unable to compute the runtime classpath
911 	 * @see IRuntimeClasspathEntry
912 	 * @since 3.10
913 	 */
computeUnresolvedRuntimeClasspath(IJavaProject project, boolean excludeTestCode)914 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(IJavaProject project, boolean excludeTestCode) throws CoreException {
915 		IClasspathEntry[] entries = project.getRawClasspath();
916 		List<IRuntimeClasspathEntry> classpathEntries = new ArrayList<>(3);
917 		for (int i = 0; i < entries.length; i++) {
918 			IClasspathEntry entry = entries[i];
919 			switch (entry.getEntryKind()) {
920 				case IClasspathEntry.CPE_CONTAINER:
921 					IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
922 					if (container != null) {
923 						switch (container.getKind()) {
924 							case IClasspathContainer.K_APPLICATION:
925 								// don't look at application entries
926 								break;
927 							case IClasspathContainer.K_DEFAULT_SYSTEM:
928 								classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.STANDARD_CLASSES, project));
929 								break;
930 							case IClasspathContainer.K_SYSTEM:
931 								classpathEntries.add(newRuntimeContainerClasspathEntry(container.getPath(), IRuntimeClasspathEntry.BOOTSTRAP_CLASSES, project));
932 								break;
933 						}
934 					}
935 					break;
936 				case IClasspathEntry.CPE_VARIABLE:
937 					if (JRELIB_VARIABLE.equals(entry.getPath().segment(0))) {
938 						IRuntimeClasspathEntry jre = newVariableRuntimeClasspathEntry(entry.getPath());
939 						jre.setClasspathProperty(IRuntimeClasspathEntry.STANDARD_CLASSES);
940 						classpathEntries.add(jre);
941 					}
942 					break;
943 				default:
944 					break;
945 			}
946 		}
947 		classpathEntries.add(newDefaultProjectClasspathEntry(project));
948 		return classpathEntries.toArray(new IRuntimeClasspathEntry[classpathEntries.size()]);
949 	}
950 
951 	/**
952 	 * Computes and returns the default unresolved runtime classpath and modulepath for the given project.
953 	 *
954 	 * @param project
955 	 *            the {@link IJavaProject} to compute the unresolved runtime classpath and modulepath for
956 	 * @return runtime classpath and modulepath entries
957 	 * @exception CoreException
958 	 *                if unable to compute the runtime classpath and/or modulepath
959 	 * @see IRuntimeClasspathEntry
960 	 * @since 3.10
961 	 */
computeUnresolvedRuntimeDependencies(IJavaProject project)962 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeDependencies(IJavaProject project) throws CoreException {
963 		return computeUnresolvedRuntimeDependencies(project, false);
964 	}
965 
966 	/**
967 	 * Computes and returns the default unresolved runtime classpath and modulepath for the given project.
968 	 *
969 	 * @param project
970 	 *            the {@link IJavaProject} to compute the unresolved runtime classpath and modulepath for
971 	 * @param excludeTestCode
972 	 *            if true, output folders corresponding to test sources and test dependencies are excluded
973 	 * @return runtime classpath and modulepath entries
974 	 * @exception CoreException
975 	 *                if unable to compute the runtime classpath and/or modulepath
976 	 * @see IRuntimeClasspathEntry
977 	 * @since 3.10
978 	 */
computeUnresolvedRuntimeDependencies(IJavaProject project, boolean excludeTestCode)979 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeDependencies(IJavaProject project, boolean excludeTestCode) throws CoreException {
980 		IClasspathEntry entry1 = JavaCore.newProjectEntry(project.getProject().getFullPath());
981 		List<Object> classpathEntries = new ArrayList<>(5);
982 		List<IClasspathEntry> expanding = new ArrayList<>(5);
983 		boolean exportedEntriesOnly = Platform.getPreferencesService().getBoolean(LaunchingPlugin.ID_PLUGIN, JavaRuntime.PREF_ONLY_INCLUDE_EXPORTED_CLASSPATH_ENTRIES, false, null);
984 		DefaultProjectClasspathEntry.expandProject(entry1, classpathEntries, expanding, excludeTestCode, exportedEntriesOnly, project, true);
985 		IRuntimeClasspathEntry[] runtimeEntries = new IRuntimeClasspathEntry[classpathEntries.size()];
986 		for (int i = 0; i < runtimeEntries.length; i++) {
987 			Object e = classpathEntries.get(i);
988 			if (e instanceof IClasspathEntry) {
989 				IClasspathEntry cpe = (IClasspathEntry) e;
990 				if (cpe == entry1) {
991 					if (isModularProject(project)) {
992 						runtimeEntries[i] = new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.MODULE_PATH);
993 					} else {
994 						runtimeEntries[i] = new RuntimeClasspathEntry(entry1, IRuntimeClasspathEntry.CLASS_PATH);
995 					}
996 				} else {
997 					runtimeEntries[i] = new RuntimeClasspathEntry(cpe);
998 					DefaultProjectClasspathEntry.adjustClasspathProperty(runtimeEntries[i], cpe);
999 				}
1000 			} else {
1001 				runtimeEntries[i] = (IRuntimeClasspathEntry) e;
1002 			}
1003 		}
1004 		List<IRuntimeClasspathEntry> ordered = new ArrayList<>(runtimeEntries.length);
1005 		for (int i = 0; i < runtimeEntries.length; i++) {
1006 			if (runtimeEntries[i].getClasspathProperty() != IRuntimeClasspathEntry.STANDARD_CLASSES
1007 					&& runtimeEntries[i].getClasspathProperty() != IRuntimeClasspathEntry.BOOTSTRAP_CLASSES) {
1008 				ordered.add(runtimeEntries[i]);
1009 			}
1010 		}
1011 		IRuntimeClasspathEntry jreEntry = JavaRuntime.computeModularJREEntry(project);
1012 		if (jreEntry != null) { // With some jre stub jars don't have jre entries
1013 			ordered.add(jreEntry);
1014 		}
1015 		return ordered.toArray(new IRuntimeClasspathEntry[ordered.size()]);
1016 	}
1017 
1018 	/**
1019 	 * Checks if classpath entry is modular and project is modular .
1020 	 *
1021 	 * @param entry
1022 	 *            the classpath entry
1023 	 * @return boolean <code>true</code> if entry is module else <code>false</code>
1024 	 * @since 3.10
1025 	 */
isModule(IClasspathEntry entry, IJavaProject proj)1026 	public static boolean isModule(IClasspathEntry entry, IJavaProject proj) {
1027 		if (entry == null) {
1028 			return false;
1029 		}
1030 		if (!isModularProject(proj)) {
1031 			return false;
1032 		}
1033 
1034 		for (IClasspathAttribute classpathAttribute : entry.getExtraAttributes()) {
1035 			if (classpathAttribute.getName().equals(IClasspathAttribute.MODULE) && "true".equals(classpathAttribute.getValue())) {//$NON-NLS-1$
1036 				return true;
1037 			}
1038 		}
1039 		return false;
1040 
1041 	}
1042 
1043 	/**
1044 	 * Checks if configuration JRE is greater than 8.
1045 	 *
1046 	 * @param configuration
1047 	 *            the launch configuration
1048 	 * @return boolean <code>true</code> if jre used in configuration is greater than 8 else <code>false</code>
1049 	 * @since 3.10
1050 	 */
isModularConfiguration(ILaunchConfiguration configuration)1051 	public static boolean isModularConfiguration(ILaunchConfiguration configuration) {
1052 
1053 		try {
1054 			IVMInstall vm = JavaRuntime.computeVMInstall(configuration);
1055 			return isModularJava(vm);
1056 		}
1057 		catch (CoreException e) {
1058 			e.printStackTrace();
1059 		}
1060 		return false;
1061 
1062 	}
1063 
1064 	/**
1065 	 * Checks if vm install is modular( version greater than 8).
1066 	 *
1067 	 * @param vm
1068 	 *            the vm install
1069 	 * @return boolean <code>true</code> if vm install is modular else <code>false</code>
1070 	 * @since 3.10
1071 	 */
isModularJava(IVMInstall vm)1072 	public static boolean isModularJava(IVMInstall vm) {
1073 		if (compareJavaVersions(vm, JavaCore.VERSION_1_8) > 0) {
1074 			return true;
1075 		}
1076 		return false;
1077 	}
1078 
1079 	/**
1080 	 * Compares the version of vm and a version of the Java platform.
1081 	 *
1082 	 * @param vm
1083 	 *            IVMInstall to be compared
1084 	 * @param ver
1085 	 *            version to be compared
1086 	 * @return the value {@code 0} if both versions are the same; a value less than {@code 0} if <code>vm</code> is smaller than <code>ver</code>; and
1087 	 *         a value greater than {@code 0} if <code>vm</code> is higher than <code>ver</code>; a value {@code -1} in case of any exceptions;
1088 	 *
1089 	 * @since 3.10
1090 	 */
compareJavaVersions(IVMInstall vm, String ver)1091 	public static int compareJavaVersions(IVMInstall vm, String ver) {
1092 		if (vm instanceof AbstractVMInstall) {
1093 			AbstractVMInstall install = (AbstractVMInstall) vm;
1094 			String vmver = install.getJavaVersion();
1095 			if (vmver == null) {
1096 				return -1;
1097 			}
1098 			// versionToJdkLevel only handles 3 char versions = 1.5, 1.6, 1.7, etc
1099 			if (vmver.length() > 3) {
1100 				vmver = vmver.substring(0, 3);
1101 			}
1102 			return JavaCore.compareJavaVersions(vmver, ver);
1103 		}
1104 		return -1;
1105 
1106 	}
1107 	/**
1108 	 * Checks if project entry is modular
1109 	 *
1110 	 * @param proj
1111 	 *            the project
1112 	 * @return boolean <code>true</code> if project is modular else <code>false</code>
1113 	 * @since 3.10
1114 	 */
isModularProject(IJavaProject proj)1115 	public static boolean isModularProject(IJavaProject proj) {
1116 
1117 		IModuleDescription module;
1118 		try {
1119 			module = proj == null ? null : proj.getModuleDescription();
1120 			String modName = module == null ? null : module.getElementName();
1121 			if (modName != null && modName.length() > 0) {
1122 				return true;
1123 			}
1124 		}
1125 		catch (JavaModelException e) {
1126 		}
1127 		return false;
1128 	}
1129 
1130 	/**
1131 	 * Computes and returns the unresolved source lookup path for the given launch
1132 	 * configuration.
1133 	 *
1134 	 * @param configuration launch configuration
1135 	 * @return runtime classpath entries
1136 	 * @exception CoreException if unable to compute the source lookup path
1137 	 * @since 2.0
1138 	 */
computeUnresolvedSourceLookupPath(ILaunchConfiguration configuration)1139 	public static IRuntimeClasspathEntry[] computeUnresolvedSourceLookupPath(ILaunchConfiguration configuration) throws CoreException {
1140 		return getSourceLookupPathProvider(configuration).computeUnresolvedClasspath(configuration);
1141 	}
1142 
1143 	/**
1144 	 * Resolves the given source lookup path, returning the resolved source lookup path
1145 	 * in the context of the given launch configuration.
1146 	 *
1147 	 * @param entries unresolved entries
1148 	 * @param configuration launch configuration
1149 	 * @return resolved entries
1150 	 * @exception CoreException if unable to resolve the source lookup path
1151 	 * @since 2.0
1152 	 */
resolveSourceLookupPath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration)1153 	public static IRuntimeClasspathEntry[] resolveSourceLookupPath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
1154 		return getSourceLookupPathProvider(configuration).resolveClasspath(entries, configuration);
1155 	}
1156 
1157 	/**
1158 	 * Returns the classpath provider for the given launch configuration.
1159 	 *
1160 	 * @param configuration launch configuration
1161 	 * @return classpath provider
1162 	 * @exception CoreException if unable to resolve the path provider
1163 	 * @since 2.0
1164 	 */
getClasspathProvider(ILaunchConfiguration configuration)1165 	public static IRuntimeClasspathProvider getClasspathProvider(ILaunchConfiguration configuration) throws CoreException {
1166 		String providerId = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, (String)null);
1167 		IRuntimeClasspathProvider provider = null;
1168 		if (providerId == null) {
1169 			provider = fgDefaultClasspathProvider;
1170 		} else {
1171 			provider = getClasspathProviders().get(providerId);
1172 			if (provider == null) {
1173 				abort(NLS.bind(LaunchingMessages.JavaRuntime_26, new String[]{providerId}), null);
1174 			}
1175 		}
1176 		return provider;
1177 	}
1178 
1179 	/**
1180 	 * Returns the source lookup path provider for the given launch configuration.
1181 	 *
1182 	 * @param configuration launch configuration
1183 	 * @return source lookup path provider
1184 	 * @exception CoreException if unable to resolve the path provider
1185 	 * @since 2.0
1186 	 */
getSourceLookupPathProvider(ILaunchConfiguration configuration)1187 	public static IRuntimeClasspathProvider getSourceLookupPathProvider(ILaunchConfiguration configuration) throws CoreException {
1188 		String providerId = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_SOURCE_PATH_PROVIDER, (String)null);
1189 		IRuntimeClasspathProvider provider = null;
1190 		if (providerId == null) {
1191 			provider = fgDefaultSourcePathProvider;
1192 		} else {
1193 			provider = getClasspathProviders().get(providerId);
1194 			if (provider == null) {
1195 				abort(NLS.bind(LaunchingMessages.JavaRuntime_27, new String[]{providerId}), null);
1196 			}
1197 		}
1198 		return provider;
1199 	}
1200 
1201 	/**
1202 	 * Returns resolved entries for the given entry in the context of the given
1203 	 * launch configuration. If the entry is of kind
1204 	 * <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
1205 	 * resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
1206 	 * and the associated Java project specifies non-default output locations,
1207 	 * the corresponding output locations are returned. Otherwise, the given
1208 	 * entry is returned.
1209 	 * <p>
1210 	 * If the given entry is a variable entry, and a resolver is not registered,
1211 	 * the entry itself is returned. If the given entry is a container, and a
1212 	 * resolver is not registered, resolved runtime classpath entries are calculated
1213 	 * from the associated container classpath entries, in the context of the project
1214 	 * associated with the given launch configuration.
1215 	 * </p>
1216 	 * @param entry runtime classpath entry
1217 	 * @param configuration launch configuration
1218 	 * @return resolved runtime classpath entry
1219 	 * @exception CoreException if unable to resolve
1220 	 * @see IRuntimeClasspathEntryResolver
1221 	 * @since 2.0
1222 	 */
resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration)1223 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, ILaunchConfiguration configuration) throws CoreException {
1224 		boolean excludeTestCode = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_EXCLUDE_TEST_CODE, false);
1225 		switch (entry.getType()) {
1226 			case IRuntimeClasspathEntry.PROJECT:
1227 				// if the project has multiple output locations, they must be returned
1228 				IResource resource = entry.getResource();
1229 				if (resource instanceof IProject) {
1230 					IProject p = (IProject)resource;
1231 					IJavaProject project = JavaCore.create(p);
1232 					if (project == null || !p.isOpen() || !project.exists()) {
1233 						return new IRuntimeClasspathEntry[0];
1234 					}
1235 					IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
1236 					IRuntimeClasspathEntry[] entries = resolveOutputLocations(project, entry.getClasspathProperty(), attributes, excludeTestCode);
1237 					if (entries != null) {
1238 						return entries;
1239 					}
1240 				} else {
1241 					if (isOptional(entry.getClasspathEntry())) {
1242 						return new IRuntimeClasspathEntry[] {};
1243 					}
1244 					abort(NLS.bind(LaunchingMessages.JavaRuntime_Classpath_references_non_existant_project___0__3, new String[]{entry.getPath().lastSegment()}), null);
1245 				}
1246 				break;
1247 			case IRuntimeClasspathEntry.VARIABLE:
1248 				IRuntimeClasspathEntryResolver resolver = getVariableResolver(entry.getVariableName());
1249 				if (resolver == null) {
1250 					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, null, false, configuration);
1251 					if (resolved != null) {
1252 						return resolved;
1253 					}
1254 					break;
1255 				}
1256 				return resolver.resolveRuntimeClasspathEntry(entry, configuration);
1257 			case IRuntimeClasspathEntry.CONTAINER:
1258 				resolver = getContainerResolver(entry.getVariableName());
1259 				if (resolver == null) {
1260 					return computeDefaultContainerEntries(entry, configuration, excludeTestCode);
1261 				}
1262 				return resolver.resolveRuntimeClasspathEntry(entry, configuration);
1263 			case IRuntimeClasspathEntry.ARCHIVE:
1264 				// verify the archive exists
1265 				String location = entry.getLocation();
1266 				if (location != null) {
1267 					File file = new File(location);
1268 					if (file.exists()) {
1269 						break;
1270 					}
1271 				}
1272 				if (isOptional(entry.getClasspathEntry())) {
1273 					return new IRuntimeClasspathEntry[] {};
1274 				}
1275 				abort(NLS.bind(LaunchingMessages.JavaRuntime_Classpath_references_non_existant_archive___0__4, new String[] { entry.getPath().toString() }), null);
1276 			case IRuntimeClasspathEntry.OTHER:
1277 				resolver = getContributedResolver(((IRuntimeClasspathEntry2)entry).getTypeId());
1278 				return resolver.resolveRuntimeClasspathEntry(entry, configuration);
1279 			default:
1280 				break;
1281 		}
1282 		return new IRuntimeClasspathEntry[] {entry};
1283 	}
1284 
isOptional(IClasspathEntry entry)1285 	private static boolean isOptional(IClasspathEntry entry) {
1286 		IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
1287 		for (int i = 0, length = extraAttributes.length; i < length; i++) {
1288 			IClasspathAttribute attribute = extraAttributes[i];
1289 			if (IClasspathAttribute.OPTIONAL.equals(attribute.getName()) && Boolean.parseBoolean(attribute.getValue())) {
1290 				return true;
1291 			}
1292 		}
1293 		return false;
1294 	}
1295 
1296 	/**
1297 	 * Default resolution for a classpath variable - resolve to an archive. Only one of project/configuration can be non-null.
1298 	 *
1299 	 * @param entry
1300 	 *            the {@link IRuntimeClasspathEntry} to try and resolve
1301 	 * @param project
1302 	 *            the project context or <code>null</code>
1303 	 * @param excludeTestCode
1304 	 *            if true, exclude test-code (only used if project is non-null)
1305 	 * @param configuration
1306 	 *            configuration context or <code>null</code>
1307 	 * @return IRuntimeClasspathEntry[]
1308 	 * @throws CoreException
1309 	 *             if a problem is encountered trying to resolve the given classpath entry
1310 	 */
resolveVariableEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode, ILaunchConfiguration configuration)1311 	private static IRuntimeClasspathEntry[] resolveVariableEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode, ILaunchConfiguration configuration) throws CoreException {
1312 		// default resolution - an archive
1313 		IPath archPath = JavaCore.getClasspathVariable(entry.getVariableName());
1314 		if (archPath != null) {
1315 			if (entry.getPath().segmentCount() > 1) {
1316 				archPath = archPath.append(entry.getPath().removeFirstSegments(1));
1317 			}
1318 			IPath srcPath = null;
1319 			IPath srcVar = entry.getSourceAttachmentPath();
1320 			IPath srcRootPath = null;
1321 			IPath srcRootVar = entry.getSourceAttachmentRootPath();
1322 			if (archPath != null && !archPath.isEmpty()) {
1323 				if (srcVar != null && !srcVar.isEmpty()) {
1324 					srcPath = JavaCore.getClasspathVariable(srcVar.segment(0));
1325 					if (srcPath != null) {
1326 						if (srcVar.segmentCount() > 1) {
1327 							srcPath = srcPath.append(srcVar.removeFirstSegments(1));
1328 						}
1329 						if (srcRootVar != null && !srcRootVar.isEmpty()) {
1330 							srcRootPath = JavaCore.getClasspathVariable(srcRootVar.segment(0));
1331 							if (srcRootPath != null) {
1332 								if (srcRootVar.segmentCount() > 1) {
1333 									srcRootPath = srcRootPath.append(srcRootVar.removeFirstSegments(1));
1334 								}
1335 							}
1336 						}
1337 					}
1338 				}
1339 				// now resolve the archive (recursively)
1340 				IClasspathEntry cpEntry = entry.getClasspathEntry();
1341 				IClasspathEntry archEntry = JavaCore.newLibraryEntry(archPath, srcPath, srcRootPath, null, cpEntry.getExtraAttributes(), cpEntry.isExported());
1342 				IRuntimeClasspathEntry runtimeArchEntry = newRuntimeClasspathEntry(archEntry);
1343 				runtimeArchEntry.setClasspathProperty(entry.getClasspathProperty());
1344 				if (configuration == null) {
1345 					return resolveRuntimeClasspathEntry(runtimeArchEntry, project, excludeTestCode);
1346 				}
1347 				return resolveRuntimeClasspathEntry(runtimeArchEntry, configuration);
1348 			}
1349 		}
1350 		return null;
1351 	}
1352 
1353 	/**
1354 	 * Returns runtime classpath entries corresponding to the output locations of the given project, or null if the project only uses the default
1355 	 * output location.
1356 	 *
1357 	 * @param project
1358 	 *            the {@link IJavaProject} to resolve the output locations for
1359 	 * @param classpathProperty
1360 	 *            the type of classpath entries to create
1361 	 * @param attributes
1362 	 *            extra attributes of the original classpath entry
1363 	 * @param excludeTestCode
1364 	 *            if true, output folders corresponding to test sources are excluded
1365 	 *
1366 	 * @return IRuntimeClasspathEntry[] or <code>null</code>
1367 	 * @throws CoreException
1368 	 *             if output resolution encounters a problem
1369 	 */
resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode)1370 	private static IRuntimeClasspathEntry[] resolveOutputLocations(IJavaProject project, int classpathProperty, IClasspathAttribute[] attributes, boolean excludeTestCode) throws CoreException {
1371 		List<IPath> nonDefault = new ArrayList<>();
1372 		boolean defaultUsedByNonTest = false;
1373 		if (project.exists() && project.getProject().isOpen()) {
1374 			IClasspathEntry entries[] = project.getRawClasspath();
1375 			for (int i = 0; i < entries.length; i++) {
1376 				IClasspathEntry classpathEntry = entries[i];
1377 				if (classpathEntry.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
1378 					IPath path = classpathEntry.getOutputLocation();
1379 					if (path != null) {
1380 						if (!(excludeTestCode && classpathEntry.isTest())) {
1381 							nonDefault.add(path);
1382 						}
1383 					} else {
1384 						if (!classpathEntry.isTest()) {
1385 							defaultUsedByNonTest = true;
1386 						}
1387 					}
1388 				}
1389 			}
1390 		}
1391 		boolean isModular = project.getOwnModuleDescription() != null;
1392 		if (nonDefault.isEmpty() && !isModular && !excludeTestCode) {
1393 			// return here only if non-modular, because patch-module might be needed otherwise
1394 			return null;
1395 		}
1396 		// add the default location if not already included
1397 		IPath def = project.getOutputLocation();
1398 		if (!excludeTestCode || defaultUsedByNonTest) {
1399 			if (!nonDefault.contains(def)) {
1400 				nonDefault.add(def);
1401 			}
1402 		}
1403 		IRuntimeClasspathEntry[] locations = new IRuntimeClasspathEntry[nonDefault.size()];
1404 		for (int i = 0; i < locations.length; i++) {
1405 			IClasspathEntry newEntry = JavaCore.newLibraryEntry(nonDefault.get(i), null, null, null, attributes, false);
1406 			locations[i] = new RuntimeClasspathEntry(newEntry);
1407 			if (isModular && !containsModuleInfo(locations[i])) {
1408 				locations[i].setClasspathProperty(IRuntimeClasspathEntry.PATCH_MODULE);
1409 				((RuntimeClasspathEntry) locations[i]).setJavaProject(project);
1410 			} else {
1411 				locations[i].setClasspathProperty(classpathProperty);
1412 			}
1413 		}
1414 		return locations;
1415 	}
1416 
containsModuleInfo(IRuntimeClasspathEntry entry)1417 	private static boolean containsModuleInfo(IRuntimeClasspathEntry entry) {
1418 		return new File(entry.getLocation() + File.separator + "module-info.class").exists(); //$NON-NLS-1$
1419 	}
1420 
1421 	/**
1422 	 * Returns resolved entries for the given entry in the context of the given
1423 	 * Java project. If the entry is of kind
1424 	 * <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
1425 	 * resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
1426 	 * and the associated Java project specifies non-default output locations,
1427 	 * the corresponding output locations are returned. Otherwise, the given
1428 	 * entry is returned.
1429 	 * <p>
1430 	 * If the given entry is a variable entry, and a resolver is not registered,
1431 	 * the entry itself is returned. If the given entry is a container, and a
1432 	 * resolver is not registered, resolved runtime classpath entries are calculated
1433 	 * from the associated container classpath entries, in the context of the
1434 	 * given project.
1435 	 * </p>
1436 	 * @param entry runtime classpath entry
1437 	 * @param project Java project context
1438 	 * @return resolved runtime classpath entry
1439 	 * @exception CoreException if unable to resolve
1440 	 * @see IRuntimeClasspathEntryResolver
1441 	 * @since 2.0
1442 	 */
resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project)1443 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project) throws CoreException {
1444 		return resolveRuntimeClasspathEntry(entry, project, false);
1445 	}
1446 
1447 	/**
1448 	 * Returns resolved entries for the given entry in the context of the given
1449 	 * Java project. If the entry is of kind
1450 	 * <code>VARIABLE</code> or <code>CONTAINER</code>, variable and container
1451 	 * resolvers are consulted. If the entry is of kind <code>PROJECT</code>,
1452 	 * and the associated Java project specifies non-default output locations,
1453 	 * the corresponding output locations are returned. Otherwise, the given
1454 	 * entry is returned.
1455 	 * <p>
1456 	 * If the given entry is a variable entry, and a resolver is not registered,
1457 	 * the entry itself is returned. If the given entry is a container, and a
1458 	 * resolver is not registered, resolved runtime classpath entries are calculated
1459 	 * from the associated container classpath entries, in the context of the
1460 	 * given project.
1461 	 * </p>
1462 	 * @param entry runtime classpath entry
1463 	 * @param project Java project context
1464 	 * @param excludeTestCode
1465 	 *            if true, output folders corresponding to test sources and test dependencies are excluded
1466 	 * @return resolved runtime classpath entry
1467 	 * @exception CoreException if unable to resolve
1468 	 * @see IRuntimeClasspathEntryResolver
1469 	 * @since 3.10
1470 	 */
resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode)1471 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspathEntry(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
1472 		switch (entry.getType()) {
1473 			case IRuntimeClasspathEntry.PROJECT:
1474 				// if the project has multiple output locations, they must be returned
1475 				IResource resource = entry.getResource();
1476 				if (resource instanceof IProject) {
1477 					IProject p = (IProject)resource;
1478 					IJavaProject jp = JavaCore.create(p);
1479 					if (jp != null && p.isOpen() && jp.exists()) {
1480 						IClasspathAttribute[] attributes = entry.getClasspathEntry().getExtraAttributes();
1481 						IRuntimeClasspathEntry[] entries = resolveOutputLocations(jp, entry.getClasspathProperty(), attributes, excludeTestCode);
1482 						if (entries != null) {
1483 							return entries;
1484 						}
1485 					} else {
1486 						return new IRuntimeClasspathEntry[0];
1487 					}
1488 				}
1489 				break;
1490 			case IRuntimeClasspathEntry.VARIABLE:
1491 				IRuntimeClasspathEntryResolver resolver = getVariableResolver(entry.getVariableName());
1492 				if (resolver == null) {
1493 					IRuntimeClasspathEntry[] resolved = resolveVariableEntry(entry, project, excludeTestCode, null);
1494 					if (resolved != null) {
1495 						return resolved;
1496 					}
1497 					break;
1498 				}
1499 				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
1500 			case IRuntimeClasspathEntry.CONTAINER:
1501 				resolver = getContainerResolver(entry.getVariableName());
1502 				if (resolver == null) {
1503 					return computeDefaultContainerEntries(entry, project, excludeTestCode);
1504 				}
1505 				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
1506 			case IRuntimeClasspathEntry.OTHER:
1507 				resolver = getContributedResolver(((IRuntimeClasspathEntry2)entry).getTypeId());
1508 				return resolver.resolveRuntimeClasspathEntry(entry, project, excludeTestCode);
1509 			default:
1510 				break;
1511 		}
1512 		return new IRuntimeClasspathEntry[] {entry};
1513 	}
1514 
1515 	/**
1516 	 * Performs default resolution for a container entry. Delegates to the Java model.
1517 	 *
1518 	 * @param entry
1519 	 *            the {@link IRuntimeClasspathEntry} to compute default container entries for
1520 	 * @param config
1521 	 *            the backing {@link ILaunchConfiguration}
1522 	 * @param excludeTestCode
1523 	 *            if true, output folders corresponding to test sources and test dependencies are excluded
1524 	 * @return the complete listing of default container entries or an empty list, never <code>null</code>
1525 	 * @throws CoreException
1526 	 *             if the computation encounters a problem
1527 	 */
computeDefaultContainerEntries(IRuntimeClasspathEntry entry, ILaunchConfiguration config, boolean excludeTestCode)1528 	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, ILaunchConfiguration config, boolean excludeTestCode) throws CoreException {
1529 		IJavaProject project = entry.getJavaProject();
1530 		if (project == null) {
1531 			project = getJavaProject(config);
1532 		}
1533 		return computeDefaultContainerEntries(entry, project, excludeTestCode);
1534 	}
1535 
1536 	/**
1537 	 * Performs default resolution for a container entry. Delegates to the Java model.
1538 	 *
1539 	 * @param entry
1540 	 *            the {@link IRuntimeClasspathEntry} to compute default container entries for
1541 	 * @param project
1542 	 *            the backing {@link IJavaProject}
1543 	 * @param excludeTestCode
1544 	 *            if true, output folders corresponding to test sources and test dependencies are excluded
1545 	 * @return the complete listing of default container entries or an empty list, never <code>null</code>
1546 	 * @throws CoreException
1547 	 *             if the computation encounters a problem
1548 	 */
computeDefaultContainerEntries(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode)1549 	private static IRuntimeClasspathEntry[] computeDefaultContainerEntries(IRuntimeClasspathEntry entry, IJavaProject project, boolean excludeTestCode) throws CoreException {
1550 		if (project == null || entry == null) {
1551 			// cannot resolve without entry or project context
1552 			return new IRuntimeClasspathEntry[0];
1553 		}
1554 		IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
1555 		if (container == null) {
1556 			abort(NLS.bind(LaunchingMessages.JavaRuntime_Could_not_resolve_classpath_container___0__1, new String[]{entry.getPath().toString()}), null);
1557 			// execution will not reach here - exception will be thrown
1558 			return null;
1559 		}
1560 		IClasspathEntry[] cpes = container.getClasspathEntries();
1561 		int property = -1;
1562 		switch (container.getKind()) {
1563 			case IClasspathContainer.K_APPLICATION:
1564 				switch (entry.getClasspathProperty()) {
1565 					case IRuntimeClasspathEntry.MODULE_PATH:
1566 						property = IRuntimeClasspathEntry.MODULE_PATH;
1567 						break;
1568 					case IRuntimeClasspathEntry.CLASS_PATH:
1569 						property = IRuntimeClasspathEntry.CLASS_PATH;
1570 						break;
1571 					default:
1572 						property = IRuntimeClasspathEntry.USER_CLASSES;
1573 						break;
1574 				}
1575 				break;
1576 
1577 			case IClasspathContainer.K_DEFAULT_SYSTEM:
1578 				property = IRuntimeClasspathEntry.STANDARD_CLASSES;
1579 				break;
1580 			case IClasspathContainer.K_SYSTEM:
1581 				property = IRuntimeClasspathEntry.BOOTSTRAP_CLASSES;
1582 				break;
1583 		}
1584 		List<IRuntimeClasspathEntry> resolved = new ArrayList<>(cpes.length);
1585 		List<IJavaProject> projects = fgProjects.get();
1586 		Integer count = fgEntryCount.get();
1587 		if (projects == null) {
1588 			projects = new ArrayList<>();
1589 			fgProjects.set(projects);
1590 			count = Integer.valueOf(0);
1591 		}
1592 		int intCount = count.intValue();
1593 		intCount++;
1594 		fgEntryCount.set(Integer.valueOf(intCount));
1595 		try {
1596 			for (int i = 0; i < cpes.length; i++) {
1597 				IClasspathEntry cpe = cpes[i];
1598 				if (cpe.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
1599 					IProject p = ResourcesPlugin.getWorkspace().getRoot().getProject(cpe.getPath().segment(0));
1600 					IJavaProject jp = JavaCore.create(p);
1601 					if (!projects.contains(jp)) {
1602 						projects.add(jp);
1603 						IRuntimeClasspathEntry classpath = newDefaultProjectClasspathEntry(jp);
1604 						IRuntimeClasspathEntry[] entries = resolveRuntimeClasspathEntry(classpath, jp, excludeTestCode);
1605 						for (int j = 0; j < entries.length; j++) {
1606 							IRuntimeClasspathEntry e = entries[j];
1607 							if (!resolved.contains(e)) {
1608 								resolved.add(entries[j]);
1609 							}
1610 						}
1611 					}
1612 				} else {
1613 					IRuntimeClasspathEntry e = newRuntimeClasspathEntry(cpe);
1614 					if (!resolved.contains(e)) {
1615 						resolved.add(e);
1616 					}
1617 				}
1618 			}
1619 		} finally {
1620 			intCount--;
1621 			if (intCount == 0) {
1622 				fgProjects.set(null);
1623 				fgEntryCount.set(null);
1624 			} else {
1625 				fgEntryCount.set(Integer.valueOf(intCount));
1626 			}
1627 		}
1628 		// set classpath property
1629 		IRuntimeClasspathEntry[] result = new IRuntimeClasspathEntry[resolved.size()];
1630 		for (int i = 0; i < result.length; i++) {
1631 			result[i] = resolved.get(i);
1632 			result[i].setClasspathProperty(property);
1633 		}
1634 		return result;
1635 	}
1636 
1637 	/**
1638 	 * Computes and returns the unresolved class path for the given launch configuration.
1639 	 * Variable and container entries are unresolved.
1640 	 *
1641 	 * @param configuration launch configuration
1642 	 * @return unresolved runtime classpath entries
1643 	 * @exception CoreException if unable to compute the classpath
1644 	 * @since 2.0
1645 	 */
computeUnresolvedRuntimeClasspath(ILaunchConfiguration configuration)1646 	public static IRuntimeClasspathEntry[] computeUnresolvedRuntimeClasspath(ILaunchConfiguration configuration) throws CoreException {
1647 		return getClasspathProvider(configuration).computeUnresolvedClasspath(configuration);
1648 	}
1649 
1650 	/**
1651 	 * Resolves the given classpath, returning the resolved classpath
1652 	 * in the context of the given launch configuration.
1653 	 *
1654 	 * @param entries unresolved classpath
1655 	 * @param configuration launch configuration
1656 	 * @return resolved runtime classpath entries
1657 	 * @exception CoreException if unable to compute the classpath
1658 	 * @since 2.0
1659 	 */
resolveRuntimeClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration)1660 	public static IRuntimeClasspathEntry[] resolveRuntimeClasspath(IRuntimeClasspathEntry[] entries, ILaunchConfiguration configuration) throws CoreException {
1661 		if (isModularConfiguration(configuration)) {
1662 			IRuntimeClasspathEntry[] entries1 = getClasspathProvider(configuration).resolveClasspath(entries, configuration);
1663 			ArrayList<IRuntimeClasspathEntry> entries2 = new ArrayList<>(entries1.length);
1664 			for (IRuntimeClasspathEntry entry : entries1) {
1665 				switch (entry.getClasspathEntry().getEntryKind()) {
1666 					case IClasspathEntry.CPE_LIBRARY:
1667 						try {
1668 							IJavaProject project = JavaRuntime.getJavaProject(configuration);
1669 							if (project == null) {
1670 								entries2.add(entry);
1671 							}
1672 							else {
1673 								IPackageFragmentRoot root = project.findPackageFragmentRoot(entry.getPath());
1674 								if (root == null && !entry.getPath().lastSegment().contains("jrt-fs.jar")) { //$NON-NLS-1$
1675 									entries2.add(entry);
1676 								} else if (root != null && !root.getRawClasspathEntry().getPath().segment(0).contains("JRE_CONTAINER")) { //$NON-NLS-1$
1677 									entries2.add(entry);
1678 								}
1679 							}
1680 						}
1681 						catch (CoreException ex) {
1682 							// Not a java project
1683 							if (!entry.getPath().lastSegment().contains("jrt-fs.jar")) { //$NON-NLS-1$
1684 								entries2.add(entry);
1685 							}
1686 						}
1687 						break;
1688 					default:
1689 						entries2.add(entry);
1690 
1691 				}
1692 			}
1693 			return entries2.toArray(new IRuntimeClasspathEntry[entries2.size()]);
1694 		}
1695 		return getClasspathProvider(configuration).resolveClasspath(entries, configuration);
1696 	}
1697 
1698 	/**
1699 	 * Return the <code>IJavaProject</code> referenced in the specified configuration or
1700 	 * <code>null</code> if none. This method looks for the existence of the {@link IJavaLaunchConfigurationConstants#ATTR_PROJECT_NAME}
1701 	 * attribute in the given configuration.
1702 	 *
1703 	 * @param configuration the {@link ILaunchConfiguration} to try and compute the {@link IJavaProject} from
1704 	 * @return the referenced {@link IJavaProject} or <code>null</code>
1705 	 * @exception CoreException if the referenced Java project does not exist
1706 	 * @since 2.0
1707 	 */
getJavaProject(ILaunchConfiguration configuration)1708 	public static IJavaProject getJavaProject(ILaunchConfiguration configuration) throws CoreException {
1709 		String projectName = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, (String)null);
1710 		if ((projectName == null) || (projectName.trim().length() < 1)) {
1711 			return null;
1712 		}
1713 		IJavaProject javaProject = getJavaModel().getJavaProject(projectName);
1714 		if (javaProject != null && javaProject.getProject().exists() && !javaProject.getProject().isOpen()) {
1715 			abort(NLS.bind(LaunchingMessages.JavaRuntime_28, new String[] {configuration.getName(), projectName}), IJavaLaunchConfigurationConstants.ERR_PROJECT_CLOSED, null);
1716 		}
1717 		if ((javaProject == null) || !javaProject.exists()) {
1718 			abort(NLS.bind(LaunchingMessages.JavaRuntime_Launch_configuration__0__references_non_existing_project__1___1, new String[] {configuration.getName(), projectName}), IJavaLaunchConfigurationConstants.ERR_NOT_A_JAVA_PROJECT, null);
1719 		}
1720 		return javaProject;
1721 	}
1722 
1723 	/**
1724 	 * Convenience method to get the java model.
1725 	 * @return the {@link IJavaModel} made against the {@link IWorkspaceRoot}
1726 	 */
getJavaModel()1727 	private static IJavaModel getJavaModel() {
1728 		return JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
1729 	}
1730 
1731 	/**
1732 	 * Returns the VM install for the given launch configuration.
1733 	 * The VM install is determined in the following prioritized way:
1734 	 * <ol>
1735 	 * <li>The VM install is explicitly specified on the launch configuration
1736 	 *  via the <code>ATTR_JRE_CONTAINER_PATH</code> attribute (since 3.2).</li>
1737 	 * <li>The VM install is explicitly specified on the launch configuration
1738 	 * 	via the <code>ATTR_VM_INSTALL_TYPE</code> and <code>ATTR_VM_INSTALL_ID</code>
1739 	 *  attributes.</li>
1740 	 * <li>If no explicit VM install is specified, the VM install associated with
1741 	 * 	the launch configuration's project is returned.</li>
1742 	 * <li>If no project is specified, or the project does not specify a custom
1743 	 * 	VM install, the workspace default VM install is returned.</li>
1744 	 * </ol>
1745 	 *
1746 	 * @param configuration launch configuration
1747 	 * @return VM install
1748 	 * @exception CoreException if unable to compute a VM install
1749 	 * @since 2.0
1750 	 */
computeVMInstall(ILaunchConfiguration configuration)1751 	public static IVMInstall computeVMInstall(ILaunchConfiguration configuration) throws CoreException {
1752 		String jreAttr = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, (String)null);
1753 		if (jreAttr == null) {
1754 			@SuppressWarnings("deprecation")
1755 			String type = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
1756 			if (type == null) {
1757 				IJavaProject proj = getJavaProject(configuration);
1758 				if (proj != null) {
1759 					IVMInstall vm = getVMInstall(proj);
1760 					if (vm != null) {
1761 						return vm;
1762 					}
1763 				}
1764 			} else {
1765 				@SuppressWarnings("deprecation")
1766 				String name = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
1767 				return resolveVM(type, name, configuration);
1768 			}
1769 		} else {
1770 			IPath jrePath = Path.fromPortableString(jreAttr);
1771 			IClasspathEntry entry = JavaCore.newContainerEntry(jrePath);
1772 			IRuntimeClasspathEntryResolver2 resolver = getVariableResolver(jrePath.segment(0));
1773 			if (resolver != null) {
1774 				return resolver.resolveVMInstall(entry);
1775 			}
1776 			resolver = getContainerResolver(jrePath.segment(0));
1777 			if (resolver != null) {
1778 				return resolver.resolveVMInstall(entry);
1779 			}
1780 		}
1781 
1782 		return getDefaultVMInstall();
1783 	}
1784 	/**
1785 	 * Returns the VM of the given type with the specified name.
1786 	 *
1787 	 * @param type VM type identifier
1788 	 * @param name VM name
1789 	 * @param configuration the backing {@link ILaunchConfiguration}
1790 	 * @return VM install
1791 	 * @exception CoreException if unable to resolve
1792 	 * @since 3.2
1793 	 */
resolveVM(String type, String name, ILaunchConfiguration configuration)1794 	private static IVMInstall resolveVM(String type, String name, ILaunchConfiguration configuration) throws CoreException {
1795 		IVMInstallType vt = getVMInstallType(type);
1796 		if (vt == null) {
1797 			// error type does not exist
1798 			abort(NLS.bind(LaunchingMessages.JavaRuntime_Specified_VM_install_type_does_not_exist___0__2, new String[] {type}), null);
1799 		}
1800 		IVMInstall vm = null;
1801 		// look for a name
1802 		if (name == null) {
1803 			// error - type specified without a specific install (could be an old config that specified a VM ID)
1804 			// log the error, but choose the default VM.
1805 			LaunchingPlugin.log(new Status(IStatus.WARNING, LaunchingPlugin.getUniqueIdentifier(), IJavaLaunchConfigurationConstants.ERR_UNSPECIFIED_VM_INSTALL, NLS.bind("VM not fully specified in launch configuration {0} - missing VM name. Reverting to default VM.", new String[] {configuration.getName()}), null));  //$NON-NLS-1$
1806 			return getDefaultVMInstall();
1807 		}
1808 		vm = vt.findVMInstallByName(name);
1809 		if (vm == null) {
1810 			// error - install not found
1811 			abort(NLS.bind(LaunchingMessages.JavaRuntime_Specified_VM_install_not_found__type__0___name__1__2, new String[] {vt.getName(), name}), null);
1812 		} else {
1813 			return vm;
1814 		}
1815 		// won't reach here
1816 		return null;
1817 	}
1818 
1819 	/**
1820 	 * Throws a core exception with an internal error status.
1821 	 *
1822 	 * @param message the status message
1823 	 * @param exception lower level exception associated with the
1824 	 *  error, or <code>null</code> if none
1825 	 * @throws CoreException a {@link CoreException} wrapper
1826 	 */
abort(String message, Throwable exception)1827 	private static void abort(String message, Throwable exception) throws CoreException {
1828 		abort(message, IJavaLaunchConfigurationConstants.ERR_INTERNAL_ERROR, exception);
1829 	}
1830 
1831 
1832 	/**
1833 	 * Throws a core exception with an internal error status.
1834 	 *
1835 	 * @param message the status message
1836 	 * @param code status code
1837 	 * @param exception lower level exception associated with the
1838 	 *
1839 	 *  error, or <code>null</code> if none
1840 	 * @throws CoreException a {@link CoreException} wrapper
1841 	 */
abort(String message, int code, Throwable exception)1842 	private static void abort(String message, int code, Throwable exception) throws CoreException {
1843 		throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(), code, message, exception));
1844 	}
1845 
1846 	/**
1847 	 * Computes the default application classpath entries for the given
1848 	 * project.
1849 	 *
1850 	 * @param	jproject The project to compute the classpath for
1851 	 * @return	The computed classpath. May be empty, but not null.
1852 	 * @throws	CoreException if unable to compute the default classpath
1853 	 */
computeDefaultRuntimeClassPath(IJavaProject jproject)1854 	public static String[] computeDefaultRuntimeClassPath(IJavaProject jproject) throws CoreException {
1855 		IRuntimeClasspathEntry[] unresolved = computeUnresolvedRuntimeClasspath(jproject);
1856 		// 1. remove bootpath entries
1857 		// 2. resolve & translate to local file system paths
1858 		List<String> resolved = new ArrayList<>(unresolved.length);
1859 		for (int i = 0; i < unresolved.length; i++) {
1860 			IRuntimeClasspathEntry entry = unresolved[i];
1861 			if (entry.getClasspathProperty() == IRuntimeClasspathEntry.USER_CLASSES) {
1862 				IRuntimeClasspathEntry[] entries = resolveRuntimeClasspathEntry(entry, jproject);
1863 				for (int j = 0; j < entries.length; j++) {
1864 					String location = entries[j].getLocation();
1865 					if (location != null) {
1866 						resolved.add(location);
1867 					}
1868 				}
1869 			}
1870 		}
1871 		return resolved.toArray(new String[resolved.size()]);
1872 	}
1873 
1874 	/**
1875 	 * Saves the VM configuration information to the preferences. This includes the following information:
1876 	 * <ul>
1877 	 * <li>The list of all defined IVMInstall instances.</li>
1878 	 * <li>The default VM</li>
1879 	 * </ul>
1880 	 * This state will be read again upon first access to VM configuration information.
1881 	 *
1882 	 * @throws CoreException
1883 	 *             if trying to save the current state of VMs encounters a problem
1884 	 */
saveVMConfiguration()1885 	public static void saveVMConfiguration() throws CoreException {
1886 		if (fgVMTypes == null) {
1887 			// if the VM types have not been instantiated, there can be no changes.
1888 			return;
1889 		}
1890 		String xml = getVMsAsXML();
1891 		InstanceScope.INSTANCE.getNode(LaunchingPlugin.ID_PLUGIN).put(PREF_VM_XML, xml);
1892 		savePreferences();
1893 	}
1894 
1895 	/**
1896 	 * Returns the listing of currently installed VMs as a single XML file
1897 	 * @return an XML representation of all of the currently installed VMs
1898 	 * @throws CoreException if trying to compute the XML for the VM state encounters a problem
1899 	 */
getVMsAsXML()1900 	private static String getVMsAsXML() throws CoreException {
1901 		VMDefinitionsContainer container = new VMDefinitionsContainer();
1902 		container.setDefaultVMInstallCompositeID(getDefaultVMId());
1903 		container.setDefaultVMInstallConnectorTypeID(getDefaultVMConnectorId());
1904 		IVMInstallType[] vmTypes = getVMInstallTypes();
1905 		IVMInstall[] vms = null;
1906 		for (int i = 0; i < vmTypes.length; ++i) {
1907 			vms = vmTypes[i].getVMInstalls();
1908 			for (int j = 0; j < vms.length; j++) {
1909 				container.addVM(vms[j]);
1910 			}
1911 		}
1912 		return container.getAsXML();
1913 	}
1914 
1915 	/**
1916 	 * This method loads installed JREs based an existing user preference
1917 	 * or old VM configurations file. The VMs found in the preference
1918 	 * or VM configurations file are added to the given VM definitions container.
1919 	 *
1920 	 * Returns whether the user preferences should be set - i.e. if it was
1921 	 * not already set when initialized.
1922 	 * @param vmDefs the container to add persisted VM information to
1923 	 * @return whether the user preferences should be set
1924 	 * @throws IOException if reading the {@link #PREF_VM_XML} preference stream encounters a problem
1925 	 */
addPersistedVMs(VMDefinitionsContainer vmDefs)1926 	private static boolean addPersistedVMs(VMDefinitionsContainer vmDefs) throws IOException {
1927 		// Try retrieving the VM preferences from the preference store
1928 		String vmXMLString = InstanceScope.INSTANCE.getNode(LaunchingPlugin.ID_PLUGIN).get(PREF_VM_XML, ""); //$NON-NLS-1$
1929 
1930 		// If the preference was found, load VMs from it into memory
1931 		if (vmXMLString.length() > 0) {
1932 			try {
1933 				ByteArrayInputStream inputStream = new ByteArrayInputStream(vmXMLString.getBytes("UTF8")); //$NON-NLS-1$
1934 				VMDefinitionsContainer.parseXMLIntoContainer(inputStream, vmDefs);
1935 				return false;
1936 			} catch (IOException ioe) {
1937 				LaunchingPlugin.log(ioe);
1938 			}
1939 		} else {
1940 			// Otherwise, look for the old file that previously held the VM definitions
1941 			IPath stateLocation= LaunchingPlugin.getDefault().getStateLocation();
1942 			IPath stateFile= stateLocation.append("vmConfiguration.xml"); //$NON-NLS-1$
1943 			File file = new File(stateFile.toOSString());
1944 
1945 			if (file.exists()) {
1946 				// If file exists, load VM definitions from it into memory and write the definitions to
1947 				// the preference store WITHOUT triggering any processing of the new value
1948 				InputStream fileInputStream = new BufferedInputStream(new FileInputStream(file));
1949 				VMDefinitionsContainer.parseXMLIntoContainer(fileInputStream, vmDefs);
1950 			}
1951 		}
1952 		return true;
1953 	}
1954 
1955 	/**
1956 	 * Loads contributed VM installs
1957 	 * @param vmDefs the container to add contributed VM install information to
1958 	 * @since 3.2
1959 	 */
addVMExtensions(VMDefinitionsContainer vmDefs)1960 	private static void addVMExtensions(VMDefinitionsContainer vmDefs) {
1961 		IExtensionPoint extensionPoint = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.ID_PLUGIN, JavaRuntime.EXTENSION_POINT_VM_INSTALLS);
1962 		IConfigurationElement[] configs= extensionPoint.getConfigurationElements();
1963 		for (int i = 0; i < configs.length; i++) {
1964 			IConfigurationElement element = configs[i];
1965 			try {
1966 				if ("vmInstall".equals(element.getName())) { //$NON-NLS-1$
1967 					String vmType = element.getAttribute("vmInstallType"); //$NON-NLS-1$
1968 					if (vmType == null) {
1969 						abort(NLS.bind("Missing required vmInstallType attribute for vmInstall contributed by {0}", //$NON-NLS-1$
1970 								new String[]{element.getContributor().getName()}), null);
1971 					}
1972 					String id = element.getAttribute("id"); //$NON-NLS-1$
1973 					if (id == null) {
1974 						abort(NLS.bind("Missing required id attribute for vmInstall contributed by {0}", //$NON-NLS-1$
1975 								new String[]{element.getContributor().getName()}), null);
1976 					}
1977 					IVMInstallType installType = getVMInstallType(vmType);
1978 					if (installType == null) {
1979 						abort(NLS.bind("vmInstall {0} contributed by {1} references undefined VM install type {2}", //$NON-NLS-1$
1980 								new String[]{id, element.getContributor().getName(), vmType}), null);
1981 					}
1982 					IVMInstall install = installType.findVMInstall(id);
1983 					if (install == null) {
1984 						// only load/create if first time we've seen this VM install
1985 						String name = element.getAttribute("name"); //$NON-NLS-1$
1986 						if (name == null) {
1987 							abort(NLS.bind("vmInstall {0} contributed by {1} missing required attribute name", //$NON-NLS-1$
1988 									new String[]{id, element.getContributor().getName()}), null);
1989 						}
1990 						String home = element.getAttribute("home"); //$NON-NLS-1$
1991 						if (home == null) {
1992 							abort(NLS.bind("vmInstall {0} contributed by {1} missing required attribute home", //$NON-NLS-1$
1993 									new String[]{id, element.getContributor().getName()}), null);
1994 						}
1995 						String javadoc = element.getAttribute("javadocURL"); //$NON-NLS-1$
1996 						String vmArgs = element.getAttribute("vmArgs"); //$NON-NLS-1$
1997 						VMStandin standin = null;
1998 						home = substitute(home);
1999 						File homeDir = new File(home);
2000                         if (homeDir.exists()) {
2001                             try {
2002                             	// adjust for relative path names
2003                                 home = homeDir.getCanonicalPath();
2004                                 homeDir = new File(home);
2005                             } catch (IOException e) {
2006                             }
2007                         }
2008 						if (EEVMType.ID_EE_VM_TYPE.equals(installType.getId())) {
2009 							standin = createVMFromDefinitionFile(homeDir, name, id);
2010 						} else {
2011 							standin = new VMStandin(installType, id);
2012 							standin.setName(name);
2013 	                        IStatus status = installType.validateInstallLocation(homeDir);
2014 	                        if (!status.isOK()) {
2015 	                        	abort(NLS.bind("Illegal install location {0} for vmInstall {1} contributed by {2}: {3}", //$NON-NLS-1$
2016 	                        			new String[]{home, id, element.getContributor().getName(), status.getMessage()}), null);
2017 	                        }
2018 	                        standin.setInstallLocation(homeDir);
2019 							if (javadoc != null) {
2020 								try {
2021 									standin.setJavadocLocation(new URL(javadoc));
2022 								} catch (MalformedURLException e) {
2023 									abort(NLS.bind("Illegal javadocURL attribute for vmInstall {0} contributed by {1}", //$NON-NLS-1$
2024 											new String[]{id, element.getContributor().getName()}), e);
2025 								}
2026 							}
2027 							// allow default arguments to be specified by VM install type if no explicit arguments
2028 							if (vmArgs == null) {
2029 								if (installType instanceof AbstractVMInstallType) {
2030 									AbstractVMInstallType type = (AbstractVMInstallType) installType;
2031 									vmArgs = type.getDefaultVMArguments(homeDir);
2032 								}
2033 							}
2034 							if (vmArgs != null) {
2035 								standin.setVMArgs(vmArgs);
2036 							}
2037 	                        IConfigurationElement[] libraries = element.getChildren("library"); //$NON-NLS-1$
2038 	                        LibraryLocation[] locations = null;
2039 	                        if (libraries.length > 0) {
2040 	                            locations = new LibraryLocation[libraries.length];
2041 	                            for (int j = 0; j < libraries.length; j++) {
2042 	                                IConfigurationElement library = libraries[j];
2043 	                                String libPathStr = library.getAttribute("path"); //$NON-NLS-1$
2044 	                                if (libPathStr == null) {
2045 	                                    abort(NLS.bind("library for vmInstall {0} contributed by {1} missing required attribute libPath", //$NON-NLS-1$
2046 	                                            new String[]{id, element.getContributor().getName()}), null);
2047 	                                }
2048 	                                String sourcePathStr = library.getAttribute("sourcePath"); //$NON-NLS-1$
2049 	                                String packageRootStr = library.getAttribute("packageRootPath"); //$NON-NLS-1$
2050 	                                String javadocOverride = library.getAttribute("javadocURL"); //$NON-NLS-1$
2051 	                                URL url = null;
2052 	                                if (javadocOverride != null) {
2053 	                                    try {
2054 	                                        url = new URL(javadocOverride);
2055 	                                    } catch (MalformedURLException e) {
2056 	                                        abort(NLS.bind("Illegal javadocURL attribute specified for library {0} for vmInstall {1} contributed by {2}" //$NON-NLS-1$
2057 	                                                ,new String[]{libPathStr, id, element.getContributor().getName()}), e);
2058 	                                    }
2059 	                                }
2060 	                                IPath homePath = new Path(home);
2061 	                                IPath libPath = homePath.append(substitute(libPathStr));
2062 	                                IPath sourcePath = Path.EMPTY;
2063 	                                if (sourcePathStr != null) {
2064 	                                    sourcePath = homePath.append(substitute(sourcePathStr));
2065 	                                }
2066 	                                IPath packageRootPath = Path.EMPTY;
2067 	                                if (packageRootStr != null) {
2068 	                                    packageRootPath = new Path(substitute(packageRootStr));
2069 	                                }
2070 	                                locations[j] = new LibraryLocation(libPath, sourcePath, packageRootPath, url);
2071 	                            }
2072 	                        }
2073 	                        standin.setLibraryLocations(locations);
2074 						}
2075                         // in case the contributed JRE attributes changed, remove it first, then add
2076                         vmDefs.removeVM(standin);
2077                         vmDefs.addVM(standin);
2078 					}
2079                     fgContributedVMs.add(id);
2080 				} else {
2081 					abort(NLS.bind("Illegal element {0} in vmInstalls extension contributed by {1}", //$NON-NLS-1$
2082 							new String[]{element.getName(), element.getContributor().getName()}), null);
2083 				}
2084 			} catch (CoreException e) {
2085 				LaunchingPlugin.log(e);
2086 			}
2087 		}
2088 	}
2089 
2090     /**
2091      * Performs string substitution on the given expression.
2092      *
2093      * @param expression the expression to evaluate
2094      * @return expression after string substitution
2095      * @throws CoreException if the substitution encounters a problem
2096      * @since 3.2
2097      */
substitute(String expression)2098     private static String substitute(String expression) throws CoreException {
2099         return VariablesPlugin.getDefault().getStringVariableManager().performStringSubstitution(expression);
2100     }
2101 
2102     /**
2103      * Returns whether the VM install with the specified id was contributed via
2104      * the vmInstalls extension point.
2105      *
2106      * @param id VM id
2107      * @return whether the VM install was contributed via extension point
2108      * @since 3.2
2109      */
isContributedVMInstall(String id)2110     public static boolean isContributedVMInstall(String id) {
2111         getVMInstallTypes(); // ensure VMs are initialized
2112         return fgContributedVMs.contains(id);
2113     }
2114 
2115 	/**
2116 	 * Evaluates library locations for a IVMInstall. If no library locations are set on the install, a default
2117 	 * location is evaluated and checked if it exists.
2118 	 * @param vm the {@link IVMInstall} to compute locations for
2119 	 * @return library locations with paths that exist or are empty
2120 	 * @since 2.0
2121 	 */
getLibraryLocations(IVMInstall vm)2122 	public static LibraryLocation[] getLibraryLocations(IVMInstall vm)  {
2123 		IPath[] libraryPaths;
2124 		IPath[] sourcePaths;
2125 		IPath[] sourceRootPaths;
2126 		IPath[] annotationPaths;
2127 		URL[] javadocLocations;
2128 		URL[] indexes;
2129 		LibraryLocation[] locations= vm.getLibraryLocations();
2130 		if (locations == null) {
2131             URL defJavaDocLocation = vm.getJavadocLocation();
2132 			File installLocation = vm.getInstallLocation();
2133 			if (installLocation == null) {
2134 				return new LibraryLocation[0];
2135 			}
2136 			LibraryLocation[] dflts= vm.getVMInstallType().getDefaultLibraryLocations(installLocation);
2137 			libraryPaths = new IPath[dflts.length];
2138 			sourcePaths = new IPath[dflts.length];
2139 			sourceRootPaths = new IPath[dflts.length];
2140 			javadocLocations= new URL[dflts.length];
2141 			indexes = new URL[dflts.length];
2142 			annotationPaths = new IPath[dflts.length];
2143 			for (int i = 0; i < dflts.length; i++) {
2144 				libraryPaths[i]= dflts[i].getSystemLibraryPath();
2145                 if (defJavaDocLocation == null) {
2146                     javadocLocations[i]= dflts[i].getJavadocLocation();
2147                 } else {
2148                     javadocLocations[i]= defJavaDocLocation;
2149                 }
2150                 indexes[i] = dflts[i].getIndexLocation();
2151 				if (!libraryPaths[i].toFile().isFile()) {
2152 					libraryPaths[i]= Path.EMPTY;
2153 				}
2154 
2155 				annotationPaths[i] = Path.EMPTY;
2156 
2157 				sourcePaths[i]= dflts[i].getSystemLibrarySourcePath();
2158 				if (sourcePaths[i].toFile().isFile()) {
2159 					sourceRootPaths[i]= dflts[i].getPackageRootPath();
2160 				} else {
2161 					sourcePaths[i]= Path.EMPTY;
2162 					sourceRootPaths[i]= Path.EMPTY;
2163 				}
2164 			}
2165 		} else {
2166 			libraryPaths = new IPath[locations.length];
2167 			sourcePaths = new IPath[locations.length];
2168 			sourceRootPaths = new IPath[locations.length];
2169 			javadocLocations= new URL[locations.length];
2170 			indexes = new URL[locations.length];
2171 			annotationPaths = new IPath[locations.length];
2172 			for (int i = 0; i < locations.length; i++) {
2173 				libraryPaths[i]= locations[i].getSystemLibraryPath();
2174 				sourcePaths[i]= locations[i].getSystemLibrarySourcePath();
2175 				sourceRootPaths[i]= locations[i].getPackageRootPath();
2176 				javadocLocations[i]= locations[i].getJavadocLocation();
2177 				annotationPaths[i] = locations[i].getExternalAnnotationsPath();
2178 				indexes[i] = locations[i].getIndexLocation();
2179 			}
2180 		}
2181 		locations = new LibraryLocation[sourcePaths.length];
2182 		for (int i = 0; i < sourcePaths.length; i++) {
2183 			locations[i] = new LibraryLocation(libraryPaths[i], sourcePaths[i], sourceRootPaths[i], javadocLocations[i], indexes[i], annotationPaths[i]);
2184 		}
2185 		return locations;
2186 	}
2187 
2188 	/**
2189 	 * Detect the VM that Eclipse is running on.
2190 	 *
2191 	 * @return a VM stand-in representing the VM that Eclipse is running on, or
2192 	 * <code>null</code> if unable to detect the runtime VM
2193 	 */
detectEclipseRuntime()2194 	private static VMStandin detectEclipseRuntime() {
2195 		// Try to detect a VM for each declared VM type
2196 		IVMInstallType[] vmTypes= getVMInstallTypes();
2197 		// If we are running from an EE file, setup the VM from it
2198 		for (int i = 0; i < vmTypes.length; i++) {
2199 			if (vmTypes[i] instanceof EEVMType){
2200 				String eeFileName = System.getProperty("ee.filename"); //$NON-NLS-1$
2201 				if (eeFileName != null){
2202 					File vmFile = new File(eeFileName);
2203 					if (vmFile.isDirectory()){
2204 						vmFile = new File(vmFile, "default.ee"); //$NON-NLS-1$
2205 					}
2206 					if (vmFile.isFile()){
2207 						// Make sure the VM id is unique
2208 						long unique = System.currentTimeMillis();
2209 						while (vmTypes[i].findVMInstall(String.valueOf(unique)) != null) {
2210 							unique++;
2211 						}
2212 
2213 						// Create a stand-in for the detected VM and add it to the result collector
2214 						String vmID = String.valueOf(unique);
2215 						try{
2216 							return createVMFromDefinitionFile(vmFile, "", vmID); //$NON-NLS-1$
2217 						} catch (CoreException e){
2218 							// The file was not a valid EE file, continue the detection process
2219 						}
2220 					}
2221 				}
2222 			}
2223 		}
2224 
2225 		// Try to create a VM install using the install location
2226 		for (int i = 0; i < vmTypes.length; i++) {
2227 			File detectedLocation= vmTypes[i].detectInstallLocation();
2228 			if (detectedLocation != null) {
2229 
2230 				// Make sure the VM id is unique
2231 				long unique = System.currentTimeMillis();
2232 				IVMInstallType vmType = vmTypes[i];
2233 				while (vmType.findVMInstall(String.valueOf(unique)) != null) {
2234 					unique++;
2235 				}
2236 
2237 				// Create a stand-in for the detected VM and add it to the result collector
2238 				String vmID = String.valueOf(unique);
2239 				VMStandin detectedVMStandin = new VMStandin(vmType, vmID);
2240 
2241 				// Java 9 and above needs the vmInstall location till jre
2242 				File pluginDir = new File(detectedLocation, "plugins"); //$NON-NLS-1$
2243 				File featuresDir = new File(detectedLocation, "features"); //$NON-NLS-1$
2244 				if (pluginDir.exists() && featuresDir.exists()) {
2245 					if (isJREVersionAbove8(vmType, detectedLocation)) {
2246 						detectedLocation = new File(detectedLocation, "jre"); //$NON-NLS-1$
2247 					}
2248 				}
2249 				detectedVMStandin.setInstallLocation(detectedLocation);
2250 				detectedVMStandin.setName(generateDetectedVMName(detectedVMStandin));
2251 				if (vmType instanceof AbstractVMInstallType) {
2252 				    AbstractVMInstallType abs = (AbstractVMInstallType)vmType;
2253 				    URL url = abs.getDefaultJavadocLocation(detectedLocation);
2254 				    detectedVMStandin.setJavadocLocation(url);
2255 				    String arguments = abs.getDefaultVMArguments(detectedLocation);
2256 					if (arguments != null) {
2257 						detectedVMStandin.setVMArgs(arguments);
2258 					}
2259 				}
2260 				return detectedVMStandin;
2261 			}
2262 		}
2263 		return null;
2264 	}
2265 
isJREVersionAbove8(IVMInstallType vmType, File installLocation)2266 	private static boolean isJREVersionAbove8(IVMInstallType vmType, File installLocation) {
2267 		LibraryLocation[] locations = vmType.getDefaultLibraryLocations(installLocation);
2268 		boolean exist = true;
2269 		for (int i = 0; i < locations.length; i++) {
2270 			exist = exist && new File(locations[i].getSystemLibraryPath().toOSString()).exists();
2271 		}
2272 		if (exist) {
2273 			return false;
2274 		}
2275 		exist = true;
2276 		LibraryLocation[] newLocations = vmType.getDefaultLibraryLocations(new File(installLocation, "jre")); //$NON-NLS-1$
2277 		for (int i = 0; i < newLocations.length; i++) {
2278 			exist = exist && new File(newLocations[i].getSystemLibraryPath().toOSString()).exists();
2279 		}
2280 		return exist;
2281 	}
2282 
2283 	/**
2284 	 * Returns whether the specified option is the same in the given
2285 	 * map and preference store.
2286 	 *
2287 	 * <p>
2288 	 * Notes:
2289 	 * <ul>
2290 	 * <li>Returns <code>false</code> if the option is only contained in one map</li>
2291 	 * <li>Returns <code>true</code> if the option is not contained in either map</li>
2292 	 * </ul>
2293 	 * </p>
2294 	 *
2295 	 * @param optionName name of option to test
2296 	 * @param options map of options
2297 	 * @param prefStore preferences node
2298 	 * @return whether the options are the same in both maps
2299 	 */
equals(String optionName, Map<?, ?> options, org.osgi.service.prefs.Preferences prefStore)2300 	private static boolean equals(String optionName, Map<?, ?> options, org.osgi.service.prefs.Preferences prefStore) {
2301 		String dummy= new String();
2302 		String prefValue= prefStore.get(optionName, dummy);
2303 		if (prefValue != null && prefValue != dummy) {
2304 			return options.containsKey(optionName) &&
2305 				equals(prefValue, options.get(optionName));
2306 		}
2307 		return !options.containsKey(optionName);
2308 	}
2309 
2310 	/**
2311 	 * Returns whether the objects are equal or both <code>null</code>
2312 	 *
2313 	 * @param o1 an object
2314 	 * @param o2 an object
2315 	 * @return whether the objects are equal or both <code>null</code>
2316 	 */
equals(Object o1, Object o2)2317 	private static boolean equals(Object o1, Object o2) {
2318 		if (o1 == null) {
2319 			return o2 == null;
2320 		}
2321 		return o1.equals(o2);
2322 	}
2323 
2324 	/**
2325 	 * Make the name of a detected VM stand out.
2326 	 * @param vm the VM to generate a name for
2327 	 * @return the new name or <code>JRE</code>
2328 	 */
generateDetectedVMName(IVMInstall vm)2329 	private static String generateDetectedVMName(IVMInstall vm) {
2330 		String name = vm.getInstallLocation().getName();
2331 		name = name.trim();
2332 		if (name.length() == 0) {
2333 			name = LaunchingMessages.JavaRuntime_25;
2334 		}
2335 		return name;
2336 	}
2337 
2338 	/**
2339 	 * Creates and returns a classpath entry describing
2340 	 * the JRE_LIB classpath variable.
2341 	 *
2342 	 * @return a new IClasspathEntry that describes the JRE_LIB classpath variable
2343 	 */
getJREVariableEntry()2344 	public static IClasspathEntry getJREVariableEntry() {
2345 		return JavaCore.newVariableEntry(
2346 			new Path(JRELIB_VARIABLE),
2347 			new Path(JRESRC_VARIABLE),
2348 			new Path(JRESRCROOT_VARIABLE)
2349 		);
2350 	}
2351 
2352 	/**
2353 	 * Creates and returns a classpath entry describing
2354 	 * the default JRE container entry.
2355 	 *
2356 	 * @return a new IClasspathEntry that describes the default JRE container entry
2357 	 * @since 2.0
2358 	 */
getDefaultJREContainerEntry()2359 	public static IClasspathEntry getDefaultJREContainerEntry() {
2360 		return JavaCore.newContainerEntry(newDefaultJREContainerPath());
2361 	}
2362 
2363 	/**
2364 	 * Returns a path for the JRE classpath container identifying the
2365 	 * default VM install.
2366 	 *
2367 	 * @return classpath container path
2368 	 * @since 3.2
2369 	 */
newDefaultJREContainerPath()2370 	public static IPath newDefaultJREContainerPath() {
2371 		return new Path(JRE_CONTAINER);
2372 	}
2373 
2374 	/**
2375 	 * Returns a path for the JRE classpath container identifying the
2376 	 * specified VM install by type and name.
2377 	 *
2378 	 * @param vm VM install
2379 	 * @return classpath container path
2380 	 * @since 3.2
2381 	 */
newJREContainerPath(IVMInstall vm)2382 	public static IPath newJREContainerPath(IVMInstall vm) {
2383 		return newJREContainerPath(vm.getVMInstallType().getId(), vm.getName());
2384 	}
2385 
2386 	/**
2387 	 * Returns a path for the JRE classpath container identifying the
2388 	 * specified VM install by type and name.
2389 	 *
2390 	 * @param typeId VM install type identifier
2391 	 * @param name VM install name
2392 	 * @return classpath container path
2393 	 * @since 3.2
2394 	 */
newJREContainerPath(String typeId, String name)2395 	public static IPath newJREContainerPath(String typeId, String name) {
2396 		IPath path = newDefaultJREContainerPath();
2397 		path = path.append(typeId);
2398 		path = path.append(name);
2399 		return path;
2400 	}
2401 
2402 	/**
2403 	 * Returns a path for the JRE classpath container identifying the
2404 	 * specified execution environment.
2405 	 *
2406 	 * @param environment execution environment
2407 	 * @return classpath container path
2408 	 * @since 3.2
2409 	 */
newJREContainerPath(IExecutionEnvironment environment)2410 	public static IPath newJREContainerPath(IExecutionEnvironment environment) {
2411 		IPath path = newDefaultJREContainerPath();
2412 		path = path.append(StandardVMType.ID_STANDARD_VM_TYPE);
2413 		path = path.append(JREContainerInitializer.encodeEnvironmentId(environment.getId()));
2414 		return path;
2415 	}
2416 
2417 	/**
2418 	 * Returns the JRE referenced by the specified JRE classpath container
2419 	 * path or <code>null</code> if none.
2420 	 *
2421 	 * @param jreContainerPath the path to the container to try and resolve the {@link IVMInstall} from
2422 	 * @return JRE referenced by the specified JRE classpath container
2423 	 *  path or <code>null</code>
2424 	 * @since 3.2
2425 	 */
getVMInstall(IPath jreContainerPath)2426 	public static IVMInstall getVMInstall(IPath jreContainerPath) {
2427 		return JREContainerInitializer.resolveVM(jreContainerPath);
2428 	}
2429 
2430 	/**
2431 	 * Returns the identifier of the VM install type referenced by the
2432 	 * given JRE classpath container path, or <code>null</code> if none.
2433 	 *
2434 	 * @param jreContainerPath the path to the container to try and resolve the {@link IVMInstallType} id from
2435 	 * @return VM install type identifier or <code>null</code>
2436 	 * @since 3.2
2437 	 */
getVMInstallTypeId(IPath jreContainerPath)2438 	public static String getVMInstallTypeId(IPath jreContainerPath) {
2439 		if (JREContainerInitializer.isExecutionEnvironment(jreContainerPath)) {
2440 			return null;
2441 		}
2442 		return JREContainerInitializer.getVMTypeId(jreContainerPath);
2443 	}
2444 
2445 	/**
2446 	 * Returns the name of the VM install referenced by the
2447 	 * given JRE classpath container path, or <code>null</code> if none.
2448 	 *
2449 	 * @param jreContainerPath the path to the container to try an resolve the {@link IVMInstall} name from
2450 	 * @return VM name or <code>null</code>
2451 	 * @since 3.2
2452 	 */
getVMInstallName(IPath jreContainerPath)2453 	public static String getVMInstallName(IPath jreContainerPath) {
2454 		if (JREContainerInitializer.isExecutionEnvironment(jreContainerPath)) {
2455 			return null;
2456 		}
2457 		return JREContainerInitializer.getVMName(jreContainerPath);
2458 	}
2459 
2460 	/**
2461 	 * Returns the execution environment identifier in the following JRE
2462 	 * classpath container path, or <code>null</code> if none.
2463 	 *
2464 	 * @param jreContainerPath classpath container path
2465 	 * @return execution environment identifier or <code>null</code>
2466 	 * @since 3.2
2467 	 */
getExecutionEnvironmentId(IPath jreContainerPath)2468 	public static String getExecutionEnvironmentId(IPath jreContainerPath) {
2469 		return JREContainerInitializer.getExecutionEnvironmentId(jreContainerPath);
2470 	}
2471 
2472 	/**
2473 	 * Returns a runtime classpath entry identifying the JRE to use when launching the specified
2474 	 * configuration or <code>null</code> if none is specified. The entry returned represents a
2475 	 * either a classpath variable or classpath container that resolves to a JRE.
2476 	 * <p>
2477 	 * The entry is resolved as follows:
2478 	 * <ol>
2479 	 * <li>If the <code>ATTR_JRE_CONTAINER_PATH</code> is present, it is used to create
2480 	 *  a classpath container referring to a JRE.</li>
2481 	 * <li>Next, if the <code>ATTR_VM_INSTALL_TYPE</code> and <code>ATTR_VM_INSTALL_NAME</code>
2482 	 * attributes are present, they are used to create a classpath container.</li>
2483 	 * <li>When none of the above attributes are specified, a default entry is
2484 	 * created which refers to the JRE referenced by the build path of the configuration's
2485 	 * associated Java project. This could be a classpath variable or classpath container.</li>
2486 	 * <li>When there is no Java project associated with a configuration, the workspace
2487 	 * default JRE is used to create a container path.</li>
2488 	 * </ol>
2489 	 * @param configuration the backing {@link ILaunchConfiguration}
2490 	 * @return classpath container path identifying a JRE or <code>null</code>
2491 	 * @exception org.eclipse.core.runtime.CoreException if an exception occurs retrieving
2492 	 *  attributes from the specified launch configuration
2493 	 * @since 3.2
2494 	 */
computeJREEntry(ILaunchConfiguration configuration)2495 	public static IRuntimeClasspathEntry computeJREEntry(ILaunchConfiguration configuration) throws CoreException {
2496 		String jreAttr = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_JRE_CONTAINER_PATH, (String)null);
2497 		IPath containerPath = null;
2498 		if (jreAttr == null) {
2499 			@SuppressWarnings("deprecation")
2500 			String type = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_TYPE, (String)null);
2501 			if (type == null) {
2502 				// default JRE for the launch configuration
2503 				IJavaProject proj = getJavaProject(configuration);
2504 				if (proj == null) {
2505 					containerPath = newDefaultJREContainerPath();
2506 				} else {
2507 					if (isModularConfiguration(configuration)) {
2508 						return computeModularJREEntry(proj);
2509 					}
2510 					return computeJREEntry(proj);
2511 				}
2512 			} else {
2513 				@SuppressWarnings("deprecation")
2514 				String name = configuration.getAttribute(IJavaLaunchConfigurationConstants.ATTR_VM_INSTALL_NAME, (String)null);
2515 				if (name != null) {
2516 					containerPath = newDefaultJREContainerPath().append(type).append(name);
2517 				}
2518 			}
2519 		} else {
2520 			containerPath = Path.fromPortableString(jreAttr);
2521 		}
2522 		if (containerPath != null) {
2523 			if (isModularConfiguration(configuration)) {
2524 				return newRuntimeContainerClasspathEntry(containerPath, IRuntimeClasspathEntry.MODULE_PATH);
2525 			}
2526 			return newRuntimeContainerClasspathEntry(containerPath, IRuntimeClasspathEntry.STANDARD_CLASSES);
2527 		}
2528 		return null;
2529 	}
2530 
2531 	/**
2532 	 * Returns a runtime classpath entry identifying the JRE referenced by the specified
2533 	 * project, or <code>null</code> if none. The entry returned represents a either a
2534 	 * classpath variable or classpath container that resolves to a JRE.
2535 	 *
2536 	 * @param project Java project
2537 	 * @return JRE runtime classpath entry or <code>null</code>
2538 	 * @exception org.eclipse.core.runtime.CoreException if an exception occurs
2539 	 * 	accessing the project's classpath
2540 	 * @since 3.2
2541 	 */
computeJREEntry(IJavaProject project)2542 	public static IRuntimeClasspathEntry computeJREEntry(IJavaProject project) throws CoreException {
2543 		IClasspathEntry[] rawClasspath = project.getRawClasspath();
2544 		IRuntimeClasspathEntryResolver2 resolver = null;
2545 		for (int i = 0; i < rawClasspath.length; i++) {
2546 			IClasspathEntry entry = rawClasspath[i];
2547 			switch (entry.getEntryKind()) {
2548 				case IClasspathEntry.CPE_VARIABLE:
2549 					resolver = getVariableResolver(entry.getPath().segment(0));
2550 					if (resolver != null) {
2551 						if (resolver.isVMInstallReference(entry)) {
2552 							return newRuntimeClasspathEntry(entry);
2553 						}
2554 					}
2555 					break;
2556 				case IClasspathEntry.CPE_CONTAINER:
2557 					resolver = getContainerResolver(entry.getPath().segment(0));
2558 					if (resolver != null) {
2559 						if (resolver.isVMInstallReference(entry)) {
2560 							IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
2561 							if (container != null) {
2562 								switch (container.getKind()) {
2563 									case IClasspathContainer.K_APPLICATION:
2564 										break;
2565 									case IClasspathContainer.K_DEFAULT_SYSTEM:
2566 										return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.STANDARD_CLASSES);
2567 									case IClasspathContainer.K_SYSTEM:
2568 										return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.BOOTSTRAP_CLASSES);
2569 								}
2570 							}
2571 						}
2572 					}
2573 					break;
2574 			}
2575 
2576 		}
2577 		return null;
2578 	}
2579 
2580 	/**
2581 	 * Returns a runtime classpath or modulepath entry identifying the JRE referenced by the specified project, or <code>null</code> if none. The
2582 	 * entry returned represents a either a classpath variable or classpath container that resolves to a JRE.
2583 	 *
2584 	 * @param project
2585 	 *            Java project
2586 	 * @return JRE runtime classpath or modulepath entry or <code>null</code>
2587 	 * @exception org.eclipse.core.runtime.CoreException
2588 	 *                if an exception occurs accessing the project's classpath
2589 	 * @since 3.10
2590 	 */
computeModularJREEntry(IJavaProject project)2591 	public static IRuntimeClasspathEntry computeModularJREEntry(IJavaProject project) throws CoreException {
2592 		IClasspathEntry[] rawClasspath = project.getRawClasspath();
2593 		IRuntimeClasspathEntryResolver2 resolver = null;
2594 		for (int i = 0; i < rawClasspath.length; i++) {
2595 			IClasspathEntry entry = rawClasspath[i];
2596 			switch (entry.getEntryKind()) {
2597 				case IClasspathEntry.CPE_VARIABLE:
2598 					resolver = getVariableResolver(entry.getPath().segment(0));
2599 					if (resolver != null) {
2600 						if (resolver.isVMInstallReference(entry)) {
2601 							if (isModularProject(project)) {
2602 								return newRuntimeClasspathEntry(entry, IRuntimeClasspathEntry.MODULE_PATH);
2603 							}
2604 							return newRuntimeClasspathEntry(entry, IRuntimeClasspathEntry.CLASS_PATH);
2605 						}
2606 					}
2607 					break;
2608 				case IClasspathEntry.CPE_CONTAINER:
2609 					resolver = getContainerResolver(entry.getPath().segment(0));
2610 					if (resolver != null) {
2611 						if (resolver.isVMInstallReference(entry)) {
2612 							IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
2613 							if (container != null) {
2614 								switch (container.getKind()) {
2615 									case IClasspathContainer.K_APPLICATION:
2616 										break;
2617 									case IClasspathContainer.K_DEFAULT_SYSTEM:
2618 										if (isModularProject(project)) {
2619 											return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.MODULE_PATH);
2620 										}
2621 										return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.CLASS_PATH);
2622 									case IClasspathContainer.K_SYSTEM:
2623 										if (isModularProject(project)) {
2624 											return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.MODULE_PATH);
2625 										}
2626 										return newRuntimeContainerClasspathEntry(entry.getPath(), IRuntimeClasspathEntry.CLASS_PATH);
2627 								}
2628 							}
2629 						}
2630 					}
2631 					break;
2632 			}
2633 
2634 		}
2635 		return null;
2636 	}
2637 
2638 	/**
2639 	 * Returns whether the given runtime classpath entry refers to a VM install.
2640 	 *
2641 	 * @param entry the entry to check
2642 	 * @return whether the given runtime classpath entry refers to a VM install
2643 	 * @since 3.2
2644 	 */
isVMInstallReference(IRuntimeClasspathEntry entry)2645 	public static boolean isVMInstallReference(IRuntimeClasspathEntry entry) {
2646 		IClasspathEntry classpathEntry = entry.getClasspathEntry();
2647 		if (classpathEntry != null) {
2648 			switch (classpathEntry.getEntryKind()) {
2649 				case IClasspathEntry.CPE_VARIABLE:
2650 					IRuntimeClasspathEntryResolver2 resolver = getVariableResolver(classpathEntry.getPath().segment(0));
2651 					if (resolver != null) {
2652 						return resolver.isVMInstallReference(classpathEntry);
2653 					}
2654 					break;
2655 				case IClasspathEntry.CPE_CONTAINER:
2656 					resolver = getContainerResolver(classpathEntry.getPath().segment(0));
2657 					if (resolver != null) {
2658 						return resolver.isVMInstallReference(classpathEntry);
2659 					}
2660 					break;
2661 				}
2662 		}
2663 		return false;
2664 	}
2665 
2666 	/**
2667 	 * Returns the VM connector defined with the specified identifier,
2668 	 * or <code>null</code> if none.
2669 	 *
2670 	 * @param id VM connector identifier
2671 	 * @return VM connector or <code>null</code> if none
2672 	 * @since 2.0
2673 	 */
getVMConnector(String id)2674 	public static IVMConnector getVMConnector(String id) {
2675 		return LaunchingPlugin.getDefault().getVMConnector(id);
2676 	}
2677 
2678 	/**
2679 	 * Returns all VM connector extensions.
2680 	 *
2681 	 * @return VM connectors
2682 	 * @since 2.0
2683 	 */
getVMConnectors()2684 	public static IVMConnector[] getVMConnectors() {
2685 		return LaunchingPlugin.getDefault().getVMConnectors();
2686 	}
2687 
2688 	/**
2689 	 * Returns the preference store for the launching plug-in.
2690 	 *
2691 	 * @return the preference store for the launching plug-in
2692 	 * @since 2.0
2693 	 */
2694 	@SuppressWarnings("deprecation")
getPreferences()2695 	public static Preferences getPreferences() {
2696 		return LaunchingPlugin.getDefault().getPluginPreferences();
2697 	}
2698 
2699 	/**
2700 	 * Saves the preferences for the launching plug-in.
2701 	 *
2702 	 * @since 2.0
2703 	 */
savePreferences()2704 	public static void savePreferences() {
2705 		IEclipsePreferences prefs = InstanceScope.INSTANCE.getNode(LaunchingPlugin.ID_PLUGIN);
2706 		try {
2707 			prefs.flush();
2708 		} catch (BackingStoreException e) {
2709 			LaunchingPlugin.log(e);
2710 		}
2711 	}
2712 
2713 	/**
2714 	 * Registers the given resolver for the specified variable.
2715 	 *
2716 	 * @param resolver runtime classpath entry resolver
2717 	 * @param variableName variable name to register for
2718 	 * @since 2.0
2719 	 */
addVariableResolver(IRuntimeClasspathEntryResolver resolver, String variableName)2720 	public static void addVariableResolver(IRuntimeClasspathEntryResolver resolver, String variableName) {
2721 		Map<String, IRuntimeClasspathEntryResolver> map = getVariableResolvers();
2722 		map.put(variableName, resolver);
2723 	}
2724 
2725 	/**
2726 	 * Registers the given resolver for the specified container.
2727 	 *
2728 	 * @param resolver runtime classpath entry resolver
2729 	 * @param containerIdentifier identifier of the classpath container to register for
2730 	 * @since 2.0
2731 	 */
addContainerResolver(IRuntimeClasspathEntryResolver resolver, String containerIdentifier)2732 	public static void addContainerResolver(IRuntimeClasspathEntryResolver resolver, String containerIdentifier) {
2733 		Map<String, IRuntimeClasspathEntryResolver> map = getContainerResolvers();
2734 		map.put(containerIdentifier, resolver);
2735 	}
2736 
2737 	/**
2738 	 * Returns all registered variable resolvers.
2739 	 * @return the initialized map of {@link RuntimeClasspathEntryResolver}s for variables
2740 	 */
getVariableResolvers()2741 	private static Map<String, IRuntimeClasspathEntryResolver> getVariableResolvers() {
2742 		if (fgVariableResolvers == null) {
2743 			initializeResolvers();
2744 		}
2745 		return fgVariableResolvers;
2746 	}
2747 
2748 	/**
2749 	 * Returns all registered container resolvers.
2750 	 * @return the initialized map of {@link RuntimeClasspathEntryResolver}s for containers
2751 	 */
getContainerResolvers()2752 	private static Map<String, IRuntimeClasspathEntryResolver> getContainerResolvers() {
2753 		if (fgContainerResolvers == null) {
2754 			initializeResolvers();
2755 		}
2756 		return fgContainerResolvers;
2757 	}
2758 
2759 	/**
2760 	 * Returns all registered runtime classpath entry resolvers.
2761 	 * @return the initialized map of {@link RuntimeClasspathEntryResolver}s for classpath entries
2762 	 */
getEntryResolvers()2763 	private static Map<String, RuntimeClasspathEntryResolver> getEntryResolvers() {
2764 		if (fgRuntimeClasspathEntryResolvers == null) {
2765 			initializeResolvers();
2766 		}
2767 		return fgRuntimeClasspathEntryResolvers;
2768 	}
2769 
2770 	/**
2771 	 * Initializes the listing of runtime classpath entry resolvers
2772 	 */
initializeResolvers()2773 	private static void initializeResolvers() {
2774 		IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.ID_PLUGIN, EXTENSION_POINT_RUNTIME_CLASSPATH_ENTRY_RESOLVERS);
2775 		IConfigurationElement[] extensions = point.getConfigurationElements();
2776 		fgVariableResolvers = new HashMap<>(extensions.length);
2777 		fgContainerResolvers = new HashMap<>(extensions.length);
2778 		fgRuntimeClasspathEntryResolvers = new HashMap<>(extensions.length);
2779 		for (int i = 0; i < extensions.length; i++) {
2780 			RuntimeClasspathEntryResolver res = new RuntimeClasspathEntryResolver(extensions[i]);
2781 			String variable = res.getVariableName();
2782 			String container = res.getContainerId();
2783 			String entryId = res.getRuntimeClasspathEntryId();
2784 			if (variable != null) {
2785 				fgVariableResolvers.put(variable, res);
2786 			}
2787 			if (container != null) {
2788 				fgContainerResolvers.put(container, res);
2789 			}
2790 			if (entryId != null) {
2791 				fgRuntimeClasspathEntryResolvers.put(entryId, res);
2792 			}
2793 		}
2794 	}
2795 
2796 	/**
2797 	 * Returns all registered classpath providers.
2798 	 * @return the initialized map of {@link RuntimeClasspathProvider}s
2799 	 */
getClasspathProviders()2800 	private static Map<String, RuntimeClasspathProvider> getClasspathProviders() {
2801 		if (fgPathProviders == null) {
2802 			initializeProviders();
2803 		}
2804 		return fgPathProviders;
2805 	}
2806 
2807 	/**
2808 	 * Initializes the listing of classpath providers
2809 	 */
initializeProviders()2810 	private static void initializeProviders() {
2811 		IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(LaunchingPlugin.ID_PLUGIN, EXTENSION_POINT_RUNTIME_CLASSPATH_PROVIDERS);
2812 		IConfigurationElement[] extensions = point.getConfigurationElements();
2813 		fgPathProviders = new HashMap<>(extensions.length);
2814 		for (int i = 0; i < extensions.length; i++) {
2815 			RuntimeClasspathProvider res = new RuntimeClasspathProvider(extensions[i]);
2816 			fgPathProviders.put(res.getIdentifier(), res);
2817 		}
2818 	}
2819 
2820 	/**
2821 	 * Returns the resolver registered for the given variable, or
2822 	 * <code>null</code> if none.
2823 	 *
2824 	 * @param variableName the variable to determine the resolver for
2825 	 * @return the resolver registered for the given variable, or
2826 	 * <code>null</code> if none
2827 	 */
getVariableResolver(String variableName)2828 	private static IRuntimeClasspathEntryResolver2 getVariableResolver(String variableName) {
2829 		return (IRuntimeClasspathEntryResolver2)getVariableResolvers().get(variableName);
2830 	}
2831 
2832 	/**
2833 	 * Returns the resolver registered for the given container id, or
2834 	 * <code>null</code> if none.
2835 	 *
2836 	 * @param containerId the container to determine the resolver for
2837 	 * @return the resolver registered for the given container id, or
2838 	 * <code>null</code> if none
2839 	 */
getContainerResolver(String containerId)2840 	private static IRuntimeClasspathEntryResolver2 getContainerResolver(String containerId) {
2841 		return (IRuntimeClasspathEntryResolver2)getContainerResolvers().get(containerId);
2842 	}
2843 
2844 	/**
2845 	 * Returns the resolver registered for the given contributed classpath
2846 	 * entry type.
2847 	 *
2848 	 * @param typeId the id of the contributed classpath entry
2849 	 * @return the resolver registered for the given classpath entry
2850 	 */
getContributedResolver(String typeId)2851 	private static IRuntimeClasspathEntryResolver getContributedResolver(String typeId) {
2852 		IRuntimeClasspathEntryResolver resolver = getEntryResolvers().get(typeId);
2853 		if (resolver == null) {
2854 			return new DefaultEntryResolver();
2855 		}
2856 		return resolver;
2857 	}
2858 
2859 	/**
2860 	 * Adds the given listener to the list of registered VM install changed
2861 	 * listeners. Has no effect if an identical listener is already registered.
2862 	 *
2863 	 * @param listener the listener to add
2864 	 * @since 2.0
2865 	 */
addVMInstallChangedListener(IVMInstallChangedListener listener)2866 	public static void addVMInstallChangedListener(IVMInstallChangedListener listener) {
2867 		fgVMListeners.add(listener);
2868 	}
2869 
2870 	/**
2871 	 * Removes the given listener from the list of registered VM install changed
2872 	 * listeners. Has no effect if an identical listener is not already registered.
2873 	 *
2874 	 * @param listener the listener to remove
2875 	 * @since 2.0
2876 	 */
removeVMInstallChangedListener(IVMInstallChangedListener listener)2877 	public static void removeVMInstallChangedListener(IVMInstallChangedListener listener) {
2878 		fgVMListeners.remove(listener);
2879 	}
2880 
2881 	/**
2882 	 * Notifies registered listeners that the default VM has changed
2883 	 * @param previous the previous VM
2884 	 * @param current the new current default VM
2885 	 */
notifyDefaultVMChanged(IVMInstall previous, IVMInstall current)2886 	private static void notifyDefaultVMChanged(IVMInstall previous, IVMInstall current) {
2887 		for (IVMInstallChangedListener listener : fgVMListeners) {
2888 			listener.defaultVMInstallChanged(previous, current);
2889 		}
2890 	}
2891 
2892 	/**
2893 	 * Notifies all VM install changed listeners of the given property change.
2894 	 *
2895 	 * @param event event describing the change.
2896 	 * @since 2.0
2897 	 */
fireVMChanged(PropertyChangeEvent event)2898 	public static void fireVMChanged(PropertyChangeEvent event) {
2899 		for (IVMInstallChangedListener listener : fgVMListeners) {
2900 			listener.vmChanged(event);
2901 		}
2902 	}
2903 
2904 	/**
2905 	 * Notifies all VM install changed listeners of the VM addition
2906 	 *
2907 	 * @param vm the VM that has been added
2908 	 * @since 2.0
2909 	 */
fireVMAdded(IVMInstall vm)2910 	public static void fireVMAdded(IVMInstall vm) {
2911 		if (!fgInitializingVMs) {
2912 			for (IVMInstallChangedListener listener : fgVMListeners) {
2913 				listener.vmAdded(vm);
2914 			}
2915 		}
2916 	}
2917 
2918 	/**
2919 	 * Notifies all VM install changed listeners of the VM removal
2920 	 *
2921 	 * @param vm the VM that has been removed
2922 	 * @since 2.0
2923 	 */
fireVMRemoved(IVMInstall vm)2924 	public static void fireVMRemoved(IVMInstall vm) {
2925 		for (IVMInstallChangedListener listener : fgVMListeners) {
2926 			listener.vmRemoved(vm);
2927 		}
2928 	}
2929 
2930 	/**
2931 	 * Return the String representation of the default output directory of the
2932 	 * launch config's project or <code>null</code> if there is no configuration, no
2933 	 * project or some sort of problem.
2934 	 * @param config the {@link ILaunchConfiguration}
2935 	 *
2936 	 * @return the default output directory for the specified launch
2937 	 * configuration's project
2938 	 * @since 2.1
2939 	 */
getProjectOutputDirectory(ILaunchConfiguration config)2940 	public static String getProjectOutputDirectory(ILaunchConfiguration config) {
2941 		try {
2942 			if (config != null) {
2943 				IJavaProject javaProject = JavaRuntime.getJavaProject(config);
2944 				if (javaProject != null) {
2945 					IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
2946 					IPath outputLocation = javaProject.getOutputLocation();
2947 					IResource resource = root.findMember(outputLocation);
2948 					if (resource != null) {
2949 						IPath path = resource.getFullPath();
2950 						if (path != null)  {
2951 							return path.makeRelative().toString();
2952 						}
2953 					}
2954 				}
2955 			}
2956 		} catch (CoreException ce) {
2957 		}
2958 		return null;
2959 	}
2960 
2961 	/**
2962 	 * Returns a collection of source containers corresponding to the given
2963 	 * resolved runtime classpath entries.
2964 	 * <p>
2965 	 * Note that the entries must be resolved to ARCHIVE and PROJECT entries,
2966 	 * as source containers cannot be determined for unresolved entries.
2967 	 * </p>
2968 	 * @param entries entries to translate
2969 	 * @return source containers corresponding to the given runtime classpath entries
2970 	 * @since 3.1
2971 	 */
getSourceContainers(IRuntimeClasspathEntry[] entries)2972 	public static ISourceContainer[] getSourceContainers(IRuntimeClasspathEntry[] entries) {
2973 		return JavaSourceLookupUtil.translate(entries);
2974 	}
2975 
2976 	/**
2977 	 * Returns a collection of paths that should be appended to the given project's
2978 	 * <code>java.library.path</code> system property when launched. Entries are
2979 	 * searched for on the project's build path as extra classpath attributes.
2980 	 * Each entry represents an absolute path in the local file system.
2981 	 *
2982 	 * @param project the project to compute the <code>java.library.path</code> for
2983 	 * @param requiredProjects whether to consider entries in required projects
2984 	 * @return a collection of paths representing entries that should be appended
2985 	 *  to the given project's <code>java.library.path</code>
2986 	 * @throws CoreException if unable to compute the Java library path
2987 	 * @since 3.1
2988 	 * @see org.eclipse.jdt.core.IClasspathAttribute
2989 	 * @see JavaRuntime#CLASSPATH_ATTR_LIBRARY_PATH_ENTRY
2990 	 */
computeJavaLibraryPath(IJavaProject project, boolean requiredProjects)2991 	public static String[] computeJavaLibraryPath(IJavaProject project, boolean requiredProjects) throws CoreException {
2992 		Set<IJavaProject> visited = new HashSet<>();
2993 		List<String> entries = new ArrayList<>();
2994 		gatherJavaLibraryPathEntries(project, requiredProjects, visited, entries);
2995 		List<String> resolved = new ArrayList<>(entries.size());
2996 		Iterator<String> iterator = entries.iterator();
2997 		IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
2998 		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
2999 		while (iterator.hasNext()) {
3000 			String entry = iterator.next();
3001 			String resolvedEntry = manager.performStringSubstitution(entry);
3002 			IPath path = new Path(resolvedEntry);
3003 			if (path.isAbsolute()) {
3004 				File file = path.toFile();
3005 				resolved.add(file.getAbsolutePath());
3006 			} else {
3007 				IResource resource = root.findMember(path);
3008 				if (resource != null) {
3009 					IPath location = resource.getLocation();
3010 					if (location != null) {
3011 						resolved.add(location.toFile().getAbsolutePath());
3012 					}
3013 				}
3014 			}
3015 		}
3016 		return resolved.toArray(new String[resolved.size()]);
3017 	}
3018 
3019 	/**
3020 	 * Gathers all Java library entries for the given project and optionally its required
3021 	 * projects.
3022 	 *
3023 	 * @param project project to gather entries for
3024 	 * @param requiredProjects whether to consider required projects
3025 	 * @param visited projects already considered
3026 	 * @param entries collection to add library entries to
3027 	 * @throws CoreException if unable to gather classpath entries
3028 	 * @since 3.1
3029 	 */
gatherJavaLibraryPathEntries(IJavaProject project, boolean requiredProjects, Set<IJavaProject> visited, List<String> entries)3030 	private static void gatherJavaLibraryPathEntries(IJavaProject project, boolean requiredProjects, Set<IJavaProject> visited, List<String> entries) throws CoreException {
3031 		if (visited.contains(project)) {
3032 			return;
3033 		}
3034 		visited.add(project);
3035 		IClasspathEntry[] rawClasspath = project.getRawClasspath();
3036 		IClasspathEntry[] required = processJavaLibraryPathEntries(project, requiredProjects, rawClasspath, entries);
3037 		if (required != null) {
3038 			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
3039 			for (int i = 0; i < required.length; i++) {
3040 				IClasspathEntry entry = required[i];
3041 				String projectName = entry.getPath().segment(0);
3042 				IProject p = root.getProject(projectName);
3043 				if (p.exists()) {
3044 					IJavaProject requiredProject = JavaCore.create(p);
3045 					if(requiredProject.isOpen()) {
3046 						gatherJavaLibraryPathEntries(requiredProject, requiredProjects, visited, entries);
3047 					}
3048 					else if(!isOptional(entry)) {
3049 						gatherJavaLibraryPathEntries(requiredProject, requiredProjects, visited, entries);
3050 					}
3051 				}
3052 			}
3053 		}
3054 	}
3055 
3056 	/**
3057 	 * Adds all java library path extra classpath entry values to the given entries collection
3058 	 * specified on the given project's classpath, and returns a collection of required
3059 	 * projects, or <code>null</code>.
3060 	 *
3061 	 * @param project project being processed
3062 	 * @param collectRequired whether to collect required projects
3063 	 * @param classpathEntries the project's raw classpath
3064 	 * @param entries collection to add java library path entries to
3065 	 * @return required project classpath entries or <code>null</code>
3066 	 * @throws CoreException if an exception occurs
3067 	 * @since 3.1
3068 	 */
processJavaLibraryPathEntries(IJavaProject project, boolean collectRequired, IClasspathEntry[] classpathEntries, List<String> entries)3069 	private static IClasspathEntry[] processJavaLibraryPathEntries(IJavaProject project, boolean collectRequired, IClasspathEntry[] classpathEntries, List<String> entries) throws CoreException {
3070 		List<IClasspathEntry> req = null;
3071 		for (int i = 0; i < classpathEntries.length; i++) {
3072 			IClasspathEntry entry = classpathEntries[i];
3073 			IClasspathAttribute[] extraAttributes = entry.getExtraAttributes();
3074 			for (int j = 0; j < extraAttributes.length; j++) {
3075 				String[] paths = getLibraryPaths(extraAttributes[j]);
3076 				if (paths != null) {
3077 					for (int k = 0; k < paths.length; k++) {
3078 						entries.add(paths[k]);
3079 					}
3080 				}
3081 			}
3082 			if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
3083 				IClasspathContainer container = JavaCore.getClasspathContainer(entry.getPath(), project);
3084 				if (container != null) {
3085 					IClasspathEntry[] requiredProjects = processJavaLibraryPathEntries(project, collectRequired, container.getClasspathEntries(), entries);
3086 					if (requiredProjects != null) {
3087 						if (req == null) {
3088 							req = new ArrayList<>();
3089 						}
3090 						for (int j = 0; j < requiredProjects.length; j++) {
3091 							req.add(requiredProjects[j]);
3092 						}
3093 					}
3094 				}
3095 			} else if (collectRequired && entry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
3096 				if (req == null) {
3097 					req = new ArrayList<>();
3098 				}
3099 				req.add(entry);
3100 			}
3101 		}
3102 		if (req != null) {
3103 			return req.toArray(new IClasspathEntry[req.size()]);
3104 		}
3105 		return null;
3106 	}
3107 
3108 	/**
3109 	 * Creates a new classpath attribute referencing a list of shared libraries that should
3110 	 * appear on the <code>-Djava.library.path</code> system property at runtime
3111 	 * for an associated {@link IClasspathEntry}.
3112 	 * <p>
3113 	 * The factory methods <code>newLibraryPathsAttribute(String[])</code>
3114 	 * and <code>getLibraryPaths(IClasspathAttribute)</code> should be used to
3115 	 * encode and decode the attribute value.
3116 	 * </p>
3117 	 * @param paths an array of strings representing paths of shared libraries.
3118 	 * Each string is used to create an <code>IPath</code> using the constructor
3119 	 * <code>Path(String)</code>, and may contain <code>IStringVariable</code>'s.
3120 	 * Variable substitution is performed on each string before a path is constructed
3121 	 * from a string.
3122 	 * @return a classpath attribute with the name <code>CLASSPATH_ATTR_LIBRARY_PATH_ENTRY</code>
3123 	 * and an value encoded to the specified paths.
3124 	 * @since 3.1
3125 	 */
newLibraryPathsAttribute(String[] paths)3126 	public static IClasspathAttribute newLibraryPathsAttribute(String[] paths) {
3127 		StringBuilder value = new StringBuilder();
3128 		for (int i = 0; i < paths.length; i++) {
3129 			value.append(paths[i]);
3130 			if (i < (paths.length - 1)) {
3131 				value.append("|"); //$NON-NLS-1$
3132 			}
3133 		}
3134 		return JavaCore.newClasspathAttribute(CLASSPATH_ATTR_LIBRARY_PATH_ENTRY, value.toString());
3135 	}
3136 
3137 	/**
3138 	 * Returns an array of strings referencing shared libraries that should
3139 	 * appear on the <code>-Djava.library.path</code> system property at runtime
3140 	 * for an associated {@link IClasspathEntry}, or <code>null</code> if the
3141 	 * given attribute is not a <code>CLASSPATH_ATTR_LIBRARY_PATH_ENTRY</code>.
3142 	 * Each string is used to create an <code>IPath</code> using the constructor
3143 	 * <code>Path(String)</code>, and may contain <code>IStringVariable</code>'s.
3144 	 * <p>
3145 	 * The factory methods <code>newLibraryPathsAttribute(String[])</code>
3146 	 * and <code>getLibraryPaths(IClasspathAttribute)</code> should be used to
3147 	 * encode and decode the attribute value.
3148 	 * </p>
3149 	 * @param attribute a <code>CLASSPATH_ATTR_LIBRARY_PATH_ENTRY</code> classpath attribute
3150 	 * @return an array of strings referencing shared libraries that should
3151 	 * appear on the <code>-Djava.library.path</code> system property at runtime
3152 	 * for an associated {@link IClasspathEntry}, or <code>null</code> if the
3153 	 * given attribute is not a <code>CLASSPATH_ATTR_LIBRARY_PATH_ENTRY</code>.
3154 	 * Each string is used to create an <code>IPath</code> using the constructor
3155 	 * <code>Path(String)</code>, and may contain <code>IStringVariable</code>'s.
3156 	 * @since 3.1
3157 	 */
getLibraryPaths(IClasspathAttribute attribute)3158 	public static String[] getLibraryPaths(IClasspathAttribute attribute) {
3159 		if (CLASSPATH_ATTR_LIBRARY_PATH_ENTRY.equals(attribute.getName())) {
3160 			String value = attribute.getValue();
3161 			return value.split("\\|"); //$NON-NLS-1$
3162 		}
3163 		return null;
3164 	}
3165 
3166 	/**
3167 	 * Returns the execution environments manager.
3168 	 *
3169 	 * @return execution environments manager
3170 	 * @since 3.2
3171 	 */
getExecutionEnvironmentsManager()3172 	public static IExecutionEnvironmentsManager getExecutionEnvironmentsManager() {
3173 		return EnvironmentsManager.getDefault();
3174 	}
3175 
3176 	/**
3177 	 * Perform VM type and VM install initialization. Does not hold locks
3178 	 * while performing change notification.
3179 	 *
3180 	 * @since 3.2
3181 	 */
initializeVMs()3182 	private static void initializeVMs() {
3183 		VMDefinitionsContainer vmDefs = null;
3184 		boolean setPref = false;
3185 		boolean updateCompliance = false;
3186 		synchronized (fgVMLock) {
3187 			if (fgVMTypes == null) {
3188 				try {
3189 					fgInitializingVMs = true;
3190 					// 1. load VM type extensions
3191 					initializeVMTypeExtensions();
3192 					try {
3193 						vmDefs = new VMDefinitionsContainer();
3194 						// 2. add persisted VMs
3195 						setPref = addPersistedVMs(vmDefs);
3196 						IStatus status = vmDefs.getStatus();
3197 						if (status != null) {
3198 							if (status.isMultiStatus()) {
3199 								MultiStatus multi = (MultiStatus) status;
3200 								IStatus[] children = multi.getChildren();
3201 								for (int i = 0; i < children.length; i++) {
3202 									IStatus child = children[i];
3203 									if (!child.isOK()) {
3204 										LaunchingPlugin.log(child);
3205 									}
3206 								}
3207 							} else if (!status.isOK()) {
3208 								LaunchingPlugin.log(status);
3209 							}
3210 						}
3211 
3212 						// 3. if there are none, detect the eclipse runtime
3213 						if (vmDefs.getValidVMList().isEmpty()) {
3214 							// calling out to detectEclipseRuntime() could allow clients to change
3215 							// VM settings (i.e. call back into change VM settings).
3216 							VMListener listener = new VMListener();
3217 							addVMInstallChangedListener(listener);
3218 							setPref = true;
3219 							VMStandin runtime = detectEclipseRuntime();
3220 							removeVMInstallChangedListener(listener);
3221 							if (!listener.isChanged()) {
3222 								if (runtime != null) {
3223 									updateCompliance = true;
3224 									vmDefs.addVM(runtime);
3225 									vmDefs.setDefaultVMInstallCompositeID(getCompositeIdFromVM(runtime));
3226 								}
3227 							} else {
3228 								// VMs were changed - reflect current settings
3229 								addPersistedVMs(vmDefs);
3230 								vmDefs.setDefaultVMInstallCompositeID(fgDefaultVMId);
3231 								updateCompliance = fgDefaultVMId != null;
3232 							}
3233 						}
3234 						// 4. load contributed VM installs
3235 						addVMExtensions(vmDefs);
3236 						// 5. verify default VM is valid
3237 						String defId = vmDefs.getDefaultVMInstallCompositeID();
3238 						boolean validDef = false;
3239 						if (defId != null) {
3240 							Iterator<IVMInstall> iterator = vmDefs.getValidVMList().iterator();
3241 							while (iterator.hasNext()) {
3242 								IVMInstall vm = iterator.next();
3243 								if (getCompositeIdFromVM(vm).equals(defId)) {
3244 									validDef = true;
3245 									break;
3246 								}
3247 							}
3248 						}
3249 						if (!validDef) {
3250 							// use the first as the default
3251 							setPref = true;
3252 							List<IVMInstall> list = vmDefs.getValidVMList();
3253 							if (!list.isEmpty()) {
3254 								IVMInstall vm = list.get(0);
3255 								vmDefs.setDefaultVMInstallCompositeID(getCompositeIdFromVM(vm));
3256 							}
3257 						}
3258 						fgDefaultVMId = vmDefs.getDefaultVMInstallCompositeID();
3259 						fgDefaultVMConnectorId = vmDefs.getDefaultVMInstallConnectorTypeID();
3260 
3261 						// Create the underlying VMs for each valid VM
3262 						List<IVMInstall> vmList = vmDefs.getValidVMList();
3263 						Iterator<IVMInstall> vmListIterator = vmList.iterator();
3264 						while (vmListIterator.hasNext()) {
3265 							VMStandin vmStandin = (VMStandin) vmListIterator.next();
3266 							vmStandin.convertToRealVM();
3267 						}
3268 
3269 
3270 					} catch (IOException e) {
3271 						LaunchingPlugin.log(e);
3272 					}
3273 				} finally {
3274 					fgInitializingVMs = false;
3275 				}
3276 			}
3277 		}
3278 		if (vmDefs != null) {
3279 			// notify of initial VMs for backwards compatibility
3280 			IVMInstallType[] installTypes = getVMInstallTypes();
3281 			for (int i = 0; i < installTypes.length; i++) {
3282 				IVMInstallType type = installTypes[i];
3283 				IVMInstall[] installs = type.getVMInstalls();
3284 				for (int j = 0; j < installs.length; j++) {
3285 					fireVMAdded(installs[j]);
3286 				}
3287 			}
3288 
3289 			// save settings if required
3290 			if (setPref) {
3291 				try {
3292 					String xml = vmDefs.getAsXML();
3293 					InstanceScope.INSTANCE.getNode(LaunchingPlugin.ID_PLUGIN).put(PREF_VM_XML, xml);
3294 				}  catch (CoreException e) {
3295 					LaunchingPlugin.log(e);
3296 				}
3297 			}
3298 			// update compliance if required
3299 			if (updateCompliance) {
3300 				updateCompliance(getDefaultVMInstall());
3301 			}
3302 		}
3303 	}
3304 
3305 	/**
3306 	 * Update compiler compliance settings based on the given VM.
3307 	 *
3308 	 * @param vm the backing {@link IVMInstall}
3309 	 */
updateCompliance(IVMInstall vm)3310 	private static void updateCompliance(IVMInstall vm) {
3311 		if (LaunchingPlugin.isVMLogging()) {
3312 			LaunchingPlugin.log("Compliance needs an update."); //$NON-NLS-1$
3313 		}
3314         if (vm instanceof IVMInstall2) {
3315             String javaVersion = ((IVMInstall2)vm).getJavaVersion();
3316             if (javaVersion != null) {
3317             	String compliance = null;
3318             	if (javaVersion.startsWith(JavaCore.VERSION_1_5)) {
3319             		compliance = JavaCore.VERSION_1_5;
3320             	} else if (javaVersion.startsWith(JavaCore.VERSION_1_6)) {
3321             		compliance = JavaCore.VERSION_1_6;
3322             	} else if (javaVersion.startsWith(JavaCore.VERSION_1_7)) {
3323             		compliance = JavaCore.VERSION_1_7;
3324 				} else if (javaVersion.startsWith(JavaCore.VERSION_1_8)) {
3325 					compliance = JavaCore.VERSION_1_8;
3326 				} else if (javaVersion.startsWith(JavaCore.VERSION_9)
3327 						&& (javaVersion.length() == JavaCore.VERSION_9.length() || javaVersion.charAt(JavaCore.VERSION_9.length()) == '.')) {
3328 					compliance = JavaCore.VERSION_9;
3329 				} else if (javaVersion.startsWith(JavaCore.VERSION_10)
3330 						&& (javaVersion.length() == JavaCore.VERSION_10.length() || javaVersion.charAt(JavaCore.VERSION_10.length()) == '.')) {
3331 					compliance = JavaCore.VERSION_10;
3332 				} else if (javaVersion.startsWith(JavaCore.VERSION_11)
3333 						&& (javaVersion.length() == JavaCore.VERSION_11.length() || javaVersion.charAt(JavaCore.VERSION_11.length()) == '.')) {
3334 					compliance = JavaCore.VERSION_11;
3335 				} else if (javaVersion.startsWith(JavaCore.VERSION_12)
3336 						&& (javaVersion.length() == JavaCore.VERSION_12.length() || javaVersion.charAt(JavaCore.VERSION_12.length()) == '.')) {
3337 					compliance = JavaCore.VERSION_12;
3338 				} else if (javaVersion.startsWith(JavaCore.VERSION_13)
3339 						&& (javaVersion.length() == JavaCore.VERSION_13.length() || javaVersion.charAt(JavaCore.VERSION_13.length()) == '.')) {
3340 					compliance = JavaCore.VERSION_13;
3341 				} else if (javaVersion.startsWith(JavaCore.VERSION_14)
3342 						&& (javaVersion.length() == JavaCore.VERSION_14.length() || javaVersion.charAt(JavaCore.VERSION_14.length()) == '.')) {
3343 					compliance = JavaCore.VERSION_14;
3344 				} else {
3345 					compliance = JavaCore.VERSION_14; // use latest by default
3346 				}
3347 
3348             	Hashtable<String, String> options= JavaCore.getOptions();
3349 
3350             	org.osgi.service.prefs.Preferences bundleDefaults = BundleDefaultsScope.INSTANCE.getNode(JavaCore.PLUGIN_ID);
3351 
3352             	boolean isDefault =
3353             			equals(JavaCore.COMPILER_COMPLIANCE, options, bundleDefaults) &&
3354             			equals(JavaCore.COMPILER_SOURCE, options, bundleDefaults) &&
3355             			equals(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, options, bundleDefaults) &&
3356             			equals(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, options, bundleDefaults) &&
3357             			equals(JavaCore.COMPILER_PB_ENUM_IDENTIFIER, options, bundleDefaults);
3358 				if (JavaCore.compareJavaVersions(compliance, JavaCore.VERSION_10) > 0) {
3359 					isDefault = isDefault && equals(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, options, bundleDefaults)
3360 							&& equals(JavaCore.COMPILER_PB_REPORT_PREVIEW_FEATURES, options, bundleDefaults);
3361 				}
3362             	// only update the compliance settings if they are default settings, otherwise the
3363             	// settings have already been modified by a tool or user
3364 				if (LaunchingPlugin.isVMLogging()) {
3365 					LaunchingPlugin.log("Compliance to be updated is: " + compliance); //$NON-NLS-1$
3366 				}
3367             	if (isDefault) {
3368 					JavaCore.setComplianceOptions(compliance, options);
3369 					JavaCore.setOptions(options);
3370 					if (LaunchingPlugin.isVMLogging()) {
3371 						LaunchingPlugin.log("Compliance Options are updated."); //$NON-NLS-1$
3372 					}
3373             	}
3374 
3375             }
3376         }
3377 	}
3378 
3379 	/**
3380 	 * Creates a new VM based on the attributes specified in the given execution
3381 	 * environment description file. The format of the file is defined by
3382 	 * <code>http://wiki.eclipse.org/Execution_Environment_Descriptions</code>.
3383 	 *
3384 	 * @param eeFile VM definition file
3385 	 * @param name name for the VM, or <code>null</code> if a default name should be assigned
3386 	 * @param id id to assign to the new VM
3387 	 * @return VM stand-in
3388 	 * @exception CoreException if unable to create a VM from the given definition file
3389 	 * @since 3.4
3390 	 */
createVMFromDefinitionFile(File eeFile, String name, String id)3391 	public static VMStandin createVMFromDefinitionFile(File eeFile, String name, String id) throws CoreException {
3392 		ExecutionEnvironmentDescription description = new ExecutionEnvironmentDescription(eeFile);
3393 		IStatus status = EEVMType.validateDefinitionFile(description);
3394 		if (status.isOK()) {
3395 			VMStandin standin = new VMStandin(getVMInstallType(EEVMType.ID_EE_VM_TYPE), id);
3396 			if (name != null && name.length() > 0){
3397 				standin.setName(name);
3398 			} else {
3399 				name = description.getProperty(ExecutionEnvironmentDescription.EE_NAME);
3400 				if (name == null) {
3401 					name = eeFile.getName();
3402 				}
3403 				standin.setName(name);
3404 			}
3405 			String home = description.getProperty(ExecutionEnvironmentDescription.JAVA_HOME);
3406 			standin.setInstallLocation(new File(home));
3407 			standin.setLibraryLocations(description.getLibraryLocations());
3408 			standin.setVMArgs(description.getVMArguments());
3409 			standin.setJavadocLocation(EEVMType.getJavadocLocation(description.getProperties()));
3410 			standin.setAttribute(EEVMInstall.ATTR_EXECUTION_ENVIRONMENT_ID, description.getProperty(ExecutionEnvironmentDescription.CLASS_LIB_LEVEL));
3411 			File exe = description.getExecutable();
3412 			if (exe == null) {
3413 				exe = description.getConsoleExecutable();
3414 			}
3415 			if (exe != null) {
3416 				try {
3417 					standin.setAttribute(EEVMInstall.ATTR_JAVA_EXE, exe.getCanonicalPath());
3418 				} catch (IOException e) {
3419 					throw new CoreException(new Status(IStatus.ERROR, LaunchingPlugin.getUniqueIdentifier(),
3420 							LaunchingMessages.JavaRuntime_24, e));
3421 				}
3422 			}
3423 			standin.setAttribute(EEVMInstall.ATTR_JAVA_VERSION, description.getProperty(ExecutionEnvironmentDescription.LANGUAGE_LEVEL));
3424 			standin.setAttribute(EEVMInstall.ATTR_DEFINITION_FILE, eeFile.getPath());
3425 			standin.setAttribute(EEVMInstall.ATTR_DEBUG_ARGS, description.getProperty(ExecutionEnvironmentDescription.DEBUG_ARGS));
3426 			return standin;
3427 		}
3428 		throw new CoreException(status);
3429 	}
3430 
3431 	private static final String BLANK = " "; //$NON-NLS-1$
3432 	private static final String COMMA = ","; //$NON-NLS-1$
3433 	private static final String OPTION_START = "--"; //$NON-NLS-1$
3434 	private static final String ADD_MODULES = "--add-modules "; //$NON-NLS-1$
3435 	private static final String LIMIT_MODULES = "--limit-modules "; //$NON-NLS-1$
3436 
3437 	/**
3438 	 * Returns the module-related command line options for the configuration that are needed at runtime as equivalents of those options specified by
3439 	 * {@link IClasspathAttribute}s of the following names:
3440 	 * <ul>
3441 	 * <li>{@link IClasspathAttribute#ADD_EXPORTS}</li>
3442 	 * <li>{@link IClasspathAttribute#ADD_READS}</li>
3443 	 * <li>{@link IClasspathAttribute#LIMIT_MODULES}</li>
3444 	 * </ul>
3445 	 * {@link IClasspathAttribute#PATCH_MODULE} is not handled here, but in
3446 	 * {@link AbstractJavaLaunchConfigurationDelegate#getModuleCLIOptions(ILaunchConfiguration)}, which then collates all options referring to the
3447 	 * same module.
3448 	 *
3449 	 * @since 3.10
3450 	 */
getModuleCLIOptions(ILaunchConfiguration configuration)3451 	public static String getModuleCLIOptions(ILaunchConfiguration configuration) {
3452 		StringBuilder cliOptionString = new StringBuilder();
3453 		try {
3454 			IRuntimeClasspathEntry[] entries;
3455 			entries = JavaRuntime.computeUnresolvedRuntimeClasspath(configuration);
3456 
3457 			IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
3458 			for (IRuntimeClasspathEntry iRuntimeClasspathEntry : entries) {
3459 				IClasspathEntry classpathEntry = iRuntimeClasspathEntry.getClasspathEntry();
3460 				if (classpathEntry != null && classpathEntry.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
3461 					IResource res = root.findMember(classpathEntry.getPath());
3462 					IJavaProject jp = (IJavaProject) JavaCore.create(res);
3463 					if (jp.isOpen()) {
3464 						IClasspathEntry[] rawClasspath = jp.getRawClasspath();
3465 						for (IClasspathEntry iClasspathEntry : rawClasspath) {
3466 							if (iClasspathEntry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
3467 								if (JavaRuntime.JRE_CONTAINER.equals(iClasspathEntry.getPath().segment(0))) {
3468 									String cliOptions = getModuleCLIOptions(jp, iClasspathEntry);
3469 									if (cliOptionString.length() > 0 && cliOptions.length() > 0) {
3470 										cliOptionString.append(" "); //$NON-NLS-1$
3471 									}
3472 									cliOptionString.append(cliOptions);
3473 								}
3474 							}
3475 						}
3476 					}
3477 
3478 				}
3479 			}
3480 		}
3481 		catch (CoreException e) {
3482 			LaunchingPlugin.log(e);
3483 		}
3484 		return cliOptionString.toString().trim();
3485 	}
3486 
3487 	/**
3488 	 * Returns the module-related command line options that are needed at runtime as equivalents of those options specified by
3489 	 * {@link IClasspathAttribute}s of the following names:
3490 	 * <ul>
3491 	 * <li>{@link IClasspathAttribute#ADD_EXPORTS}</li>
3492 	 * <li>{@link IClasspathAttribute#ADD_OPENS}</li>
3493 	 * <li>{@link IClasspathAttribute#ADD_READS}</li>
3494 	 * <li>{@link IClasspathAttribute#LIMIT_MODULES}</li>
3495 	 * </ul>
3496 	 * <p>
3497 	 * Note that the {@link IClasspathAttribute#LIMIT_MODULES} value may be split into an {@code --add-modules} part and a {@code --limit-modules}
3498 	 * part.
3499 	 * </p>
3500 	 *
3501 	 * @param project
3502 	 *            the project holding the main class to be launched
3503 	 * @param systemLibrary
3504 	 *            the classpath entry of the given project which represents the JRE System Library
3505 	 * @return module-related command line options suitable for running the application.
3506 	 * @throws JavaModelException
3507 	 *             when access to the classpath or module description of the given project fails.
3508 	 */
getModuleCLIOptions(IJavaProject project, IClasspathEntry systemLibrary)3509 	private static String getModuleCLIOptions(IJavaProject project, IClasspathEntry systemLibrary) throws JavaModelException {
3510 		StringBuilder buf = new StringBuilder();
3511 		for (IClasspathEntry classpathEntry : project.getRawClasspath()) {
3512 			for (IClasspathAttribute classpathAttribute : classpathEntry.getExtraAttributes()) {
3513 				String optName = classpathAttribute.getName();
3514 				switch (optName) {
3515 					case IClasspathAttribute.ADD_EXPORTS:
3516 					case IClasspathAttribute.ADD_OPENS:
3517 					case IClasspathAttribute.ADD_READS: {
3518 						String readModules = classpathAttribute.getValue();
3519 						int equalsIdx = readModules.indexOf('=');
3520 						if (equalsIdx != -1) {
3521 							for (String readModule : readModules.split(":")) { //$NON-NLS-1$
3522 								buf.append(OPTION_START).append(optName).append(BLANK).append(readModule).append(BLANK);
3523 							}
3524 						} else {
3525 							buf.append(OPTION_START).append(optName).append(BLANK).append(readModules).append(BLANK);
3526 						}
3527 						break;
3528 					}
3529 					// case IClasspathAttribute.PATCH_MODULE: handled in
3530 					// org.eclipse.jdt.launching.AbstractJavaLaunchConfigurationDelegate.getModuleCLIOptions(ILaunchConfiguration)
3531 					case IClasspathAttribute.LIMIT_MODULES:
3532 						addLimitModules(buf, project, systemLibrary, classpathAttribute.getValue());
3533 						break;
3534 				}
3535 			}
3536 		}
3537 		return buf.toString().trim();
3538 	}
addLimitModules(StringBuilder buf, IJavaProject prj, IClasspathEntry systemLibrary, String value)3539 	private static void addLimitModules(StringBuilder buf, IJavaProject prj, IClasspathEntry systemLibrary, String value) throws JavaModelException {
3540 		String[] modules = value.split(COMMA);
3541 		boolean isUnnamed = prj.getModuleDescription() == null;
3542 		if (isUnnamed) {
3543 			Set<String> selected = new HashSet<>(Arrays.asList(modules));
3544 			List<IPackageFragmentRoot> allSystemRoots = Arrays.asList(prj.findUnfilteredPackageFragmentRoots(systemLibrary));
3545 			Set<String> defaultModules = getDefaultModules(allSystemRoots);
3546 			Set<String> limit = new HashSet<>(defaultModules); // contains some redundancy, but is no full closure
3547 
3548 			// selected contains the minimal representation, now compute the transitive closure for comparison with semi-closed defaultModules:
3549 			Map<String, IModuleDescription> allModules = allSystemRoots.stream() //
3550 					.map(r -> r.getModuleDescription()) //
3551 					.filter(Objects::nonNull) //
3552 					.collect(Collectors.toMap(IModuleDescription::getElementName, module -> module));
3553 			Set<String> selectedClosure = closure(selected, new HashSet<>(), allModules);
3554 
3555 			if (limit.retainAll(selectedClosure)) { // limit = selected ∩ default -- only add the option, if limit ⊂ default
3556 				if (limit.isEmpty()) {
3557 					throw new IllegalArgumentException("Cannot hide all modules, at least java.base is required"); //$NON-NLS-1$
3558 				}
3559 				buf.append(LIMIT_MODULES).append(joinedSortedList(reduceNames(limit, allModules.values()))).append(BLANK);
3560 			}
3561 
3562 			selectedClosure.removeAll(defaultModules);
3563 			if (!selectedClosure.isEmpty()) { // add = selected \ default
3564 				buf.append(ADD_MODULES).append(joinedSortedList(selectedClosure)).append(BLANK);
3565 			}
3566 		} else {
3567 			Arrays.sort(modules);
3568 			buf.append(LIMIT_MODULES).append(String.join(COMMA, modules)).append(BLANK);
3569 		}
3570 	}
3571 
closure(Collection<String> moduleNames, Set<String> collected, Map<String, IModuleDescription> allModules)3572 	private static Set<String> closure(Collection<String> moduleNames, Set<String> collected, Map<String, IModuleDescription> allModules) {
3573 		for (String name : moduleNames) {
3574 			if (collected.add(name)) {
3575 				IModuleDescription module = allModules.get(name);
3576 				if (module != null) {
3577 					try {
3578 						closure(Arrays.asList(module.getRequiredModuleNames()), collected, allModules);
3579 					} catch (JavaModelException e) {
3580 						LaunchingPlugin.log(e);
3581 					}
3582 				}
3583 			}
3584 		}
3585 		return collected;
3586 	}
3587 
reduceNames(Collection<String> names, Collection<IModuleDescription> allModules)3588 	private static Collection<String> reduceNames(Collection<String> names, Collection<IModuleDescription> allModules) {
3589 		// build a reverse dependency tree:
3590 		Map<String, List<String>> moduleRequiredByModules = new HashMap<>();
3591 		for (IModuleDescription module : allModules) {
3592 			if (!names.contains(module.getElementName())) {
3593 				continue;
3594 			}
3595 			try {
3596 				for (String required : module.getRequiredModuleNames()) {
3597 					List<String> dominators = moduleRequiredByModules.get(required);
3598 					if (dominators == null) {
3599 						moduleRequiredByModules.put(required, dominators = new ArrayList<>());
3600 					}
3601 					dominators.add(module.getElementName());
3602 				}
3603 			} catch (CoreException e) {
3604 				LaunchingPlugin.log(e);
3605 				return names; // unreduced
3606 			}
3607 		}
3608 		// use the tree to find and eliminate redundancy:
3609 		List<String> reduced = new ArrayList<>();
3610 		outer: for (String name : names) {
3611 			List<String> dominators = moduleRequiredByModules.get(name);
3612 			if (dominators != null) {
3613 				for (String dominator : dominators) {
3614 					if (names.contains(dominator)) {
3615 						continue outer;
3616 					}
3617 				}
3618 			}
3619 			reduced.add(name);
3620 		}
3621 		return reduced;
3622 	}
3623 
getDefaultModules(List<IPackageFragmentRoot> allSystemRoots)3624 	private static Set<String> getDefaultModules(List<IPackageFragmentRoot> allSystemRoots) throws JavaModelException {
3625 		HashMap<String, String[]> moduleDescriptions = new HashMap<>();
3626 		for (IPackageFragmentRoot packageFragmentRoot : allSystemRoots) {
3627 			IModuleDescription module = packageFragmentRoot.getModuleDescription();
3628 			if (module != null) {
3629 				moduleDescriptions.put(module.getElementName(), module.getRequiredModuleNames());
3630 			}
3631 		}
3632 		HashSet<String> result = new HashSet<>();
3633 		HashSet<String> todo = new HashSet<>(JavaProject.defaultRootModules(allSystemRoots));
3634 		while (!todo.isEmpty()) {
3635 			HashSet<String> more = new HashSet<>();
3636 			for (String s : todo) {
3637 				if (result.add(s)) {
3638 					String[] requiredModules = moduleDescriptions.get(s);
3639 					if (requiredModules != null) {
3640 						Collections.addAll(more, requiredModules);
3641 					}
3642 				}
3643 			}
3644 			todo = more;
3645 		}
3646 		return result;
3647 	}
3648 
joinedSortedList(Collection<String> list)3649 	private static String joinedSortedList(Collection<String> list) {
3650 		String[] limitArray = list.toArray(new String[list.size()]);
3651 		Arrays.sort(limitArray);
3652 		return String.join(COMMA, limitArray);
3653 	}
3654 }
3655