1 /******************************************************************************* 2 * Copyright (c) 2005, 2019 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 *******************************************************************************/ 14 package org.eclipse.jdt.internal.launching.environments; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.net.URL; 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.HashSet; 22 import java.util.Iterator; 23 import java.util.LinkedHashSet; 24 import java.util.List; 25 import java.util.Map; 26 import java.util.Properties; 27 import java.util.Set; 28 29 import org.eclipse.core.resources.ResourcesPlugin; 30 import org.eclipse.core.runtime.FileLocator; 31 import org.eclipse.core.runtime.IConfigurationElement; 32 import org.eclipse.core.runtime.IPath; 33 import org.eclipse.core.runtime.NullProgressMonitor; 34 import org.eclipse.core.runtime.Path; 35 import org.eclipse.core.runtime.Platform; 36 import org.eclipse.jdt.core.IAccessRule; 37 import org.eclipse.jdt.core.IClasspathContainer; 38 import org.eclipse.jdt.core.IClasspathEntry; 39 import org.eclipse.jdt.core.IJavaModel; 40 import org.eclipse.jdt.core.IJavaProject; 41 import org.eclipse.jdt.core.JavaCore; 42 import org.eclipse.jdt.core.JavaModelException; 43 import org.eclipse.jdt.internal.launching.LaunchingPlugin; 44 import org.eclipse.jdt.launching.IVMInstall; 45 import org.eclipse.jdt.launching.IVMInstallChangedListener; 46 import org.eclipse.jdt.launching.JavaRuntime; 47 import org.eclipse.jdt.launching.LibraryLocation; 48 import org.eclipse.jdt.launching.PropertyChangeEvent; 49 import org.eclipse.jdt.launching.environments.IAccessRuleParticipant; 50 import org.eclipse.jdt.launching.environments.IExecutionEnvironment; 51 import org.eclipse.osgi.util.NLS; 52 import org.osgi.framework.Bundle; 53 import org.osgi.framework.Constants; 54 import org.osgi.framework.Version; 55 56 /** 57 * A contributed execution environment. 58 * 59 * @since 3.2 60 */ 61 class ExecutionEnvironment implements IExecutionEnvironment { 62 63 /** 64 * Add a VM changed listener to clear cached values when a VM changes or is removed 65 */ 66 private IVMInstallChangedListener fListener = new IVMInstallChangedListener() { 67 68 /* (non-Javadoc) 69 * @see org.eclipse.jdt.launching.IVMInstallChangedListener#defaultVMInstallChanged(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.IVMInstall) 70 */ 71 @Override 72 public void defaultVMInstallChanged(IVMInstall previous, IVMInstall current) {} 73 74 /* (non-Javadoc) 75 * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmAdded(org.eclipse.jdt.launching.IVMInstall) 76 */ 77 @Override 78 public void vmAdded(IVMInstall newVm) {} 79 80 /* (non-Javadoc) 81 * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmChanged(org.eclipse.jdt.launching.PropertyChangeEvent) 82 */ 83 @Override 84 public void vmChanged(PropertyChangeEvent event) { 85 if (event.getSource() != null) { 86 fParticipantMap.remove(event.getSource()); 87 fRuleCache.remove(event.getSource()); 88 } 89 } 90 91 /* (non-Javadoc) 92 * @see org.eclipse.jdt.launching.IVMInstallChangedListener#vmRemoved(org.eclipse.jdt.launching.IVMInstall) 93 */ 94 @Override 95 public void vmRemoved(IVMInstall removedVm) { 96 fParticipantMap.remove(removedVm); 97 fRuleCache.remove(removedVm); 98 } 99 }; 100 101 102 /** 103 * The backing <code>IConfigurationElement</code> 104 */ 105 private IConfigurationElement fElement; 106 107 /** 108 * Environment specific rule participant or <code>null</code> if none. 109 */ 110 private IAccessRuleParticipant fRuleParticipant; 111 112 /** 113 * OSGi profile properties or <code>null</code> if none. 114 */ 115 private Properties fProfileProperties; 116 117 /** 118 * Whether profile properties have been initialized 119 */ 120 private boolean fPropertiesInitialized; 121 122 /** 123 * Set of compatible vm's - just the strictly compatible ones 124 */ 125 private Set<IVMInstall> fStrictlyCompatible = new HashSet<>(); 126 127 /** 128 * All compatible vm's 129 */ 130 private List<IVMInstall> fCompatibleVMs = new ArrayList<>(); 131 132 /** 133 * default VM install or <code>null</code> if none 134 */ 135 private IVMInstall fDefault = null; 136 137 /** 138 * Cache of access rule participants to consider for this environment. 139 */ 140 private IAccessRuleParticipant[] fParticipants = null; 141 142 /** 143 * Map of {IVMInstall -> Map of {participant -> IAccessRule[][]}}. 144 * Caches access rules returned by each participant for a given VM. 145 * @since 3.3 146 */ 147 private Map<IVMInstall, Map<IAccessRuleParticipant, IAccessRule[][]>> fParticipantMap = new HashMap<>(); 148 149 /** 150 * Cache of VM -> IAccessRule[][] based on the current state of the participant 151 * map. These are the union of the latest rules generated by the participants 152 * for a specific VM. 153 * @since 3.3 154 */ 155 private Map<IVMInstall, IAccessRule[][]> fRuleCache = new HashMap<>(); 156 157 /** 158 * Wild card pattern matching all files 159 */ 160 private static final IPath ALL_PATTERN = new Path("**/*"); //$NON-NLS-1$ 161 162 /** 163 * Prefix of compiler settings in properties file 164 */ 165 private static final String COMPILER_SETTING_PREFIX = JavaCore.PLUGIN_ID + ".compiler"; //$NON-NLS-1$ 166 167 /** 168 * Constructor 169 * @param element the backing {@link IConfigurationElement} 170 */ ExecutionEnvironment(IConfigurationElement element)171 ExecutionEnvironment(IConfigurationElement element) { 172 fElement = element; 173 fPropertiesInitialized = false; 174 String attribute = fElement.getAttribute(EnvironmentsManager.RULE_PARTICIPANT_ELEMENT); 175 if (attribute != null) { 176 fRuleParticipant = new AccessRuleParticipant(fElement); 177 } 178 JavaRuntime.addVMInstallChangedListener(fListener); 179 } 180 181 /** 182 * Initializes the <code>EnvironmentsManager</code> 183 */ init()184 private void init() { 185 EnvironmentsManager.getDefault().initializeCompatibilities(); 186 } 187 188 /* (non-Javadoc) 189 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getId() 190 */ 191 @Override getId()192 public String getId() { 193 return fElement.getAttribute("id"); //$NON-NLS-1$ 194 } 195 196 /* (non-Javadoc) 197 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getDescription() 198 */ 199 @Override getDescription()200 public String getDescription() { 201 return fElement.getAttribute("description"); //$NON-NLS-1$ 202 } 203 204 /* (non-Javadoc) 205 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getCompatibleVMs() 206 */ 207 @Override getCompatibleVMs()208 public IVMInstall[] getCompatibleVMs() { 209 init(); 210 return fCompatibleVMs.toArray(new IVMInstall[fCompatibleVMs.size()]); 211 } 212 213 /* (non-Javadoc) 214 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#isStrictlyCompatible(org.eclipse.jdt.launching.IVMInstall) 215 */ 216 @Override isStrictlyCompatible(IVMInstall vm)217 public boolean isStrictlyCompatible(IVMInstall vm) { 218 init(); 219 return fStrictlyCompatible.contains(vm); 220 } 221 222 /* (non-Javadoc) 223 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getDefaultVM() 224 */ 225 @Override getDefaultVM()226 public IVMInstall getDefaultVM() { 227 init(); 228 return fDefault; 229 } 230 231 /* (non-Javadoc) 232 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#setDefaultVM(org.eclipse.jdt.launching.IVMInstall) 233 */ 234 @Override setDefaultVM(IVMInstall vm)235 public void setDefaultVM(IVMInstall vm) { 236 init(); 237 if (vm != null && !fCompatibleVMs.contains(vm)) { 238 throw new IllegalArgumentException(NLS.bind(EnvironmentMessages.EnvironmentsManager_0, new String[]{getId()})); 239 } 240 if (vm != null && vm.equals(fDefault)) { 241 return; 242 } 243 fDefault = vm; 244 EnvironmentsManager.getDefault().updateDefaultVMs(); 245 // update classpath containers 246 rebindClasspathContainers(); 247 } 248 249 /** 250 * Updates Java projects referencing this environment, if any. 251 */ rebindClasspathContainers()252 private void rebindClasspathContainers() { 253 IJavaModel model = JavaCore.create(ResourcesPlugin.getWorkspace().getRoot()); 254 if (model != null) { 255 try { 256 List<IJavaProject> updates = new ArrayList<>(); 257 IJavaProject[] javaProjects = model.getJavaProjects(); 258 IPath path = JavaRuntime.newJREContainerPath(this); 259 for (int i = 0; i < javaProjects.length; i++) { 260 IJavaProject project = javaProjects[i]; 261 IClasspathEntry[] rawClasspath = project.getRawClasspath(); 262 for (int j = 0; j < rawClasspath.length; j++) { 263 IClasspathEntry entry = rawClasspath[j]; 264 if (entry.getEntryKind() == IClasspathEntry.CPE_CONTAINER) { 265 if (entry.getPath().equals(path)) { 266 updates.add(project); 267 } 268 } 269 } 270 } 271 if (!updates.isEmpty()) { 272 JavaCore.setClasspathContainer(path, 273 updates.toArray(new IJavaProject[updates.size()]), 274 new IClasspathContainer[updates.size()], 275 new NullProgressMonitor()); 276 } 277 } catch (JavaModelException e) { 278 LaunchingPlugin.log(e); 279 } 280 } 281 } 282 283 /** 284 * Adds the specified VM to the listing of compatible VMs, also 285 * adds the VM to the listing of strictly compatible ones based on 286 * the strictlyCompatible flag 287 * @param vm the VM to add to the environment 288 * @param strictlyCompatible if it is strictly compatible 289 */ add(IVMInstall vm, boolean strictlyCompatible)290 void add(IVMInstall vm, boolean strictlyCompatible) { 291 if (fCompatibleVMs.contains(vm)) { 292 return; 293 } 294 fCompatibleVMs.add(vm); 295 if (strictlyCompatible) { 296 fStrictlyCompatible.add(vm); 297 } 298 } 299 300 /** 301 * Removes the specified VM from the listings of VMs 302 * @param vm the VM to remove 303 */ remove(IVMInstall vm)304 void remove(IVMInstall vm) { 305 fCompatibleVMs.remove(vm); 306 fStrictlyCompatible.remove(vm); 307 } 308 309 /** 310 * Sets the default VM to be the one specified 311 * @param vm the VM to set as the default 312 */ initDefaultVM(IVMInstall vm)313 void initDefaultVM(IVMInstall vm) { 314 fDefault = vm; 315 } 316 317 /* (non-Javadoc) 318 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getAccessRules(org.eclipse.jdt.launching.IVMInstall, org.eclipse.jdt.launching.LibraryLocation[], org.eclipse.jdt.core.IJavaProject) 319 */ 320 @Override getAccessRules(IVMInstall vm, LibraryLocation[] libraries, IJavaProject project)321 public IAccessRule[][] getAccessRules(IVMInstall vm, LibraryLocation[] libraries, IJavaProject project) { 322 IAccessRuleParticipant[] participants = getParticipants(); 323 Map<IAccessRuleParticipant, IAccessRule[][]> rulesByParticipant = collectRulesByParticipant(participants, vm, libraries, project); 324 synchronized (this) { 325 Map<IAccessRuleParticipant, IAccessRule[][]> cachedRules = fParticipantMap.get(vm); 326 if (cachedRules == null || !cachedRules.equals(rulesByParticipant)) { 327 ArrayList<List<IAccessRule>> libLists = new ArrayList<>(); // array of lists of access rules 328 for (int i = 0; i < libraries.length; i++) { 329 libLists.add(new ArrayList<IAccessRule>()); 330 } 331 for (int i = 0; i < participants.length; i++) { 332 IAccessRuleParticipant participant = participants[i]; 333 addRules(rulesByParticipant.get(participant), libLists); 334 } 335 IAccessRule[][] allRules = new IAccessRule[libraries.length][]; 336 for (int i = 0; i < libLists.size(); i++) { 337 List<IAccessRule> l = libLists.get(i); 338 allRules[i] = l.toArray(new IAccessRule[l.size()]); 339 } 340 fParticipantMap.put(vm, rulesByParticipant); 341 fRuleCache.put(vm, allRules); 342 return allRules; 343 } 344 return fRuleCache.get(vm); 345 } 346 } 347 348 /** 349 * Returns all access rule participants to consider for this environment. 350 * Includes any participant contributed with this environment and all other 351 * stand alone participants. 352 * 353 * @return access rule participants to consider for this environment 354 */ getParticipants()355 private synchronized IAccessRuleParticipant[] getParticipants() { 356 if (fParticipants == null) { 357 // check participants first 358 IAccessRuleParticipant[] participants = EnvironmentsManager.getDefault().getAccessRuleParticipants(); 359 if (fRuleParticipant != null) { 360 // ensure environment specific provider is last and not duplicated 361 LinkedHashSet<IAccessRuleParticipant> set = new LinkedHashSet<>(); 362 for (int i = 0; i < participants.length; i++) { 363 set.add(participants[i]); 364 } 365 // remove, add to make last 366 set.remove(fRuleParticipant); 367 set.add(fRuleParticipant); 368 participants = set.toArray(new IAccessRuleParticipant[set.size()]); 369 } 370 fParticipants = participants; 371 } 372 return fParticipants; 373 } 374 375 /** 376 * Returns a map of participant to the access rules for that participant for the given 377 * VM, libraries, and project. 378 * 379 * @param participants access rule participants 380 * @param vm the VM 381 * @param libraries the {@link LibraryLocation}s 382 * @param project the {@link IJavaProject} context 383 * @return the mapping of {@link IAccessRuleParticipant} to {@link IAccessRule}s 384 */ collectRulesByParticipant(IAccessRuleParticipant[] participants, IVMInstall vm, LibraryLocation[] libraries, IJavaProject project)385 private Map<IAccessRuleParticipant, IAccessRule[][]> collectRulesByParticipant(IAccessRuleParticipant[] participants, IVMInstall vm, LibraryLocation[] libraries, IJavaProject project) { 386 Map<IAccessRuleParticipant, IAccessRule[][]> map = new HashMap<>(); 387 for (int i = 0; i < participants.length; i++) { 388 // TODO: use safe runnable 389 map.put(participants[i], participants[i].getAccessRules(this, vm, libraries, project)); 390 } 391 return map; 392 } 393 394 /** 395 * Adds the access rules to each list in the given collection. If the last rule in a 396 * given collection is the wild card pattern then no more rules are added to that collection. 397 * 398 * @param accessRules the list of {@link IAccessRule}s 399 * @param collect the array of lists to collect the {@link IAccessRule}s in 400 */ addRules(IAccessRule[][] accessRules, ArrayList<List<IAccessRule>> collect)401 private void addRules(IAccessRule[][] accessRules, ArrayList<List<IAccessRule>> collect) { 402 for (int i = 0; i < accessRules.length; i++) { 403 IAccessRule[] libRules = accessRules[i]; 404 List<IAccessRule> list = collect.get(i); 405 // if the last rule is a **/* pattern, don't add any more rules, as they will have no effect 406 if (!list.isEmpty()) { 407 IAccessRule lastRule = list.get(list.size() - 1); 408 if(lastRule.getPattern().equals(ALL_PATTERN)) { 409 continue; 410 } 411 } 412 for (int j = 0; j < libRules.length; j++) { 413 list.add(libRules[j]); 414 } 415 } 416 } 417 418 /* (non-Javadoc) 419 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getProfileProperties() 420 */ 421 @Override getProfileProperties()422 public Properties getProfileProperties() { 423 if (!fPropertiesInitialized) { 424 fPropertiesInitialized = true; 425 String path = fElement.getAttribute("profileProperties"); //$NON-NLS-1$ 426 Bundle bundle = null; 427 if (path == null) { 428 // attempt default profiles known to OSGi 429 bundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$ 430 path = getId().replace('/', '_') + ".profile"; //$NON-NLS-1$ 431 } else { 432 // read provided file 433 bundle = Platform.getBundle(fElement.getContributor().getName()); 434 } 435 if (bundle != null && path != null) { 436 fProfileProperties = getJavaProfileProperties(bundle, path); 437 } 438 } 439 return fProfileProperties; 440 } 441 442 /** 443 * Returns properties file contained in the specified bundle at the given 444 * bundle relative path, or <code>null</code> if none. 445 * 446 * @param bundle bundle to locate file in 447 * @param path bundle relative path to properties file 448 * @return properties or <code>null</code> if none 449 */ getJavaProfileProperties(Bundle bundle, String path)450 private Properties getJavaProfileProperties(Bundle bundle, String path) { 451 Properties profile = new Properties(); 452 URL profileURL = bundle.getEntry(path); 453 if (profileURL != null) { 454 try (InputStream is = profileURL.openStream()) { 455 profileURL = FileLocator.resolve(profileURL); 456 if (is != null) { 457 profile.load(is); 458 fixJavaSE9ComplianceSourceTargetLevels(profile); 459 } 460 } catch (IOException e) { 461 return null; 462 } 463 } else { 464 String compliance = getCompliance(); 465 if (compliance == null) { 466 return null; 467 } 468 profile.setProperty(JavaCore.COMPILER_COMPLIANCE, compliance); 469 profile.setProperty(JavaCore.COMPILER_SOURCE, compliance); 470 profile.setProperty(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, compliance); 471 profile.setProperty(JavaCore.COMPILER_PB_ASSERT_IDENTIFIER, JavaCore.ERROR); 472 profile.setProperty(JavaCore.COMPILER_PB_ENUM_IDENTIFIER, JavaCore.ERROR); 473 profile.setProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT, calculateVMExecutionEnvs(new Version(compliance))); 474 profile.setProperty(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); 475 if (JavaCore.compareJavaVersions(compliance, JavaCore.VERSION_10) > 0) { 476 profile.setProperty(JavaCore.COMPILER_PB_ENABLE_PREVIEW_FEATURES, JavaCore.DISABLED); 477 profile.setProperty(JavaCore.COMPILER_PB_REPORT_PREVIEW_FEATURES, JavaCore.WARNING); 478 } 479 480 } 481 // For below Java 9 EE, compiler release option should be disabled by default 482 String property = profile.getProperty(JavaCore.COMPILER_COMPLIANCE); 483 if (property != null) { 484 if (JavaCore.compareJavaVersions(property, JavaCore.VERSION_9) < 0) { 485 profile.setProperty(JavaCore.COMPILER_RELEASE, JavaCore.DISABLED); 486 } 487 } 488 return profile; 489 } 490 491 private static final String JAVASE = "JavaSE"; //$NON-NLS-1$ 492 calculateVMExecutionEnvs(Version javaVersion)493 private String calculateVMExecutionEnvs(Version javaVersion) { 494 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$ 495 Version v = new Version(9, 0, 0); 496 while (v.compareTo(javaVersion) <= 0) { 497 result.append(',').append(' ').append(JAVASE).append('-').append(v.getMajor()); 498 if (v.getMinor() > 0) { 499 result.append('.').append(v.getMinor()); 500 } 501 if (v.getMajor() == javaVersion.getMajor()) { 502 v = new Version(v.getMajor(), v.getMinor() + 1, 0); 503 } else { 504 v = new Version(v.getMajor() + 1, 0, 0); 505 } 506 } 507 return result.toString(); 508 } 509 510 511 /** 512 * Bug 470616: [1.9] JavaSE-9 Execution Environment should set proper compiler compliance/source/target levels 513 * <p> 514 * This is a workaround for Bug 495497: [9] JavaSE-9.profile Execution Environment should set compiler levels to 9 515 */ fixJavaSE9ComplianceSourceTargetLevels(Properties profile)516 private void fixJavaSE9ComplianceSourceTargetLevels(Properties profile) { 517 if (ExecutionEnvironmentAnalyzer.JavaSE_9.equals(getId())) { 518 profile.setProperty(JavaCore.COMPILER_COMPLIANCE, JavaCore.VERSION_9); 519 profile.setProperty(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_9); 520 profile.setProperty(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, JavaCore.VERSION_9); 521 profile.setProperty(JavaCore.COMPILER_RELEASE, JavaCore.ENABLED); 522 } 523 } 524 525 /* (non-Javadoc) 526 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getSubEnvironments() 527 */ 528 @Override getSubEnvironments()529 public IExecutionEnvironment[] getSubEnvironments() { 530 Properties properties = getProfileProperties(); 531 Set<IExecutionEnvironment> subenv = new LinkedHashSet<>(); 532 if (properties != null) { 533 @SuppressWarnings("deprecation") 534 String subsets = properties.getProperty(Constants.FRAMEWORK_EXECUTIONENVIRONMENT); 535 if (subsets != null) { 536 String[] ids = subsets.split(","); //$NON-NLS-1$ 537 for (int i = 0; i < ids.length; i++) { 538 IExecutionEnvironment sub = JavaRuntime.getExecutionEnvironmentsManager().getEnvironment(ids[i].trim()); 539 if (sub != null && !sub.getId().equals(getId())) { 540 subenv.add(sub); 541 } 542 } 543 } 544 } 545 return subenv.toArray(new IExecutionEnvironment[subenv.size()]); 546 } 547 548 /* (non-Javadoc) 549 * @see org.eclipse.jdt.launching.environments.IExecutionEnvironment#getComplianceOptions() 550 */ 551 @Override getComplianceOptions()552 public Map<String, String> getComplianceOptions() { 553 Properties properties = getProfileProperties(); 554 if (properties != null) { 555 Map<String, String> map = new HashMap<>(); 556 Iterator<?> iterator = properties.keySet().iterator(); 557 while (iterator.hasNext()) { 558 String key = (String) iterator.next(); 559 if (key.startsWith(COMPILER_SETTING_PREFIX)) { 560 map.put(key, properties.getProperty(key)); 561 } 562 } 563 if (!map.isEmpty()) { 564 return map; 565 } 566 } 567 return null; 568 } 569 getCompliance()570 private String getCompliance() { 571 return fElement.getAttribute("compliance"); //$NON-NLS-1$ 572 } 573 574 @Override toString()575 public String toString() { 576 return this.fElement.getAttribute("id"); //$NON-NLS-1$ 577 } 578 } 579