blob: 3e9ff140d484ff4ed08f2b048b6c94e14039d3a3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 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
*******************************************************************************/
package org.eclipse.jdt.internal.launching.environments;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.IAccessRule;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.launching.LaunchingPlugin;
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.IAccessRuleParticipant;
import org.eclipse.jdt.launching.environments.IExecutionEnvironment;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.Version;
/**
* A contributed execution environment.
*
* @since 3.2
*/
class ExecutionEnvironment implements IExecutionEnvironment {
/**
* Add a VM changed listener to clear cached values when a VM changes or is removed
*/
private IVMInstallChangedListener fListener = 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) {
fParticipantMap.remove(event.getSource());
fRuleCache.remove(event.getSource());
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmRemoved(org.eclipse.jdt.launching.IVMInstall)
*/
@Override
public void vmRemoved(IVMInstall removedVm) {
fParticipantMap.remove(removedVm);
fRuleCache.remove(removedVm);
}
};
/**
* The backing <code>IConfigurationElement</code>
*/
private IConfigurationElement fElement;
/**
* Environment specific rule participant or <code>null</code> if none.
*/
private IAccessRuleParticipant fRuleParticipant;
/**
* OSGi profile properties or <code>null</code> if none.
*/
private Properties fProfileProperties;
/**
* Whether profile properties have been initialized
*/
private boolean fPropertiesInitialized;
/**
* Set of compatible vm's - just the strictly compatible ones
*/
private Set<IVMInstall> fStrictlyCompatible = new HashSet<>();
/**
* All compatible vm's
*/
private List<IVMInstall> fCompatibleVMs = new ArrayList<>();
/**
* default VM install or <code>null</code> if none
*/
private IVMInstall fDefault = null;
/**
* Cache of access rule participants to consider for this environment.
*/
private IAccessRuleParticipant[] fParticipants = null;
/**
* Map of {IVMInstall -> Map of {participant -> IAccessRule[][]}}.
* Caches access rules returned by each participant for a given VM.
* @since 3.3
*/
private Map<IVMInstall, Map<IAccessRuleParticipant, IAccessRule[][]>> fParticipantMap = new HashMap<>();
/**
* Cache of VM -> IAccessRule[][] based on the current state of the participant
* map. These are the union of the latest rules generated by the participants
* for a specific VM.
* @since 3.3
*/
private Map<IVMInstall, IAccessRule[][]> fRuleCache = new HashMap<>();
/**
* Wild card pattern matching all files
*/
private static final IPath ALL_PATTERN = new Path("**/*"); //$NON-NLS-1$
/**
* Prefix of compiler settings in properties file
*/
private static final String COMPILER_SETTING_PREFIX = JavaCore.PLUGIN_ID + ".compiler"; //$NON-NLS-1$
/**
* Constructor
* @param element the backing {@link IConfigurationElement}
*/
ExecutionEnvironment(IConfigurationElement element) {
fElement = element;
fPropertiesInitialized = false;
String attribute = fElement.getAttribute(EnvironmentsManager.RULE_PARTICIPANT_ELEMENT);
if (attribute != null) {
fRuleParticipant = new AccessRuleParticipant(fElement);
}
JavaRuntime.addVMInstallChangedListener(fListener);
}
/**
* Initializes the <code>EnvironmentsManager</code>
*/
private void init() {
EnvironmentsManager.getDefault().initializeCompatibilities();
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getId()
*/
@Override
public String getId() {
return fElement.getAttribute("id"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getDescription()
*/
@Override
public String getDescription() {
return fElement.getAttribute("description"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getCompatibleVMs()
*/
@Override
public IVMInstall[] getCompatibleVMs() {
init();
return fCompatibleVMs.toArray(new IVMInstall[fCompatibleVMs.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#isStrictlyCompatible(org.eclipse.jdt.launching.IVMInstall)
*/
@Override
public boolean isStrictlyCompatible(IVMInstall vm) {
init();
return fStrictlyCompatible.contains(vm);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getDefaultVM()
*/
@Override
public IVMInstall getDefaultVM() {
init();
return fDefault;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#setDefaultVM(org.eclipse.jdt.launching.IVMInstall)
*/
@Override
public void setDefaultVM(IVMInstall vm) {
init();
if (vm != null && !fCompatibleVMs.contains(vm)) {
throw new IllegalArgumentException(NLS.bind(EnvironmentMessages.EnvironmentsManager_0, new String[]{getId()}));
}
if (vm != null && vm.equals(fDefault)) {
return;
}
fDefault = vm;
EnvironmentsManager.getDefault().updateDefaultVMs();
// update classpath containers
rebindClasspathContainers();
}
/**
* Updates Java projects referencing this environment, if any.
*/
private void rebindClasspathContainers() {
IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot());
if (model != null) {
try {
List<IJavaProject> updates = new ArrayList<>();
IJavaProject[] javaProjects = model.getJavaProjects();
IPath path = JavaRuntime.newJREContainerPath(this);
for (int i = 0; i < javaProjects.length; i++) {
IJavaProject project = javaProjects[i];
IClasspathEntry[] rawClasspath = project.getRawClasspath();
for (int j = 0; j < rawClasspath.length; j++) {
IClasspathEntry entry = rawClasspath[j];
if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) {
if (entry.getPath().equals(path)) {
updates.add(project);
}
}
}
}
if (!updates.isEmpty()) {
JavaCore.setClasspathContainer(path,
updates.toArray(new IJavaProject[updates.size()]),
new IClasspathContainer[updates.size()],
new NullProgressMonitor());
}
} catch (JavaModelException e) {
LaunchingPlugin.log(e);
}
}
}
/**
* Adds the specified VM to the listing of compatible VMs, also
* adds the VM to the listing of strictly compatible ones based on
* the strictlyCompatible flag
* @param vm the VM to add to the environment
* @param strictlyCompatible if it is strictly compatible
*/
void add(IVMInstall vm, boolean strictlyCompatible) {
if (fCompatibleVMs.contains(vm)) {
return;
}
fCompatibleVMs.add(vm);
if (strictlyCompatible) {
fStrictlyCompatible.add(vm);
}
}
/**
* Removes the specified VM from the listings of VMs
* @param vm the VM to remove
*/
void remove(IVMInstall vm) {
fCompatibleVMs.remove(vm);
fStrictlyCompatible.remove(vm);
}
/**
* Sets the default VM to be the one specified
* @param vm the VM to set as the default
*/
void initDefaultVM(IVMInstall vm) {
fDefault = vm;
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getAccessRules(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.LibraryLocation[], org.eclipse.jdt.core.IJavaProject)
*/
@Override
public IAccessRule[][] getAccessRules(IVMInstall vm, LibraryLocation[] libraries, IJavaProject project) {
IAccessRuleParticipant[] participants = getParticipants();
Map<IAccessRuleParticipant, IAccessRule[][]> rulesByParticipant = collectRulesByParticipant(participants, vm, libraries, project);
synchronized (this) {
Map<IAccessRuleParticipant, IAccessRule[][]> cachedRules = fParticipantMap.get(vm);
if (cachedRules == null || !cachedRules.equals(rulesByParticipant)) {
ArrayList<List<IAccessRule>> libLists = new ArrayList<>(); // array of lists of access rules
for (int i = 0; i < libraries.length; i++) {
libLists.add(new ArrayList<IAccessRule>());
}
for (int i = 0; i < participants.length; i++) {
IAccessRuleParticipant participant = participants[i];
addRules(rulesByParticipant.get(participant), libLists);
}
IAccessRule[][] allRules = new IAccessRule[libraries.length][];
for (int i = 0; i < libLists.size(); i++) {
List<IAccessRule> l = libLists.get(i);
allRules[i] = l.toArray(new IAccessRule[l.size()]);
}
fParticipantMap.put(vm, rulesByParticipant);
fRuleCache.put(vm, allRules);
return allRules;
}
return fRuleCache.get(vm);
}
}
/**
* Returns all access rule participants to consider for this environment.
* Includes any participant contributed with this environment and all other
* stand alone participants.
*
* @return access rule participants to consider for this environment
*/
private synchronized IAccessRuleParticipant[] getParticipants() {
if (fParticipants == null) {
// check participants first
IAccessRuleParticipant[] participants = EnvironmentsManager.getDefault().getAccessRuleParticipants();
if (fRuleParticipant != null) {
// ensure environment specific provider is last and not duplicated
LinkedHashSet<IAccessRuleParticipant> set = new LinkedHashSet<>();
for (int i = 0; i < participants.length; i++) {
set.add(participants[i]);
}
// remove, add to make last
set.remove(fRuleParticipant);
set.add(fRuleParticipant);
participants = set.toArray(new IAccessRuleParticipant[set.size()]);
}
fParticipants = participants;
}
return fParticipants;
}
/**
* Returns a map of participant to the access rules for that participant for the given
* VM, libraries, and project.
*
* @param participants access rule participants
* @param vm the VM
* @param libraries the {@link LibraryLocation}s
* @param project the {@link IJavaProject} context
* @return the mapping of {@link IAccessRuleParticipant} to {@link IAccessRule}s
*/
private Map<IAccessRuleParticipant, IAccessRule[][]> collectRulesByParticipant(IAccessRuleParticipant[] participants, IVMInstall vm, LibraryLocation[] libraries, IJavaProject project) {
Map<IAccessRuleParticipant, IAccessRule[][]> map = new HashMap<>();
for (int i = 0; i < participants.length; i++) {
// TODO: use safe runnable
map.put(participants[i], participants[i].getAccessRules(this, vm, libraries, project));
}
return map;
}
/**
* Adds the access rules to each list in the given collection. If the last rule in a
* given collection is the wild card pattern then no more rules are added to that collection.
*
* @param accessRules the list of {@link IAccessRule}s
* @param collect the array of lists to collect the {@link IAccessRule}s in
*/
private void addRules(IAccessRule[][] accessRules, ArrayList<List<IAccessRule>> collect) {
for (int i = 0; i < accessRules.length; i++) {
IAccessRule[] libRules = accessRules[i];
List<IAccessRule> list = collect.get(i);
// if the last rule is a **/* pattern, don't add any more rules, as they will have no effect
if (!list.isEmpty()) {
IAccessRule lastRule = list.get(list.size() - 1);
if(lastRule.getPattern().equals(ALL_PATTERN)) {
continue;
}
}
for (int j = 0; j < libRules.length; j++) {
list.add(libRules[j]);
}
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getProfileProperties()
*/
@Override
public Properties getProfileProperties() {
if (!fPropertiesInitialized) {
fPropertiesInitialized = true;
String path = fElement.getAttribute("profileProperties"); //$NON-NLS-1$
Bundle bundle = null;
if (path == null) {
// attempt default profiles known to OSGi
bundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
path = getId().replace('/', '_') + ".profile"; //$NON-NLS-1$
} else {
// read provided file
bundle = Platform.getBundle(fElement.getContributor().getName());
}
if (bundle != null && path != null) {
fProfileProperties = getJavaProfileProperties(bundle, path);
}
}
return fProfileProperties;
}
/**
* Returns properties file contained in the specified bundle at the given
* bundle relative path, or <code>null</code> if none.
*
* @param bundle bundle to locate file in
* @param path bundle relative path to properties file
* @return properties or <code>null</code> if none
*/
private Properties getJavaProfileProperties(Bundle bundle, String path) {
Properties profile = new Properties();
URL profileURL = bundle.getEntry(path);
if (profileURL != null) {
try (InputStream is = profileURL.openStream()) {
profileURL = FileLocator.resolve(profileURL);
if (is != null) {
profile.load(is);
fixJavaSE9ComplianceSourceTargetLevels(profile);
}
} catch (IOException e) {
return null;
}
} else {
if (getCompliance() == null) {
return null;
}
profile.setProperty(JavaCore.COMPILER_COMPLIANCE, getCompliance());
profile.setProperty(JavaCore.COMPILER_SOURCE, getCompliance());
profile.setProperty(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, getCompliance());
profile.setProperty("org.eclipse.jdt.core.compiler.problem.assertIdentifier", "error"); //$NON-NLS-1$ //$NON-NLS-2$
profile.setProperty("org.eclipse.jdt.core.compiler.problem.enumIdentifier", "error"); //$NON-NLS-1$ //$NON-NLS-2$
profile.setProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, calculateVMExecutionEnvs(new Version(getCompliance())));
profile.setProperty(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED);
}
return profile;
}
private static final String JAVASE = "JavaSE"; //$NON-NLS-1$
private String calculateVMExecutionEnvs(Version javaVersion) {
StringBuilder result = new StringBuilder("OSGi/Minimum-1.0, OSGi/Minimum-1.1, OSGi/Minimum-1.2, JavaSE/compact1-1.8, JavaSE/compact2-1.8, JavaSE/compact3-1.8, JRE-1.1, J2SE-1.2, J2SE-1.3, J2SE-1.4, J2SE-1.5, JavaSE-1.6, JavaSE-1.7, JavaSE-1.8"); //$NON-NLS-1$
Version v = new Version(9, 0, 0);
while (v.compareTo(javaVersion) <= 0) {
result.append(',').append(' ').append(JAVASE).append('-').append(v.getMajor());
if (v.getMinor() > 0) {
result.append('.').append(v.getMinor());
}
if (v.getMajor() == javaVersion.getMajor()) {
v = new Version(v.getMajor(), v.getMinor() + 1, 0);
} else {
v = new Version(v.getMajor() + 1, 0, 0);
}
}
return result.toString();
}
/**
* Bug 470616: [1.9] JavaSE-9 Execution Environment should set proper compiler compliance/source/target levels
* <p>
* This is a workaround for Bug 495497: [9] JavaSE-9.profile Execution Environment should set compiler levels to 9
*/
private void fixJavaSE9ComplianceSourceTargetLevels(Properties profile) {
if (ExecutionEnvironmentAnalyzer.JavaSE_9.equals(getId())) {
profile.setProperty(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_9);
profile.setProperty(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_9);
profile.setProperty(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_9);
profile.setProperty(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED);
}
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getSubEnvironments()
*/
@Override
public IExecutionEnvironment[] getSubEnvironments() {
Properties properties = getProfileProperties();
Set<IExecutionEnvironment> subenv = new LinkedHashSet<>();
if (properties != null) {
@SuppressWarnings("deprecation")
String subsets = properties.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT);
if (subsets != null) {
String[] ids = subsets.split(","); //$NON-NLS-1$
for (int i = 0; i < ids.length; i++) {
IExecutionEnvironment sub = JavaRuntime.getExecutionEnvironmentsManager().getEnvironment(ids[i].trim());
if (sub != null && !sub.getId().equals(getId())) {
subenv.add(sub);
}
}
}
}
return subenv.toArray(new IExecutionEnvironment[subenv.size()]);
}
/* (non-Javadoc)
* @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getComplianceOptions()
*/
@Override
public Map<String, String> getComplianceOptions() {
Properties properties = getProfileProperties();
if (properties != null) {
Map<String, String> map = new HashMap<>();
Iterator<?> iterator = properties.keySet().iterator();
while (iterator.hasNext()) {
String key = (String) iterator.next();
if (key.startsWith(COMPILER_SETTING_PREFIX)) {
map.put(key, properties.getProperty(key));
}
}
if (!map.isEmpty()) {
return map;
}
}
return null;
}
private String getCompliance() {
return fElement.getAttribute("compliance"); //$NON-NLS-1$
}
}