1 /******************************************************************************* 2 * Copyright (c) 2000, 2015 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 * Martin Oberhuber (Wind River) - [245937] setLinkLocation() detects non-change 14 * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support 15 * Markus Schorn (Wind River) - [306575] Save snapshot location with project 16 * Broadcom Corporation - build configurations and references 17 * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 18 *******************************************************************************/ 19 package org.eclipse.core.internal.resources; 20 21 import java.net.URI; 22 import java.util.*; 23 import java.util.Map.Entry; 24 import org.eclipse.core.filesystem.URIUtil; 25 import org.eclipse.core.internal.events.BuildCommand; 26 import org.eclipse.core.internal.utils.FileUtil; 27 import org.eclipse.core.resources.*; 28 import org.eclipse.core.runtime.*; 29 30 public class ProjectDescription extends ModelObject implements IProjectDescription { 31 // constants 32 private static final IBuildConfiguration[] EMPTY_BUILD_CONFIG_REFERENCE_ARRAY = new IBuildConfiguration[0]; 33 private static final ICommand[] EMPTY_COMMAND_ARRAY = new ICommand[0]; 34 private static final IProject[] EMPTY_PROJECT_ARRAY = new IProject[0]; 35 private static final String[] EMPTY_STRING_ARRAY = new String[0]; 36 private static final String EMPTY_STR = ""; //$NON-NLS-1$ 37 38 protected static boolean isReading = false; 39 40 //flags to indicate when we are in the middle of reading or writing a 41 // workspace description 42 //these can be static because only one description can be read at once. 43 protected static boolean isWriting = false; 44 protected ICommand[] buildSpec = EMPTY_COMMAND_ARRAY; 45 protected String comment = EMPTY_STR; 46 47 // Build configuration + References state 48 /** Id of the currently active build configuration */ 49 protected String activeConfiguration = IBuildConfiguration.DEFAULT_CONFIG_NAME; 50 /** 51 * The 'real' build configuration names set on this project. 52 * This doesn't contain the generated 'default' build configuration with name 53 * {@link IBuildConfiguration#DEFAULT_CONFIG_NAME} 54 * when no build configurations have been defined. 55 */ 56 protected String[] configNames = EMPTY_STRING_ARRAY; 57 // Static + Dynamic project level references 58 protected IProject[] staticRefs = EMPTY_PROJECT_ARRAY; 59 protected IProject[] dynamicRefs = EMPTY_PROJECT_ARRAY; 60 /** Map from config name in this project -> build configurations in other projects */ 61 protected HashMap<String, IBuildConfiguration[]> dynamicConfigRefs = new HashMap<>(1); 62 63 // Cache of the build configurations 64 protected volatile IBuildConfiguration[] cachedBuildConfigs; 65 // Cached build configuration references. Not persisted. 66 protected Map<String, IBuildConfiguration[]> cachedConfigRefs = Collections.synchronizedMap(new HashMap<>(1)); 67 /** 68 * Cached project level references. Synchronize on {@link #cachedRefsMutex} before reading or writing. Increment 69 * {@link #cachedRefsDirtyCount} whenever this is dirtied. 70 */ 71 protected IProject[] cachedRefs; 72 /** 73 * Counts the number of times {@link #cachedRefs} has been dirtied. Can be used to determine if dynamic dependencies have 74 * changed during an operation that is intended to be atomic with respect to dynamic dependencies. Synchronize on 75 * {@link #cachedRefsMutex} before accessing. 76 */ 77 protected int cachedRefsDirtyCount; 78 /** 79 * Mutex used to protect {@link #cachedRefs} and {@link #cachedRefsDirtyCount}. 80 */ 81 protected final Object cachedRefsMutex = new Object(); 82 83 /** 84 * Map of (IPath -> LinkDescription) pairs for each linked resource 85 * in this project, where IPath is the project relative path of the resource. 86 */ 87 protected HashMap<IPath, LinkDescription> linkDescriptions = null; 88 89 /** 90 * Map of {@literal (IPath -> LinkedList<FilterDescription>)} pairs for each filtered resource 91 * in this project, where IPath is the project relative path of the resource. 92 */ 93 protected HashMap<IPath, LinkedList<FilterDescription>> filterDescriptions = null; 94 95 /** 96 * Map of (String -> VariableDescription) pairs for each variable in this 97 * project, where String is the name of the variable. 98 */ 99 protected HashMap<String, VariableDescription> variableDescriptions = null; 100 101 // fields 102 protected URI location = null; 103 protected String[] natures = EMPTY_STRING_ARRAY; 104 protected URI snapshotLocation = null; 105 ProjectDescription()106 public ProjectDescription() { 107 super(); 108 } 109 110 @Override 111 @SuppressWarnings({"unchecked"}) clone()112 public Object clone() { 113 ProjectDescription clone = (ProjectDescription) super.clone(); 114 //don't want the clone to have access to our internal link locations table or builders 115 clone.linkDescriptions = null; 116 clone.filterDescriptions = null; 117 if (variableDescriptions != null) 118 clone.variableDescriptions = (HashMap<String, VariableDescription>) variableDescriptions.clone(); 119 clone.buildSpec = getBuildSpec(true); 120 clone.dynamicConfigRefs = (HashMap<String, IBuildConfiguration[]>) dynamicConfigRefs.clone(); 121 clone.cachedConfigRefs = Collections.synchronizedMap(new HashMap<>(1)); 122 clone.clearCachedDynamicReferences(null); 123 return clone; 124 } 125 126 /** 127 * Clear cached references for the specified build config name 128 * or all if configName is null. 129 */ clearCachedDynamicReferences(String configName)130 public void clearCachedDynamicReferences(String configName) { 131 synchronized (cachedRefsMutex) { 132 if (configName == null) 133 cachedConfigRefs.clear(); 134 else 135 cachedConfigRefs.remove(configName); 136 cachedRefs = null; 137 cachedRefsDirtyCount++; 138 } 139 } 140 141 /** 142 * Returns a copy of the given array of build configs with all duplicates removed 143 */ copyAndRemoveDuplicates(IBuildConfiguration[] values)144 private IBuildConfiguration[] copyAndRemoveDuplicates(IBuildConfiguration[] values) { 145 Set<IBuildConfiguration> set = new LinkedHashSet<>(Arrays.asList(values)); 146 return set.toArray(new IBuildConfiguration[set.size()]); 147 } 148 149 /** 150 * Returns a copy of the given array with all duplicates removed 151 */ copyAndRemoveDuplicates(IProject[] projects)152 private IProject[] copyAndRemoveDuplicates(IProject[] projects) { 153 IProject[] result = new IProject[projects.length]; 154 int count = 0; 155 next: for (IProject project : projects) { 156 // scan to see if there are any other projects by the same name 157 for (int j = 0; j < count; j++) 158 if (project.equals(result[j])) 159 continue next; 160 // not found 161 result[count++] = project; 162 } 163 if (count < projects.length) { 164 //shrink array 165 IProject[] reduced = new IProject[count]; 166 System.arraycopy(result, 0, reduced, 0, count); 167 return reduced; 168 } 169 return result; 170 } 171 172 /** 173 * Helper to turn an array of projects into an array of {@link IBuildConfiguration} to the 174 * projects' active configuration 175 * Order is preserved - the buildConfigs appear for each project in the order 176 * that the projects were specified. 177 * @param projects projects to get the active configuration from 178 * @return collection of build config references 179 */ getBuildConfigReferencesFromProjects(IProject[] projects)180 private Collection<BuildConfiguration> getBuildConfigReferencesFromProjects(IProject[] projects) { 181 List<BuildConfiguration> refs = new ArrayList<>(projects.length); 182 for (IProject project : projects) 183 refs.add(new BuildConfiguration(project, null)); 184 return refs; 185 } 186 187 /** 188 * Helper to fetch projects from an array of build configuration references 189 * @param refs 190 * @return {@literal List<IProject>} 191 */ getProjectsFromBuildConfigRefs(IBuildConfiguration[] refs)192 private Collection<IProject> getProjectsFromBuildConfigRefs(IBuildConfiguration[] refs) { 193 LinkedHashSet<IProject> projects = new LinkedHashSet<>(refs.length); 194 for (IBuildConfiguration ref : refs) 195 projects.add(ref.getProject()); 196 return projects; 197 } 198 getActiveBuildConfig()199 public String getActiveBuildConfig() { 200 return activeConfiguration; 201 } 202 203 /** 204 * Returns the union of the description's static and dynamic project references, 205 * with duplicates omitted. The calculation is optimized by caching the result 206 * Call the configuration based implementation. 207 * @see #getAllBuildConfigReferences(IProject, String, boolean) 208 */ getAllReferences(IProject project, boolean makeCopy)209 public IProject[] getAllReferences(IProject project, boolean makeCopy) { 210 int dirtyCount; 211 IProject[] projRefs; 212 213 synchronized (cachedRefsMutex) { 214 projRefs = cachedRefs; 215 dirtyCount = cachedRefsDirtyCount; 216 } 217 // Retry this computation until we're able to proceed to the end without someone dirtying the cache. 218 // This loop is here to prevent us from caching a stale result if someone dirties the cache between 219 // the time we invoke getAllBuildConfigReferences and the time we can write to cachedRefs. 220 while (projRefs == null) { 221 IBuildConfiguration[] refs; 222 if (hasBuildConfig(activeConfiguration)) 223 refs = getAllBuildConfigReferences(project, activeConfiguration, false); 224 else if (configNames.length > 0) 225 refs = getAllBuildConfigReferences(project, configNames[0], false); 226 else 227 // No build configuration => fall-back to default 228 refs = getAllBuildConfigReferences(project, IBuildConfiguration.DEFAULT_CONFIG_NAME, false); 229 Collection<IProject> l = getProjectsFromBuildConfigRefs(refs); 230 231 synchronized (cachedRefsMutex) { 232 // If nobody dirtied the cache since the start of this operation then we can cache the 233 // new result and end the loop. 234 if (cachedRefsDirtyCount == dirtyCount) { 235 cachedRefs = l.toArray(new IProject[l.size()]); 236 } 237 projRefs = cachedRefs; 238 dirtyCount = cachedRefsDirtyCount; 239 } 240 } 241 //still need to copy the result to prevent tampering with the cache 242 return makeCopy ? (IProject[]) projRefs.clone() : projRefs; 243 } 244 245 /** 246 * The main entrance point to fetch the full set of Project references. 247 * 248 * Returns the union of all the description's references. Includes static and dynamic 249 * project level references as well as build configuration references for the configuration 250 * with the given id. 251 * Duplicates are omitted. The calculation is optimized by caching the result. 252 * Note that these BuildConfiguration references may have <code>null</code> name. They must 253 * be resolved using {@link BuildConfiguration#getBuildConfig()} before use. 254 * Returns an empty array if the given configName does not exist in the description. 255 */ getAllBuildConfigReferences(IProject project, String configName, boolean makeCopy)256 public IBuildConfiguration[] getAllBuildConfigReferences(IProject project, String configName, boolean makeCopy) { 257 if (!hasBuildConfig(configName)) 258 return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; 259 IBuildConfiguration[] refs = cachedConfigRefs.get(configName); 260 if (refs == null) { 261 Set<IBuildConfiguration> references = new LinkedHashSet<>(); 262 IBuildConfiguration[] dynamicBuildConfigs = dynamicConfigRefs.containsKey(configName) ? dynamicConfigRefs.get(configName) : EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; 263 Collection<BuildConfiguration> dynamic; 264 try { 265 IBuildConfiguration buildConfig = project.getBuildConfig(configName); 266 dynamic = getBuildConfigReferencesFromProjects(computeDynamicReferencesForProject(buildConfig, getBuildSpec())); 267 } catch (CoreException e) { 268 dynamic = Collections.emptyList(); 269 } 270 Collection<BuildConfiguration> legacyDynamic = getBuildConfigReferencesFromProjects(dynamicRefs); 271 Collection<BuildConfiguration> statik = getBuildConfigReferencesFromProjects(staticRefs); 272 273 // Combine all references: 274 // New build config references (which only come in dynamic form) trump all others. 275 references.addAll(Arrays.asList(dynamicBuildConfigs)); 276 // We preserve the previous order of static project references before dynamic project references 277 references.addAll(statik); 278 references.addAll(legacyDynamic); 279 references.addAll(dynamic); 280 refs = references.toArray(new IBuildConfiguration[references.size()]); 281 cachedConfigRefs.put(configName, refs); 282 } 283 return makeCopy ? (IBuildConfiguration[]) refs.clone() : refs; 284 } 285 286 /** 287 * Used by Project to get the buildConfigs on the description. 288 * @return the project configurations 289 */ getBuildConfigs(IProject project, boolean makeCopy)290 public IBuildConfiguration[] getBuildConfigs(IProject project, boolean makeCopy) { 291 IBuildConfiguration[] configs = cachedBuildConfigs; 292 // Ensure project is up to date in the cache 293 if (configs != null && !project.equals(configs[0].getProject())) 294 configs = null; 295 if (configs == null) { 296 if (configNames.length == 0) 297 configs = new IBuildConfiguration[] {new BuildConfiguration(project)}; 298 else { 299 configs = new IBuildConfiguration[configNames.length]; 300 for (int i = 0; i < configs.length; i++) 301 configs[i] = new BuildConfiguration(project, configNames[i]); 302 } 303 cachedBuildConfigs = configs; 304 } 305 return makeCopy ? (IBuildConfiguration[]) configs.clone() : configs; 306 } 307 308 @Override getBuildConfigReferences(String configName)309 public IBuildConfiguration[] getBuildConfigReferences(String configName) { 310 return getBuildConfigRefs(configName, true); 311 } 312 getBuildConfigRefs(String configName, boolean makeCopy)313 public IBuildConfiguration[] getBuildConfigRefs(String configName, boolean makeCopy) { 314 if (!hasBuildConfig(configName) || !dynamicConfigRefs.containsKey(configName)) 315 return EMPTY_BUILD_CONFIG_REFERENCE_ARRAY; 316 317 return makeCopy ? (IBuildConfiguration[]) dynamicConfigRefs.get(configName).clone() : dynamicConfigRefs.get(configName); 318 } 319 320 /** 321 * Returns the build configuration references map 322 * @param makeCopy 323 */ 324 @SuppressWarnings({"unchecked"}) getBuildConfigReferences(boolean makeCopy)325 public Map<String, IBuildConfiguration[]> getBuildConfigReferences(boolean makeCopy) { 326 return makeCopy ? (Map<String, IBuildConfiguration[]>) dynamicConfigRefs.clone() : dynamicConfigRefs; 327 } 328 329 @Override getBuildSpec()330 public ICommand[] getBuildSpec() { 331 return getBuildSpec(true); 332 } 333 getBuildSpec(boolean makeCopy)334 public ICommand[] getBuildSpec(boolean makeCopy) { 335 //thread safety: copy reference in case of concurrent write 336 ICommand[] oldCommands = this.buildSpec; 337 if (oldCommands == null) 338 return EMPTY_COMMAND_ARRAY; 339 if (!makeCopy) 340 return oldCommands; 341 ICommand[] result = new ICommand[oldCommands.length]; 342 for (int i = 0; i < result.length; i++) 343 result[i] = (ICommand) ((BuildCommand) oldCommands[i]).clone(); 344 return result; 345 } 346 347 @Override getComment()348 public String getComment() { 349 return comment; 350 } 351 352 @Override getDynamicReferences()353 public IProject[] getDynamicReferences() { 354 return getDynamicReferences(true); 355 } 356 getDynamicReferences(boolean makeCopy)357 public IProject[] getDynamicReferences(boolean makeCopy) { 358 return makeCopy ? (IProject[]) dynamicRefs.clone() : dynamicRefs; 359 } 360 361 /** 362 * Returns the link location for the given resource name. Returns null if 363 * no such link exists. 364 */ getLinkLocationURI(IPath aPath)365 public URI getLinkLocationURI(IPath aPath) { 366 if (linkDescriptions == null) 367 return null; 368 LinkDescription desc = linkDescriptions.get(aPath); 369 return desc == null ? null : desc.getLocationURI(); 370 } 371 372 /** 373 * Returns the filter for the given resource name. Returns null if 374 * no such filter exists. 375 */ getFilter(IPath aPath)376 synchronized public LinkedList<FilterDescription> getFilter(IPath aPath) { 377 if (filterDescriptions == null) 378 return null; 379 return filterDescriptions.get(aPath); 380 } 381 382 /** 383 * Returns the map of link descriptions (IPath (project relative path) -> LinkDescription). 384 * Since this method is only used internally, it never creates a copy. 385 * Returns null if the project does not have any linked resources. 386 */ getLinks()387 public HashMap<IPath, LinkDescription> getLinks() { 388 return linkDescriptions; 389 } 390 391 /** 392 * Returns the map of filter descriptions (IPath (project relative path) -> 393 * {@literal LinkedList<FilterDescription>}). Since this method is only used 394 * internally, it never creates a copy. Returns null if the project does not 395 * have any filtered resources. 396 */ getFilters()397 public HashMap<IPath, LinkedList<FilterDescription>> getFilters() { 398 return filterDescriptions; 399 } 400 401 /** 402 * Returns the map of variable descriptions (String (variable name) -> 403 * VariableDescription). Since this method is only used internally, it never 404 * creates a copy. Returns null if the project does not have any variables. 405 */ getVariables()406 public HashMap<String, VariableDescription> getVariables() { 407 return variableDescriptions; 408 } 409 410 /** 411 * @see IProjectDescription#getLocation() 412 * @deprecated 413 */ 414 @Override 415 @Deprecated getLocation()416 public IPath getLocation() { 417 if (location == null) 418 return null; 419 return FileUtil.toPath(location); 420 } 421 422 @Override getLocationURI()423 public URI getLocationURI() { 424 return location; 425 } 426 427 @Override getNatureIds()428 public String[] getNatureIds() { 429 return getNatureIds(true); 430 } 431 getNatureIds(boolean makeCopy)432 public String[] getNatureIds(boolean makeCopy) { 433 if (natures == null) 434 return EMPTY_STRING_ARRAY; 435 return makeCopy ? (String[]) natures.clone() : natures; 436 } 437 438 @Override getReferencedProjects()439 public IProject[] getReferencedProjects() { 440 return getReferencedProjects(true); 441 } 442 getReferencedProjects(boolean makeCopy)443 public IProject[] getReferencedProjects(boolean makeCopy) { 444 if (staticRefs == null) 445 return EMPTY_PROJECT_ARRAY; 446 return makeCopy ? (IProject[]) staticRefs.clone() : staticRefs; 447 } 448 449 /** 450 * Returns the URI to load a resource snapshot from. 451 * May return <code>null</code> if no snapshot is set. 452 * <p> 453 * <strong>EXPERIMENTAL</strong>. This constant has been added as 454 * part of a work in progress. There is no guarantee that this API will 455 * work or that it will remain the same. Please do not use this API without 456 * consulting with the Platform Core team. 457 * </p> 458 * @return the snapshot location URI, 459 * or <code>null</code>. 460 * @see IProject#loadSnapshot(int, URI, IProgressMonitor) 461 * @see #setSnapshotLocationURI(URI) 462 * @since 3.6 463 */ getSnapshotLocationURI()464 public URI getSnapshotLocationURI() { 465 return snapshotLocation; 466 } 467 468 @Override hasNature(String natureID)469 public boolean hasNature(String natureID) { 470 String[] natureIDs = getNatureIds(false); 471 for (String natureID2 : natureIDs) 472 if (natureID2.equals(natureID)) 473 return true; 474 return false; 475 } 476 477 /** 478 * Helper method to compare two maps of Configuration Name -> IBuildConfigurationReference[] 479 * @return boolean indicating if there are differences between the two maps 480 */ configRefsHaveChanges(Map<String, IBuildConfiguration[]> m1, Map<String, IBuildConfiguration[]> m2)481 private static boolean configRefsHaveChanges(Map<String, IBuildConfiguration[]> m1, Map<String, IBuildConfiguration[]> m2) { 482 if (m1.size() != m2.size()) 483 return true; 484 for (Entry<String, IBuildConfiguration[]> e : m1.entrySet()) { 485 if (!m2.containsKey(e.getKey())) 486 return true; 487 if (!Arrays.equals(e.getValue(), m2.get(e.getKey()))) 488 return true; 489 } 490 return false; 491 } 492 493 /** 494 * Internal method to check if the description has a given build configuration. 495 */ hasBuildConfig(String buildConfigName)496 boolean hasBuildConfig(String buildConfigName) { 497 Assert.isNotNull(buildConfigName); 498 if (configNames.length == 0) 499 return IBuildConfiguration.DEFAULT_CONFIG_NAME.equals(buildConfigName); 500 for (String configName : configNames) 501 if (configName.equals(buildConfigName)) 502 return true; 503 return false; 504 } 505 506 /** 507 * Returns true if any private attributes of the description have changed. 508 * Private attributes are those that are not stored in the project description 509 * file (.project). 510 */ hasPrivateChanges(ProjectDescription description)511 public boolean hasPrivateChanges(ProjectDescription description) { 512 if (location == null) { 513 if (description.location != null) 514 return true; 515 } else if (!location.equals(description.location)) 516 return true; 517 518 if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) 519 return true; 520 521 // Build Configuration state 522 if (!activeConfiguration.equals(description.activeConfiguration)) 523 return true; 524 if (!Arrays.equals(configNames, description.configNames)) 525 return true; 526 // Configuration level references 527 if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) 528 return true; 529 530 return false; 531 } 532 533 /** 534 * Returns true if any public attributes of the description have changed. 535 * Public attributes are those that are stored in the project description 536 * file (.project). 537 */ hasPublicChanges(ProjectDescription description)538 public boolean hasPublicChanges(ProjectDescription description) { 539 if (!getName().equals(description.getName())) 540 return true; 541 if (!comment.equals(description.getComment())) 542 return true; 543 //don't bother optimizing if the order has changed 544 if (!Arrays.equals(buildSpec, description.getBuildSpec(false))) 545 return true; 546 if (!Arrays.equals(staticRefs, description.getReferencedProjects(false))) 547 return true; 548 if (!Arrays.equals(natures, description.getNatureIds(false))) 549 return true; 550 551 HashMap<IPath, LinkedList<FilterDescription>> otherFilters = description.getFilters(); 552 if ((filterDescriptions == null) && (otherFilters != null)) 553 return otherFilters != null; 554 if ((filterDescriptions != null) && !filterDescriptions.equals(otherFilters)) 555 return true; 556 557 HashMap<String, VariableDescription> otherVariables = description.getVariables(); 558 if ((variableDescriptions == null) && (otherVariables != null)) 559 return true; 560 if ((variableDescriptions != null) && !variableDescriptions.equals(otherVariables)) 561 return true; 562 563 final HashMap<IPath, LinkDescription> otherLinks = description.getLinks(); 564 if (linkDescriptions != otherLinks) { 565 if (linkDescriptions == null || !linkDescriptions.equals(otherLinks)) 566 return true; 567 } 568 569 final URI otherSnapshotLoc = description.getSnapshotLocationURI(); 570 if (snapshotLocation != otherSnapshotLoc) { 571 if (snapshotLocation == null || !snapshotLocation.equals(otherSnapshotLoc)) 572 return true; 573 } 574 return false; 575 } 576 577 @Override newCommand()578 public ICommand newCommand() { 579 return new BuildCommand(); 580 } 581 582 @Override setActiveBuildConfig(String configName)583 public void setActiveBuildConfig(String configName) { 584 Assert.isNotNull(configName); 585 if (!configName.equals(activeConfiguration)) 586 clearCachedDynamicReferences(null); 587 activeConfiguration = configName; 588 } 589 590 @Override setBuildSpec(ICommand[] value)591 public void setBuildSpec(ICommand[] value) { 592 Assert.isLegal(value != null); 593 //perform a deep copy in case clients perform further changes to the command 594 ICommand[] result = new ICommand[value.length]; 595 for (int i = 0; i < result.length; i++) { 596 result[i] = (ICommand) ((BuildCommand) value[i]).clone(); 597 //copy the reference to any builder instance from the old build spec 598 //to preserve builder states if possible. 599 for (ICommand element : buildSpec) { 600 if (result[i].equals(element)) { 601 ((BuildCommand) result[i]).setBuilders(((BuildCommand) element).getBuilders()); 602 break; 603 } 604 } 605 } 606 buildSpec = result; 607 } 608 609 @Override setComment(String value)610 public void setComment(String value) { 611 comment = value; 612 } 613 614 @Deprecated 615 @Override setDynamicReferences(IProject[] value)616 public void setDynamicReferences(IProject[] value) { 617 Assert.isLegal(value != null); 618 dynamicRefs = copyAndRemoveDuplicates(value); 619 clearCachedDynamicReferences(null); 620 } 621 setBuildConfigReferences(HashMap<String, IBuildConfiguration[]> refs)622 public void setBuildConfigReferences(HashMap<String, IBuildConfiguration[]> refs) { 623 dynamicConfigRefs = new HashMap<>(refs); 624 clearCachedDynamicReferences(null); 625 } 626 627 @Override setBuildConfigReferences(String configName, IBuildConfiguration[] references)628 public void setBuildConfigReferences(String configName, IBuildConfiguration[] references) { 629 Assert.isLegal(configName != null); 630 Assert.isLegal(references != null); 631 if (!hasBuildConfig(configName)) 632 return; 633 dynamicConfigRefs.put(configName, copyAndRemoveDuplicates(references)); 634 clearCachedDynamicReferences(configName); 635 } 636 637 @Override setBuildConfigs(String[] names)638 public void setBuildConfigs(String[] names) { 639 // Remove references for deleted buildConfigs 640 LinkedHashSet<String> buildConfigNames = new LinkedHashSet<>(); 641 642 if (names == null || names.length == 0) { 643 configNames = EMPTY_STRING_ARRAY; 644 buildConfigNames.add(IBuildConfiguration.DEFAULT_CONFIG_NAME); 645 } else { 646 // Filter out duplicates 647 for (String n : names) { 648 Assert.isLegal(n != null); 649 buildConfigNames.add(n); 650 } 651 652 if (buildConfigNames.size() == 1 && ((buildConfigNames.iterator().next())).equals(IBuildConfiguration.DEFAULT_CONFIG_NAME)) 653 configNames = EMPTY_STRING_ARRAY; 654 else 655 configNames = buildConfigNames.toArray(new String[buildConfigNames.size()]); 656 } 657 658 // Remove references for deleted buildConfigs 659 boolean modified = dynamicConfigRefs.keySet().retainAll(buildConfigNames); 660 if (modified) 661 clearCachedDynamicReferences(null); 662 // Clear the cached IBuildConfiguration[] 663 cachedBuildConfigs = null; 664 } 665 666 /** 667 * Sets the map of link descriptions (String name -> LinkDescription). 668 * Since this method is only used internally, it never creates a copy. May 669 * pass null if this project does not have any linked resources 670 */ setLinkDescriptions(HashMap<IPath, LinkDescription> linkDescriptions)671 public void setLinkDescriptions(HashMap<IPath, LinkDescription> linkDescriptions) { 672 this.linkDescriptions = linkDescriptions; 673 } 674 675 /** 676 * Sets the map of filter descriptions {@literal (String name -> LinkedList<LinkDescription>)}. 677 * Since this method is only used internally, it never creates a copy. May 678 * pass null if this project does not have any filtered resources 679 */ setFilterDescriptions(HashMap<IPath, LinkedList<FilterDescription>> filterDescriptions)680 public void setFilterDescriptions(HashMap<IPath, LinkedList<FilterDescription>> filterDescriptions) { 681 this.filterDescriptions = filterDescriptions; 682 } 683 684 /** 685 * Sets the map of variable descriptions (String name -> 686 * VariableDescription). Since this method is only used internally, it never 687 * creates a copy. May pass null if this project does not have any variables 688 */ setVariableDescriptions(HashMap<String, VariableDescription> variableDescriptions)689 public void setVariableDescriptions(HashMap<String, VariableDescription> variableDescriptions) { 690 this.variableDescriptions = variableDescriptions; 691 } 692 693 /** 694 * Sets the description of a link. Setting to a description of null will 695 * remove the link from the project description. 696 * @return <code>true</code> if the description was actually changed, 697 * <code>false</code> otherwise. 698 * @since 3.5 returns boolean (was void before) 699 */ 700 @SuppressWarnings({"unchecked"}) setLinkLocation(IPath path, LinkDescription description)701 public boolean setLinkLocation(IPath path, LinkDescription description) { 702 HashMap<IPath, LinkDescription> tempMap = linkDescriptions; 703 if (description != null) { 704 //addition or modification 705 if (tempMap == null) 706 tempMap = new HashMap<>(10); 707 else 708 //copy on write to protect against concurrent read 709 tempMap = (HashMap<IPath, LinkDescription>) tempMap.clone(); 710 Object oldValue = tempMap.put(path, description); 711 if (oldValue != null && description.equals(oldValue)) { 712 //not actually changed anything 713 return false; 714 } 715 linkDescriptions = tempMap; 716 } else { 717 //removal 718 if (tempMap == null) 719 return false; 720 //copy on write to protect against concurrent access 721 HashMap<IPath, LinkDescription> newMap = (HashMap<IPath, LinkDescription>) tempMap.clone(); 722 Object oldValue = newMap.remove(path); 723 if (oldValue == null) { 724 //not actually changed anything 725 return false; 726 } 727 linkDescriptions = newMap.isEmpty() ? null : newMap; 728 } 729 return true; 730 } 731 732 /** 733 * Add the description of a filter. Setting to a description of null will 734 * remove the filter from the project description. 735 */ addFilter(IPath path, FilterDescription description)736 synchronized public void addFilter(IPath path, FilterDescription description) { 737 Assert.isNotNull(description); 738 if (filterDescriptions == null) 739 filterDescriptions = new HashMap<>(10); 740 LinkedList<FilterDescription> descList = filterDescriptions.get(path); 741 if (descList == null) { 742 descList = new LinkedList<>(); 743 filterDescriptions.put(path, descList); 744 } 745 descList.add(description); 746 } 747 748 /** 749 * Add the description of a filter. Setting to a description of null will 750 * remove the filter from the project description. 751 */ removeFilter(IPath path, FilterDescription description)752 synchronized public void removeFilter(IPath path, FilterDescription description) { 753 if (filterDescriptions != null) { 754 LinkedList<FilterDescription> descList = filterDescriptions.get(path); 755 if (descList != null) { 756 descList.remove(description); 757 if (descList.isEmpty()) { 758 filterDescriptions.remove(path); 759 if (filterDescriptions.isEmpty()) 760 filterDescriptions = null; 761 } 762 } 763 } 764 } 765 766 /** 767 * Sets the description of a variable. Setting to a description of null will 768 * remove the variable from the project description. 769 * @return <code>true</code> if the description was actually changed, 770 * <code>false</code> otherwise. 771 * @since 3.5 772 */ 773 @SuppressWarnings({"unchecked"}) setVariableDescription(String name, VariableDescription description)774 public boolean setVariableDescription(String name, VariableDescription description) { 775 HashMap<String, VariableDescription> tempMap = variableDescriptions; 776 if (description != null) { 777 // addition or modification 778 if (tempMap == null) 779 tempMap = new HashMap<>(10); 780 else 781 // copy on write to protect against concurrent read 782 tempMap = (HashMap<String, VariableDescription>) tempMap.clone(); 783 Object oldValue = tempMap.put(name, description); 784 if (oldValue != null && description.equals(oldValue)) { 785 //not actually changed anything 786 return false; 787 } 788 variableDescriptions = tempMap; 789 } else { 790 // removal 791 if (tempMap == null) 792 return false; 793 // copy on write to protect against concurrent access 794 HashMap<String, VariableDescription> newMap = (HashMap<String, VariableDescription>) tempMap.clone(); 795 Object oldValue = newMap.remove(name); 796 if (oldValue == null) { 797 //not actually changed anything 798 return false; 799 } 800 variableDescriptions = newMap.isEmpty() ? null : newMap; 801 } 802 return true; 803 } 804 805 /** 806 * set the filters for a given resource. Setting to a description of null will 807 * remove the filter from the project description. 808 * @return <code>true</code> if the description was actually changed, 809 * <code>false</code> otherwise. 810 */ setFilters(IPath path, LinkedList<FilterDescription> descriptions)811 synchronized public boolean setFilters(IPath path, LinkedList<FilterDescription> descriptions) { 812 if (descriptions != null) { 813 // addition 814 if (filterDescriptions == null) 815 filterDescriptions = new HashMap<>(10); 816 Object oldValue = filterDescriptions.put(path, descriptions); 817 if (oldValue != null && descriptions.equals(oldValue)) { 818 //not actually changed anything 819 return false; 820 } 821 } else { 822 // removal 823 if (filterDescriptions == null) 824 return false; 825 826 Object oldValue = filterDescriptions.remove(path); 827 if (oldValue == null) { 828 //not actually changed anything 829 return false; 830 } 831 if (filterDescriptions.isEmpty()) 832 filterDescriptions = null; 833 } 834 return true; 835 } 836 837 @Override setLocation(IPath path)838 public void setLocation(IPath path) { 839 this.location = path == null ? null : URIUtil.toURI(path); 840 } 841 842 @Override setLocationURI(URI location)843 public void setLocationURI(URI location) { 844 this.location = location; 845 } 846 847 @Override setName(String value)848 public void setName(String value) { 849 super.setName(value); 850 } 851 852 @Override setNatureIds(String[] value)853 public void setNatureIds(String[] value) { 854 natures = value.clone(); 855 } 856 857 @Override setReferencedProjects(IProject[] value)858 public void setReferencedProjects(IProject[] value) { 859 Assert.isLegal(value != null); 860 staticRefs = copyAndRemoveDuplicates(value); 861 clearCachedDynamicReferences(null); 862 } 863 864 /** 865 * Sets the location URI for a project snapshot that may be 866 * loaded automatically when the project is created in a workspace. 867 * <p> 868 * <strong>EXPERIMENTAL</strong>. This method has been added as 869 * part of a work in progress. There is no guarantee that this API will 870 * work or that it will remain the same. Please do not use this API without 871 * consulting with the Platform Core team. 872 * </p> 873 * @param snapshotLocation the location URI or 874 * <code>null</code> to clear the setting 875 * @see IProject#loadSnapshot(int, URI, IProgressMonitor) 876 * @see #getSnapshotLocationURI() 877 * @since 3.6 878 */ setSnapshotLocationURI(URI snapshotLocation)879 public void setSnapshotLocationURI(URI snapshotLocation) { 880 this.snapshotLocation = snapshotLocation; 881 } 882 getGroupLocationURI(IPath projectRelativePath)883 public URI getGroupLocationURI(IPath projectRelativePath) { 884 return LinkDescription.VIRTUAL_LOCATION; 885 } 886 887 /** 888 * Updates the dynamic build configuration and reference state to that of the passed in 889 * description. 890 * Copies in: 891 * <ul> 892 * <li>Active configuration name</li> 893 * <li>Dynamic Project References</li> 894 * <li>Build configurations list</li> 895 * <li>Build Configuration References</li> 896 * </ul> 897 * @param description Project description to copy dynamic state from 898 * @return boolean indicating if anything changed requing re-calculation of WS build order 899 */ updateDynamicState(ProjectDescription description)900 public boolean updateDynamicState(ProjectDescription description) { 901 boolean changed = false; 902 if (!activeConfiguration.equals(description.activeConfiguration)) { 903 changed = true; 904 activeConfiguration = description.activeConfiguration; 905 } 906 if (!Arrays.equals(dynamicRefs, description.dynamicRefs)) { 907 changed = true; 908 setDynamicReferences(description.dynamicRefs); 909 } 910 if (!Arrays.equals(configNames, description.configNames)) { 911 changed = true; 912 setBuildConfigs(description.configNames); 913 } 914 if (configRefsHaveChanges(dynamicConfigRefs, description.dynamicConfigRefs)) { 915 changed = true; 916 dynamicConfigRefs = new HashMap<>(description.dynamicConfigRefs); 917 } 918 if (changed) 919 clearCachedDynamicReferences(null); 920 return changed; 921 } 922 923 /** 924 * Computes the dynamic references for the given project + configuration. 925 */ computeDynamicReferencesForProject(IBuildConfiguration buildConfig, ICommand[] buildSpec)926 private static IProject[] computeDynamicReferencesForProject(IBuildConfiguration buildConfig, ICommand[] buildSpec) { 927 List<IProject> result = new ArrayList<>(); 928 for (ICommand command : buildSpec) { 929 IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_BUILDERS, command.getBuilderName()); 930 931 if (extension == null) { 932 continue; 933 } 934 935 IConfigurationElement[] configurationElements = extension.getConfigurationElements(); 936 937 if (configurationElements.length == 0) { 938 continue; 939 } 940 941 IConfigurationElement element = configurationElements[0]; 942 943 Object executableExtension; 944 try { 945 IConfigurationElement[] children = element.getChildren("dynamicReference"); //$NON-NLS-1$ 946 if (children.length != 0) { 947 executableExtension = children[0].createExecutableExtension("class"); //$NON-NLS-1$ 948 if (executableExtension instanceof IDynamicReferenceProvider) { 949 IDynamicReferenceProvider provider = (IDynamicReferenceProvider) executableExtension; 950 951 result.addAll(provider.getDependentProjects(buildConfig)); 952 } 953 } 954 } catch (CoreException e) { 955 String problemElement = element.toString(); 956 ResourcesPlugin.getPlugin().getLog().log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, "Unable to load dynamic reference provider: " + problemElement, e)); //$NON-NLS-1$ 957 } 958 } 959 return result.toArray(new IProject[0]); 960 } 961 } 962