| /******************************************************************************* |
| * Copyright (c) 2000, 2020 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Frits Jalvingh - Contribution for Bug 459831 - [launching] Support attaching |
| * external annotations to a JRE container |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.launching; |
| |
| |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.jdt.core.IAccessRule; |
| import org.eclipse.jdt.core.IClasspathAttribute; |
| import org.eclipse.jdt.core.IClasspathContainer; |
| import org.eclipse.jdt.core.IClasspathEntry; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.launching.IVMInstall; |
| import org.eclipse.jdt.launching.IVMInstallChangedListener; |
| import org.eclipse.jdt.launching.JavaRuntime; |
| import org.eclipse.jdt.launching.LibraryLocation; |
| import org.eclipse.jdt.launching.PropertyChangeEvent; |
| import org.eclipse.jdt.launching.environments.IExecutionEnvironment; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * JRE Container - resolves a classpath container variable to a JRE |
| */ |
| public class JREContainer implements IClasspathContainer { |
| |
| /** |
| * Corresponding JRE |
| */ |
| private IVMInstall fVMInstall = null; |
| |
| /** |
| * Container path used to resolve to this JRE |
| */ |
| private IPath fPath = null; |
| |
| /** |
| * The project this container is for |
| */ |
| private IJavaProject fProject = null; |
| |
| /** |
| * Cache of classpath entries per VM install. Cleared when a VM changes. |
| */ |
| private static Map<IVMInstall, IClasspathEntry[]> fgClasspathEntries = new HashMap<>(10); |
| |
| /** |
| * Variable to return an empty array of <code>IAccessRule</code>s |
| */ |
| private static IAccessRule[] EMPTY_RULES = new IAccessRule[0]; |
| |
| /** |
| * Map of {IVMInstall -> Map of {{IExeuctionEnvironment, IAccessRule[][]} -> {IClasspathEntry[]}} |
| */ |
| private static Map<RuleKey, RuleEntry> fgClasspathEntriesWithRules = new HashMap<>(10); |
| |
| /** |
| * A single key entry for the cache of access rules and classpath entries |
| * A rule key is made up of an <code>IVMInstall</code> and an execution environment id |
| * @since 3.3 |
| */ |
| static class RuleKey { |
| private String fEnvironmentId = null; |
| private IVMInstall fInstall = null; |
| |
| /** |
| * Constructor |
| * @param install the VM |
| * @param environmentId the environment |
| */ |
| public RuleKey(IVMInstall install, String environmentId) { |
| fInstall = install; |
| fEnvironmentId = environmentId; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| if (obj instanceof RuleKey) { |
| RuleKey key = (RuleKey) obj; |
| return fEnvironmentId.equals(key.fEnvironmentId) && fInstall.equals(key.fInstall); |
| } |
| return false; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#hashCode() |
| */ |
| @Override |
| public int hashCode() { |
| return fEnvironmentId.hashCode() + fInstall.hashCode(); |
| } |
| } |
| |
| /** |
| * Holds an entry for the cache of access rules/classpath entries. |
| * An entry is made up of an array of classpath entries and the collection of access rules. |
| * @since 3.3 |
| */ |
| static class RuleEntry { |
| private IAccessRule[][] fRules = null; |
| private IClasspathEntry[] fEntries = null; |
| |
| /** |
| * Constructor |
| * @param rules the rules |
| * @param entries the entries |
| */ |
| public RuleEntry(IAccessRule[][] rules, IClasspathEntry[] entries) { |
| fRules = rules; |
| fEntries = entries; |
| } |
| |
| /** |
| * Returns the collection of classpath entries for this RuleEntry |
| * @return the cached array of classpath entries |
| */ |
| public IClasspathEntry[] getClasspathEntries() { |
| return fEntries; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.lang.Object#equals(java.lang.Object) |
| */ |
| @Override |
| public boolean equals(Object obj) { |
| IAccessRule[][] rules = null; |
| if(obj instanceof RuleEntry) { |
| rules = ((RuleEntry)obj).fRules; |
| } |
| if(obj instanceof IAccessRule[][]) { |
| rules = (IAccessRule[][]) obj; |
| } |
| if (fRules == rules) { |
| return true; |
| } |
| if(rules != null) { |
| if (fRules.length == rules.length) { |
| for (int i = 0; i < fRules.length; i++) { |
| if (!rulesEqual(fRules[i], rules[i])){ |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks if the two arrays of rules are equal (same rules in each position in the array) |
| * |
| * @param a First list of rules to compare, must not be <code>null</code> |
| * @param b Second list of rules to compare, must not be <code>null</code> |
| * @return <code>true</code> if the arrays are equal, <code>false</code> otherwise |
| */ |
| private static boolean rulesEqual(IAccessRule[] a, IAccessRule[] b){ |
| if (a == b){ |
| return true; |
| } |
| if (a.length != b.length){ |
| return false; |
| } |
| for (int j = 0; j < a.length; j++) { |
| if (!a[j].equals(b[j])) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** |
| * Add a VM changed listener to clear cached values when a VM changes or is removed |
| */ |
| static { |
| IVMInstallChangedListener listener = new IVMInstallChangedListener() { |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#defaultVMInstallChanged(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void defaultVMInstallChanged(IVMInstall previous, IVMInstall current) {} |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmAdded(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmAdded(IVMInstall newVm) {} |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent) |
| */ |
| @Override |
| public void vmChanged(PropertyChangeEvent event) { |
| if (event.getSource() != null) { |
| fgClasspathEntries.remove(event.getSource()); |
| removeRuleEntry(event.getSource()); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmRemoved(org.eclipse.jdt.launching.IVMInstall) |
| */ |
| @Override |
| public void vmRemoved(IVMInstall removedVm) { |
| fgClasspathEntries.remove(removedVm); |
| removeRuleEntry(removedVm); |
| } |
| |
| /** |
| * Removes all occurrences of the given VM found as part key members in the current |
| * cache for classpath entries |
| * @param obj an object which should be castable to IVMInstall |
| */ |
| private void removeRuleEntry(Object obj) { |
| if(obj instanceof IVMInstall) { |
| IVMInstall install = (IVMInstall) obj; |
| RuleKey key = null; |
| ArrayList<RuleKey> list = new ArrayList<>(); |
| for(Iterator<RuleKey> iter = fgClasspathEntriesWithRules.keySet().iterator(); iter.hasNext();) { |
| key = iter.next(); |
| if(key.fInstall.equals(install)) { |
| list.add(key); |
| } |
| } |
| for(int i = 0; i < list.size(); i++) { |
| fgClasspathEntriesWithRules.remove(list.get(i)); |
| } |
| } |
| } |
| }; |
| JavaRuntime.addVMInstallChangedListener(listener); |
| } |
| |
| /** |
| * Returns the classpath entries associated with the given VM |
| * in the context of the given path and project. |
| * |
| * @param vm the VM |
| * @param containerPath the container path resolution is for |
| * @param project project the resolution is for |
| * @return classpath entries |
| */ |
| private static IClasspathEntry[] getClasspathEntries(IVMInstall vm, IPath containerPath, IJavaProject project) { |
| String id = JavaRuntime.getExecutionEnvironmentId(containerPath); |
| IClasspathEntry[] entries = null; |
| if (id == null) { |
| // cache classpath entries per JRE when not bound to an EE |
| entries = fgClasspathEntries.get(vm); |
| if (entries == null) { |
| entries = computeClasspathEntries(vm, project, id); |
| fgClasspathEntries.put(vm, entries); |
| } |
| } else { |
| if (LaunchingPlugin.DEBUG_JRE_CONTAINER) { |
| LaunchingPlugin.trace("\tEE:\t" + id); //$NON-NLS-1$ |
| } |
| // dynamically compute entries when bound to an EE |
| entries = computeClasspathEntries(vm, project, id); |
| } |
| return entries; |
| } |
| |
| /** |
| * Computes the classpath entries associated with a VM - one entry per library |
| * in the context of the given path and project. |
| * |
| * @param vm the VM |
| * @param project the project the resolution is for |
| * @param environmentId execution environment the resolution is for, or <code>null</code> |
| * @return classpath entries |
| */ |
| @SuppressWarnings("unlikely-arg-type") |
| private static IClasspathEntry[] computeClasspathEntries(IVMInstall vm, IJavaProject project, String environmentId) { |
| LibraryLocation[] libs = vm.getLibraryLocations(); |
| boolean overrideJavaDoc = false; |
| if (libs == null) { |
| libs = JavaRuntime.getLibraryLocations(vm); |
| overrideJavaDoc = true; |
| } |
| IAccessRule[][] rules = null; |
| if (environmentId != null) { |
| // compute access rules for execution environment |
| IExecutionEnvironment environment = JavaRuntime.getExecutionEnvironmentsManager().getEnvironment(environmentId); |
| if (environment != null) { |
| rules = environment.getAccessRules(vm, libs, project); |
| } |
| } |
| RuleKey key = null; |
| if (vm != null && rules != null && environmentId != null) { |
| key = new RuleKey(vm, environmentId); |
| RuleEntry entry = fgClasspathEntriesWithRules.get(key); |
| if(entry != null && entry.equals(rules)) { |
| return entry.getClasspathEntries(); |
| } |
| } |
| List<IClasspathEntry> entries = new ArrayList<>(libs.length); |
| for (int i = 0; i < libs.length; i++) { |
| if (!libs[i].getSystemLibraryPath().isEmpty()) { |
| IPath sourcePath = libs[i].getSystemLibrarySourcePath(); |
| if (sourcePath.isEmpty()) { |
| sourcePath = null; |
| } |
| IPath rootPath = libs[i].getPackageRootPath(); |
| if (rootPath.isEmpty()) { |
| rootPath = null; |
| } |
| // construct the classpath attributes for this library location |
| IClasspathAttribute[] attributes = JREContainer.buildClasspathAttributes(vm, libs[i], overrideJavaDoc); |
| IAccessRule[] libRules = null; |
| if (rules != null) { |
| libRules = rules[i]; |
| } else { |
| libRules = EMPTY_RULES; |
| } |
| entries.add(JavaCore.newLibraryEntry(libs[i].getSystemLibraryPath(), sourcePath, rootPath, libRules, attributes, false)); |
| } |
| } |
| IClasspathEntry[] cpEntries = entries.toArray(new IClasspathEntry[entries.size()]); |
| if (key != null && rules != null) { |
| fgClasspathEntriesWithRules.put(key, new RuleEntry(rules, cpEntries)); |
| } |
| return cpEntries; |
| } |
| |
| private static IClasspathAttribute[] buildClasspathAttributes(final IVMInstall vm, final LibraryLocation lib, final boolean overrideJavaDoc) { |
| |
| List<IClasspathAttribute> classpathAttributes = new LinkedList<>(); |
| // process the javadoc location |
| URL javadocLocation = lib.getJavadocLocation(); |
| if (overrideJavaDoc && javadocLocation == null) { |
| javadocLocation = vm.getJavadocLocation(); |
| } |
| if(javadocLocation != null) { |
| IClasspathAttribute javadocCPAttribute = JavaCore.newClasspathAttribute(IClasspathAttribute.JAVADOC_LOCATION_ATTRIBUTE_NAME, javadocLocation.toExternalForm()); |
| classpathAttributes.add(javadocCPAttribute); |
| } |
| // process the index location |
| URL indexLocation = lib.getIndexLocation(); |
| if(indexLocation != null) { |
| IClasspathAttribute indexCPLocation = JavaCore.newClasspathAttribute(IClasspathAttribute.INDEX_LOCATION_ATTRIBUTE_NAME, indexLocation.toExternalForm()); |
| classpathAttributes.add(indexCPLocation); |
| } |
| IPath annotationsPath = lib.getExternalAnnotationsPath(); |
| if (null != annotationsPath && !annotationsPath.isEmpty()) { |
| IClasspathAttribute xAnnLocation = JavaCore.newClasspathAttribute(IClasspathAttribute.EXTERNAL_ANNOTATION_PATH, annotationsPath.toPortableString()); |
| classpathAttributes.add(xAnnLocation); |
| } |
| |
| return classpathAttributes.toArray(new IClasspathAttribute[classpathAttributes.size()]); |
| } |
| |
| /** |
| * Constructs a JRE classpath container on the given VM install |
| * |
| * @param vm VM install - cannot be <code>null</code> |
| * @param path container path used to resolve this JRE |
| * @param project the project context |
| */ |
| public JREContainer(IVMInstall vm, IPath path, IJavaProject project) { |
| fVMInstall = vm; |
| fPath = path; |
| fProject = project; |
| } |
| |
| /** |
| * @see IClasspathContainer#getClasspathEntries() |
| */ |
| @Override |
| public IClasspathEntry[] getClasspathEntries() { |
| if (LaunchingPlugin.DEBUG_JRE_CONTAINER) { |
| LaunchingPlugin.trace("<JRE_CONTAINER> getClasspathEntries() " + this.toString()); //$NON-NLS-1$ |
| LaunchingPlugin.trace("\tJRE:\t" + fVMInstall.getName()); //$NON-NLS-1$ |
| LaunchingPlugin.trace("\tPath:\t" + getPath().toString()); //$NON-NLS-1$ |
| LaunchingPlugin.trace("\tProj:\t" + fProject.getProject().getName()); //$NON-NLS-1$ |
| } |
| IClasspathEntry[] entries = getClasspathEntries(fVMInstall, getPath(), fProject); |
| if (LaunchingPlugin.DEBUG_JRE_CONTAINER) { |
| LaunchingPlugin.trace("\tResolved " + entries.length + " entries:"); //$NON-NLS-1$//$NON-NLS-2$ |
| } |
| return entries; |
| } |
| |
| /** |
| * @see IClasspathContainer#getDescription() |
| */ |
| @Override |
| public String getDescription() { |
| String environmentId = JavaRuntime.getExecutionEnvironmentId(getPath()); |
| String tag = null; |
| if (environmentId == null) { |
| tag = fVMInstall.getName(); |
| } else { |
| tag = environmentId; |
| } |
| return NLS.bind(LaunchingMessages.JREContainer_JRE_System_Library_1, new String[]{tag}); |
| } |
| |
| /** |
| * @see IClasspathContainer#getKind() |
| */ |
| @Override |
| public int getKind() { |
| return IClasspathContainer.K_DEFAULT_SYSTEM; |
| } |
| |
| /** |
| * @see IClasspathContainer#getPath() |
| */ |
| @Override |
| public IPath getPath() { |
| return fPath; |
| } |
| |
| } |