/******************************************************************************* * Copyright (c) 2005, 2011 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 * James Blackburn (Broadcom Corp.) - ongoing development *******************************************************************************/ package org.eclipse.core.internal.resources; import java.util.*; import org.eclipse.core.internal.utils.Cache; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.runtime.*; import org.eclipse.core.runtime.content.*; import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; import org.eclipse.core.runtime.preferences.*; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; /** * Manages project-specific content type behavior. * * @see ContentDescriptionManager * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy * @since 3.1 */ public class ProjectContentTypes { /** * A project-aware content type selection policy. * This class is also a dynamic scope context that will delegate to either * project or instance scope depending on whether project specific settings were enabled * for the project in question. */ private class ProjectContentTypeSelectionPolicy implements ISelectionPolicy, IScopeContext { // corresponding project private Project project; // cached project scope private IScopeContext projectScope; public ProjectContentTypeSelectionPolicy(Project project) { this.project = project; this.projectScope = new ProjectScope(project); } /* (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof IScopeContext)) return false; IScopeContext other = (IScopeContext) obj; if (!getName().equals(other.getName())) return false; IPath location = getLocation(); return location == null ? other.getLocation() == null : location.equals(other.getLocation()); } private IScopeContext getDelegate() { if (!usesContentTypePreferences(project.getName())) return ProjectContentTypes.INSTANCE_SCOPE; return projectScope; } public IPath getLocation() { return getDelegate().getLocation(); } public String getName() { return getDelegate().getName(); } public IEclipsePreferences getNode(String qualifier) { return getDelegate().getNode(qualifier); } /* (non-Javadoc) * @see java.lang.Object#hashCode() */ public int hashCode() { return getName().hashCode(); } public IContentType[] select(IContentType[] candidates, boolean fileName, boolean content) { return ProjectContentTypes.this.select(project, candidates, fileName, content); } } private static final String CONTENT_TYPE_PREF_NODE = "content-types"; //$NON-NLS-1$ static final InstanceScope INSTANCE_SCOPE = new InstanceScope(); private static final String PREF_LOCAL_CONTENT_TYPE_SETTINGS = "enabled"; //$NON-NLS-1$ private static final Preferences PROJECT_SCOPE = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE); private Cache contentTypesPerProject; private Workspace workspace; static boolean usesContentTypePreferences(String projectName) { try { // be careful looking up for our node so not to create any nodes as side effect Preferences node = PROJECT_SCOPE; //TODO once bug 90500 is fixed, should be simpler // for now, take the long way if (!node.nodeExists(projectName)) return false; node = node.node(projectName); if (!node.nodeExists(Platform.PI_RUNTIME)) return false; node = node.node(Platform.PI_RUNTIME); if (!node.nodeExists(CONTENT_TYPE_PREF_NODE)) return false; node = node.node(CONTENT_TYPE_PREF_NODE); return node.getBoolean(PREF_LOCAL_CONTENT_TYPE_SETTINGS, false); } catch (BackingStoreException e) { // exception treated when retrieving the project preferences } return false; } public ProjectContentTypes(Workspace workspace) { this.workspace = workspace; // keep cache small this.contentTypesPerProject = new Cache(5, 30, 0.4); } /** * Collect content types associated to the natures configured for the given project. */ private Set<String> collectAssociatedContentTypes(Project project) { String[] enabledNatures = workspace.getNatureManager().getEnabledNatures(project); if (enabledNatures.length == 0) return Collections.EMPTY_SET; Set<String> related = new HashSet<String>(enabledNatures.length); for (int i = 0; i < enabledNatures.length; i++) { ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNatures[i]); if (descriptor == null) // no descriptor found for the nature, skip it continue; String[] natureContentTypes = descriptor.getContentTypeIds(); for (int j = 0; j < natureContentTypes.length; j++) // collect associate content types related.add(natureContentTypes[j]); } return related; } public void contentTypePreferencesChanged(IProject project) { final ProjectInfo info = (ProjectInfo) ((Project) project).getResourceInfo(false, false); if (info != null) info.setMatcher(null); } /** * Creates a content type matcher for the given project. Takes natures and user settings into account. */ private IContentTypeMatcher createMatcher(Project project) { ProjectContentTypeSelectionPolicy projectContentTypeSelectionPolicy = new ProjectContentTypeSelectionPolicy(project); return Platform.getContentTypeManager().getMatcher(projectContentTypeSelectionPolicy, projectContentTypeSelectionPolicy); } private Set<String> getAssociatedContentTypes(Project project) { final ResourceInfo info = project.getResourceInfo(false, false); if (info == null) // the project has been deleted return null; final String projectName = project.getName(); synchronized (contentTypesPerProject) { Cache.Entry entry = contentTypesPerProject.getEntry(projectName); if (entry != null) // we have an entry... if (entry.getTimestamp() == info.getContentId()) // ...and it is not stale, so just return it return (Set<String>) entry.getCached(); // no cached information found, have to collect associated content types Set<String> result = collectAssociatedContentTypes(project); if (entry == null) // there was no entry before - create one entry = contentTypesPerProject.addEntry(projectName, result, info.getContentId()); else { // just update the existing entry entry.setTimestamp(info.getContentId()); entry.setCached(result); } return result; } } public IContentTypeMatcher getMatcherFor(Project project) throws CoreException { ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, false); //fail if project has been deleted concurrently if (info == null) project.checkAccessible(project.getFlags(info)); IContentTypeMatcher matcher = info.getMatcher(); if (matcher != null) return matcher; matcher = createMatcher(project); info.setMatcher(matcher); return matcher; } /** * Implements project specific, nature-based selection policy. No content types are vetoed. * * The criteria for this policy is as follows: * <ol> * <li>associated content types should appear before non-associated content types</li> * <li>otherwise, relative ordering should be preserved.</li> * </ol> * * @see org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy */ final IContentType[] select(Project project, IContentType[] candidates, boolean fileName, boolean content) { // since no vetoing is done here, don't go further if there is nothing to sort if (candidates.length < 2) return candidates; final Set<String> associated = getAssociatedContentTypes(project); if (associated == null || associated.isEmpty()) // project has no content types associated return candidates; int associatedCount = 0; for (int i = 0; i < candidates.length; i++) // is it an associated content type? if (associated.contains(candidates[i].getId())) { // need to move it to the right spot (unless all types visited so far are associated as well) if (associatedCount < i) { final IContentType promoted = candidates[i]; // move all non-associated content types before it one one position up... for (int j = i; j > associatedCount; j--) candidates[j] = candidates[j - 1]; // ...so there is an empty spot for the content type we are promoting candidates[associatedCount] = promoted; } associatedCount++; } return candidates; } }