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 -&gt; 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 -&gt; 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 -&gt; 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) -&gt; 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) -&gt;
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) -&gt;
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 -&gt; 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 -&gt; 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 -&gt;
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