/******************************************************************************* * Copyright (c) 2000, 2010 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Martin Oberhuber (Wind River) - [245937] setLinkLocation() detects non-change * Serge Beauchamp (Freescale Semiconductor) - [229633] Project Path Variable Support * Markus Schorn (Wind River) - [306575] Save snapshot location with project *******************************************************************************/ package org.eclipse.core.internal.resources; import java.net.URI; import java.util.*; import org.eclipse.core.filesystem.URIUtil; import org.eclipse.core.internal.events.BuildCommand; import org.eclipse.core.internal.utils.FileUtil; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; public class ProjectDescription extends ModelObject implements IProjectDescription { private static final ICommand[] EMPTY_COMMAND_ARRAY= new ICommand[0]; // constants private static final IProject[] EMPTY_PROJECT_ARRAY= new IProject[0]; private static final String[] EMPTY_STRING_ARRAY= new String[0]; protected static boolean isReading= false; //flags to indicate when we are in the middle of reading or writing a // workspace description //these can be static because only one description can be read at once. protected static boolean isWriting= false; protected ICommand[] buildSpec= EMPTY_COMMAND_ARRAY; /* * Cached union of static and dynamic references (duplicates omitted). * This cache is not persisted. */ protected IProject[] cachedRefs= null; protected String comment= ""; //$NON-NLS-1$ protected IProject[] dynamicRefs= EMPTY_PROJECT_ARRAY; /** * Map of (IPath -> LinkDescription) pairs for each linked resource in this project, where IPath * is the project relative path of the resource. */ protected HashMap linkDescriptions= null; /** * Map of (IPath -> LinkedList<FilterDescription>) pairs for each filtered resource in this * project, where IPath is the project relative path of the resource. */ protected HashMap filterDescriptions= null; /** * Map of (String -> VariableDescription) pairs for each variable in this project, where String * is the name of the variable. */ protected HashMap variableDescriptions= null; // fields protected URI location= null; protected String[] natures= EMPTY_STRING_ARRAY; protected IProject[] staticRefs= EMPTY_PROJECT_ARRAY; protected URI snapshotLocation= null; public ProjectDescription() { super(); } public Object clone() { ProjectDescription clone= (ProjectDescription)super.clone(); //don't want the clone to have access to our internal link locations table or builders clone.linkDescriptions= null; clone.filterDescriptions= null; if (variableDescriptions != null) clone.variableDescriptions= (HashMap)variableDescriptions.clone(); clone.buildSpec= getBuildSpec(true); return clone; } /** * Returns a copy of the given array with all duplicates removed */ private IProject[] copyAndRemoveDuplicates(IProject[] projects) { IProject[] result= new IProject[projects.length]; int count= 0; next: for (int i= 0; i < projects.length; i++) { IProject project= projects[i]; // scan to see if there are any other projects by the same name for (int j= 0; j < count; j++) if (project.equals(result[j])) continue next; // not found result[count++]= project; } if (count < projects.length) { //shrink array IProject[] reduced= new IProject[count]; System.arraycopy(result, 0, reduced, 0, count); return reduced; } return result; } /** * Returns the union of the description's static and dynamic project references, with duplicates * omitted. The calculation is optimized by caching the result */ public IProject[] getAllReferences(boolean makeCopy) { if (cachedRefs == null) { IProject[] statik= getReferencedProjects(false); IProject[] dynamic= getDynamicReferences(false); if (dynamic.length == 0) { cachedRefs= statik; } else if (statik.length == 0) { cachedRefs= dynamic; } else { //combine all references IProject[] result= new IProject[dynamic.length + statik.length]; System.arraycopy(statik, 0, result, 0, statik.length); System.arraycopy(dynamic, 0, result, statik.length, dynamic.length); cachedRefs= copyAndRemoveDuplicates(result); } } //still need to copy the result to prevent tampering with the cache return makeCopy ? (IProject[])cachedRefs.clone() : cachedRefs; } /* (non-Javadoc) * @see IProjectDescription#getBuildSpec() */ public ICommand[] getBuildSpec() { return getBuildSpec(true); } public ICommand[] getBuildSpec(boolean makeCopy) { //thread safety: copy reference in case of concurrent write ICommand[] oldCommands= this.buildSpec; if (oldCommands == null) return EMPTY_COMMAND_ARRAY; if (!makeCopy) return oldCommands; ICommand[] result= new ICommand[oldCommands.length]; for (int i= 0; i < result.length; i++) result[i]= (ICommand)((BuildCommand)oldCommands[i]).clone(); return result; } /* (non-Javadoc) * @see IProjectDescription#getComment() */ public String getComment() { return comment; } /* (non-Javadoc) * @see IProjectDescription#getDynamicReferences() */ public IProject[] getDynamicReferences() { return getDynamicReferences(true); } public IProject[] getDynamicReferences(boolean makeCopy) { if (dynamicRefs == null) return EMPTY_PROJECT_ARRAY; return makeCopy ? (IProject[])dynamicRefs.clone() : dynamicRefs; } /** * Returns the link location for the given resource name. Returns null if no such link exists. */ public URI getLinkLocationURI(IPath aPath) { if (linkDescriptions == null) return null; LinkDescription desc= (LinkDescription)linkDescriptions.get(aPath); return desc == null ? null : desc.getLocationURI(); } /** * Returns the filter for the given resource name. Returns null if no such filter exists. */ synchronized public LinkedList/*<FilterDescription>*/getFilter(IPath aPath) { if (filterDescriptions == null) return null; return (LinkedList /*<FilterDescription> */)filterDescriptions.get(aPath); } /** * Returns the map of link descriptions (IPath (project relative path) -> LinkDescription). * Since this method is only used internally, it never creates a copy. Returns null if the * project does not have any linked resources. */ public HashMap getLinks() { return linkDescriptions; } /** * Returns the map of filter descriptions (IPath (project relative path) -> * LinkedList<FilterDescription>). Since this method is only used internally, it never creates a * copy. Returns null if the project does not have any filtered resources. */ public HashMap getFilters() { return filterDescriptions; } /** * Returns the map of variable descriptions (String (variable name) -> VariableDescription). * Since this method is only used internally, it never creates a copy. Returns null if the * project does not have any variables. */ public HashMap getVariables() { return variableDescriptions; } /** * @see IProjectDescription#getLocation() * @deprecated */ public IPath getLocation() { if (location == null) return null; return FileUtil.toPath(location); } /* (non-Javadoc) * @see IProjectDescription#getLocationURI() */ public URI getLocationURI() { return location; } /* (non-Javadoc) * @see IProjectDescription#getNatureIds() */ public String[] getNatureIds() { return getNatureIds(true); } public String[] getNatureIds(boolean makeCopy) { if (natures == null) return EMPTY_STRING_ARRAY; return makeCopy ? (String[])natures.clone() : natures; } /* (non-Javadoc) * @see IProjectDescription#getReferencedProjects() */ public IProject[] getReferencedProjects() { return getReferencedProjects(true); } public IProject[] getReferencedProjects(boolean makeCopy) { if (staticRefs == null) return EMPTY_PROJECT_ARRAY; return makeCopy ? (IProject[])staticRefs.clone() : staticRefs; } /** * Returns the URI to load a resource snapshot from. May return <code>null</code> if no snapshot * is set. * <p> * <strong>EXPERIMENTAL</strong>. This constant has been added as part of a work in progress. * There is no guarantee that this API will work or that it will remain the same. Please do not * use this API without consulting with the Platform Core team. * </p> * * @return the snapshot location URI, or <code>null</code>. * @see IProject#loadSnapshot(int, URI, IProgressMonitor) * @see #setSnapshotLocationURI(URI) * @since 3.6 */ public URI getSnapshotLocationURI() { return snapshotLocation; } /* (non-Javadoc) * @see IProjectDescription#hasNature(String) */ public boolean hasNature(String natureID) { String[] natureIDs= getNatureIds(false); for (int i= 0; i < natureIDs.length; ++i) if (natureIDs[i].equals(natureID)) return true; return false; } /** * Returns true if any private attributes of the description have changed. Private attributes * are those that are not stored in the project description file (.project). */ public boolean hasPrivateChanges(ProjectDescription description) { if (!Arrays.equals(dynamicRefs, description.getDynamicReferences(false))) return true; IPath otherLocation= description.getLocation(); if (location == null) return otherLocation != null; return !location.equals(otherLocation); } /** * Returns true if any public attributes of the description have changed. Public attributes are * those that are stored in the project description file (.project). */ public boolean hasPublicChanges(ProjectDescription description) { if (!getName().equals(description.getName())) return true; if (!comment.equals(description.getComment())) return true; //don't bother optimizing if the order has changed if (!Arrays.equals(buildSpec, description.getBuildSpec(false))) return true; if (!Arrays.equals(staticRefs, description.getReferencedProjects(false))) return true; if (!Arrays.equals(natures, description.getNatureIds(false))) return true; HashMap otherFilters= description.getFilters(); if ((filterDescriptions == null) && (otherFilters != null)) return otherFilters != null; if ((filterDescriptions != null) && !filterDescriptions.equals(otherFilters)) return true; HashMap otherVariables= description.getVariables(); if ((variableDescriptions == null) && (otherVariables != null)) return true; if ((variableDescriptions != null) && !variableDescriptions.equals(otherVariables)) return true; final HashMap otherLinks= description.getLinks(); if (linkDescriptions != otherLinks) { if (linkDescriptions == null || !linkDescriptions.equals(otherLinks)) return true; } final URI otherSnapshotLoc= description.getSnapshotLocationURI(); if (snapshotLocation != otherSnapshotLoc) { if (snapshotLocation == null || !snapshotLocation.equals(otherSnapshotLoc)) return true; } return false; } /* (non-Javadoc) * @see IProjectDescription#newCommand() */ public ICommand newCommand() { return new BuildCommand(); } /* (non-Javadoc) * @see IProjectDescription#setBuildSpec(ICommand[]) */ public void setBuildSpec(ICommand[] value) { Assert.isLegal(value != null); //perform a deep copy in case clients perform further changes to the command ICommand[] result= new ICommand[value.length]; for (int i= 0; i < result.length; i++) { result[i]= (ICommand)((BuildCommand)value[i]).clone(); //copy the reference to any builder instance from the old build spec //to preserve builder states if possible. for (int j= 0; j < buildSpec.length; j++) { if (result[i].equals(buildSpec[j])) { ((BuildCommand)result[i]).setBuilder(((BuildCommand)buildSpec[j]).getBuilder()); break; } } } buildSpec= result; } /* (non-Javadoc) * @see IProjectDescription#setComment(String) */ public void setComment(String value) { comment= value; } /* (non-Javadoc) * @see IProjectDescription#setDynamicReferences(IProject[]) */ public void setDynamicReferences(IProject[] value) { Assert.isLegal(value != null); dynamicRefs= copyAndRemoveDuplicates(value); cachedRefs= null; } /** * Sets the map of link descriptions (String name -> LinkDescription). Since this method is only * used internally, it never creates a copy. May pass null if this project does not have any * linked resources */ public void setLinkDescriptions(HashMap linkDescriptions) { this.linkDescriptions= linkDescriptions; } /** * Sets the map of filter descriptions (String name -> LinkedList<LinkDescription>). Since this * method is only used internally, it never creates a copy. May pass null if this project does * not have any filtered resources */ public void setFilterDescriptions(HashMap filterDescriptions) { this.filterDescriptions= filterDescriptions; } /** * Sets the map of variable descriptions (String name -> VariableDescription). Since this method * is only used internally, it never creates a copy. May pass null if this project does not have * any variables */ public void setVariableDescriptions(HashMap variableDescriptions) { this.variableDescriptions= variableDescriptions; } /** * Sets the description of a link. Setting to a description of null will remove the link from * the project description. * * @return <code>true</code> if the description was actually changed, <code>false</code> * otherwise. * @since 3.5 returns boolean (was void before) */ public boolean setLinkLocation(IPath path, LinkDescription description) { HashMap tempMap= linkDescriptions; if (description != null) { //addition or modification if (tempMap == null) tempMap= new HashMap(10); else //copy on write to protect against concurrent read tempMap= (HashMap)tempMap.clone(); Object oldValue= tempMap.put(path, description); if (oldValue != null && description.equals(oldValue)) { //not actually changed anything return false; } linkDescriptions= tempMap; } else { //removal if (tempMap == null) return false; //copy on write to protect against concurrent access HashMap newMap= (HashMap)tempMap.clone(); Object oldValue= newMap.remove(path); if (oldValue == null) { //not actually changed anything return false; } linkDescriptions= newMap.size() == 0 ? null : newMap; } return true; } /** * Add the description of a filter. Setting to a description of null will remove the filter from * the project description. */ synchronized public void addFilter(IPath path, FilterDescription description) { Assert.isNotNull(description); if (filterDescriptions == null) filterDescriptions= new HashMap(10); LinkedList/*<FilterDescription>*/descList= (LinkedList /*<FilterDescription> */)filterDescriptions.get(path); if (descList == null) { descList= new LinkedList/*<FilterDescription>*/(); filterDescriptions.put(path, descList); } descList.add(description); } /** * Add the description of a filter. Setting to a description of null will remove the filter from * the project description. */ synchronized public void removeFilter(IPath path, FilterDescription description) { if (filterDescriptions != null) { LinkedList/*<FilterDescription>*/descList= (LinkedList /*<FilterDescription> */)filterDescriptions.get(path); if (descList != null) { descList.remove(description); if (descList.size() == 0) { filterDescriptions.remove(path); if (filterDescriptions.size() == 0) filterDescriptions= null; } } } } /** * Sets the description of a variable. Setting to a description of null will remove the variable * from the project description. * * @return <code>true</code> if the description was actually changed, <code>false</code> * otherwise. * @since 3.5 */ public boolean setVariableDescription(String name, VariableDescription description) { HashMap tempMap= variableDescriptions; if (description != null) { // addition or modification if (tempMap == null) tempMap= new HashMap(10); else // copy on write to protect against concurrent read tempMap= (HashMap)tempMap.clone(); Object oldValue= tempMap.put(name, description); if (oldValue != null && description.equals(oldValue)) { //not actually changed anything return false; } variableDescriptions= tempMap; } else { // removal if (tempMap == null) return false; // copy on write to protect against concurrent access HashMap newMap= (HashMap)tempMap.clone(); Object oldValue= newMap.remove(name); if (oldValue == null) { //not actually changed anything return false; } variableDescriptions= newMap.size() == 0 ? null : newMap; } return true; } /** * set the filters for a given resource. Setting to a description of null will remove the filter * from the project description. * * @return <code>true</code> if the description was actually changed, <code>false</code> * otherwise. */ synchronized public boolean setFilters(IPath path, LinkedList/*<FilterDescription>*/descriptions) { if (descriptions != null) { // addition if (filterDescriptions == null) filterDescriptions= new HashMap(10); Object oldValue= filterDescriptions.put(path, descriptions); if (oldValue != null && descriptions.equals(oldValue)) { //not actually changed anything return false; } } else { // removal if (filterDescriptions == null) return false; Object oldValue= filterDescriptions.remove(path); if (oldValue == null) { //not actually changed anything return false; } if (filterDescriptions.size() == 0) filterDescriptions= null; } return true; } /* (non-Javadoc) * @see IProjectDescription#setLocation(IPath) */ public void setLocation(IPath path) { this.location= path == null ? null : URIUtil.toURI(path); } public void setLocationURI(URI location) { this.location= location; } /* (non-Javadoc) * @see IProjectDescription#setName(String) */ public void setName(String value) { super.setName(value); } /* (non-Javadoc) * @see IProjectDescription#setNatureIds(String[]) */ public void setNatureIds(String[] value) { natures= (String[])value.clone(); } /* (non-Javadoc) * @see IProjectDescription#setReferencedProjects(IProject[]) */ public void setReferencedProjects(IProject[] value) { Assert.isLegal(value != null); staticRefs= copyAndRemoveDuplicates(value); cachedRefs= null; } /** * Sets the location URI for a project snapshot that may be loaded automatically when the * project is created in a workspace. * <p> * <strong>EXPERIMENTAL</strong>. This method has been added as part of a work in progress. * There is no guarantee that this API will work or that it will remain the same. Please do not * use this API without consulting with the Platform Core team. * </p> * * @param snapshotLocation the location URI or <code>null</code> to clear the setting * @see IProject#loadSnapshot(int, URI, IProgressMonitor) * @see #getSnapshotLocationURI() * @since 3.6 */ public void setSnapshotLocationURI(URI snapshotLocation) { this.snapshotLocation= snapshotLocation; } public URI getGroupLocationURI(IPath projectRelativePath) { return LinkDescription.VIRTUAL_LOCATION; } }