| /******************************************************************************* |
| * Copyright (c) 2005, 2015 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 |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 |
| *******************************************************************************/ |
| 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.IContentType; |
| import org.eclipse.core.runtime.content.IContentTypeManager.ISelectionPolicy; |
| import org.eclipse.core.runtime.content.IContentTypeMatcher; |
| 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); |
| } |
| |
| @Override |
| 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 InstanceScope.INSTANCE; |
| return projectScope; |
| } |
| |
| @Override |
| public IPath getLocation() { |
| return getDelegate().getLocation(); |
| } |
| |
| @Override |
| public String getName() { |
| return getDelegate().getName(); |
| } |
| |
| @Override |
| public IEclipsePreferences getNode(String qualifier) { |
| return getDelegate().getNode(qualifier); |
| } |
| |
| @Override |
| public int hashCode() { |
| return getName().hashCode(); |
| } |
| |
| @Override |
| 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$ |
| |
| 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<>(enabledNatures.length); |
| for (String enabledNature : enabledNatures) { |
| ProjectNatureDescriptor descriptor = (ProjectNatureDescriptor) workspace.getNatureDescriptor(enabledNature); |
| if (descriptor == null) |
| // no descriptor found for the nature, skip it |
| continue; |
| String[] natureContentTypes = descriptor.getContentTypeIds(); |
| for (String natureContentType : natureContentTypes) |
| // collect associate content types |
| related.add(natureContentType); |
| } |
| 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); |
| } |
| |
| @SuppressWarnings({"unchecked"}) |
| 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(null)); |
| 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; |
| } |
| } |