blob: 3227b19d5775dd95e408e1d854649626feaf01e4 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2008-2019 See4sys, BMW Car IT, Continental Engineering Services, itemis 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:
* See4sys - Initial API and implementation
* BMW Car IT - Added/Updated javadoc
* Conti - [353487] - Performance of ExtendedPlatform.isPlatformPrivateResource
* itemis - [357965] Scheduling rules for saving new resources must be scoped to enclosing project
* BMW Car IT - [373481] Performance optimizations for model loading
* BMW Car IT - [374883] Improve handling of out-of-sync workspace files during descriptor initialization
*
* </copyright>
*/
package org.eclipse.sphinx.platform.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IProjectNature;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceRuleFactory;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.core.runtime.content.IContentTypeSettings;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.MultiRule;
import org.eclipse.osgi.util.NLS;
import org.eclipse.sphinx.platform.IExtendedPlatformConstants;
import org.eclipse.sphinx.platform.internal.Activator;
import org.eclipse.sphinx.platform.internal.messages.Messages;
import org.eclipse.sphinx.platform.resources.AbstractResourceVisitor;
import org.eclipse.sphinx.platform.resources.IResourceSyncMarker;
import org.eclipse.sphinx.platform.resources.MarkerJob;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.wiring.BundleRevision;
/**
* A utility class which extends the functionality provided by the eclipse platform.
*/
@SuppressWarnings("restriction")
public final class ExtendedPlatform {
public static final int LIMIT_INDIVIDUAL_RESOURCES_SCHEDULING_RULE = 500;
// Prevent from instantiation
private ExtendedPlatform() {
}
public static final boolean IS_PLATFORM_RUNNING;
static {
boolean result = false;
try {
result = Platform.isRunning();
} catch (Throwable exception) {
// Assume that we aren't running
}
IS_PLATFORM_RUNNING = result;
}
public static final boolean IS_WORKSPACE_AVAILABLE;
static {
IS_WORKSPACE_AVAILABLE = isBundleAvailable(ResourcesPlugin.PI_RESOURCES);
}
/**
* Returns the feature version of the running instance of the Eclipse platform (e.g., 3.4 for Eclipse 3.4, 3.5 for
* Eclipse 3.5).
*
* @return The feature version of the running Eclipse platform instance.
*/
public static String getFeatureVersion() {
Bundle bundle = Platform.getBundle("org.eclipse.core.runtime"); //$NON-NLS-1$
if (bundle != null) {
Dictionary<?, ?> headers = bundle.getHeaders();
String version = (String) headers.get("Bundle-Version"); //$NON-NLS-1$
return version.substring(0, 3);
}
return ""; //$NON-NLS-1$
}
/**
* Returns an ordinal for the feature version of the running instance of the Eclipse platform (e.g., 34 for Eclipse
* 3.4, 35 for Eclipse 3.5). Can be used for easy testing if the running version of Eclipse platform is newer or
* older than a given version.
*
* @return The feature version ordinal of the running Eclipse platform instance.
*/
public static int getFeatureVersionOrdinal() {
String version = getFeatureVersion();
return Integer.parseInt(version.replaceAll("\\.", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
public static boolean isBundleAvailable(String pluginId) {
if (IS_PLATFORM_RUNNING) {
try {
Bundle resourcesBundle = Platform.getBundle(pluginId);
return resourcesBundle != null && (resourcesBundle.getState() & (Bundle.ACTIVE | Bundle.STARTING | Bundle.RESOLVED)) != 0;
} catch (Throwable exception) {
// Assume that it's not available
}
}
return false;
}
/**
* Returns the active bundle matching specified symbolic name that has the highest version. Starts that bundle if
* not already in active state.
*
* @param symbolicName
* Symbolic name of bundle to be loaded.
* @return The bundle that has the specified symbolic name with the highest version in active state, or
* <tt>null</tt> if no bundle can be found or activated.
* @see Platform#getBundle(String)
*/
public static Bundle loadBundle(String symbolicName) {
Bundle bundle = Platform.getBundle(symbolicName);
if (bundle != null) {
try {
// Avoid attempts to start fragments individually (they get automatically started when their host
// plug-in is started)
BundleRevision revision = bundle.adapt(BundleRevision.class);
if (revision != null && (revision.getTypes() & BundleRevision.TYPE_FRAGMENT) == 0) {
if (bundle.getState() != Bundle.ACTIVE && bundle.getState() != Bundle.STARTING) {
bundle.start(Bundle.START_TRANSIENT);
}
}
} catch (BundleException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
return null;
}
}
return bundle;
}
/**
* Returns the contributing bundle for a given <code>contribution</code>.
*
* @param contribution
* The contribution whose contributor bundle must be returned<br>
* (must not be <tt>null</tt>).
* @return The bundle that is responsible for the contribution of the given contribution (configuration element).
*/
public static final Bundle loadContributorBundle(IConfigurationElement contribution) {
Assert.isNotNull(contribution);
String symbolicName = contribution.getContributor().getName();
return loadBundle(symbolicName);
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for modifying the given {@link IResource resource}.
*
* @param resource
* The {@link IResource resource} to be modified.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if no such could be created.
*/
public static ISchedulingRule createModifySchedulingRule(IResource resource) {
if (resource != null) {
IResourceRuleFactory ruleFactory = resource.getWorkspace().getRuleFactory();
return ruleFactory.modifyRule(resource);
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for modifying the given collection of {@link IFile
* file}s.
*
* @param files
* The collection of {@link IFile file}s to be modified.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if provided collection of
* {@link IFile file}s is empty or no such could be created for some other reason.
*/
public static ISchedulingRule createModifySchedulingRule(Collection<IFile> files) {
if (files != null) {
/*
* Performance optimization: Create a scheduling rule on a per file basis only if number of files is
* reasonably low.
*/
if (files.size() < LIMIT_INDIVIDUAL_RESOURCES_SCHEDULING_RULE) {
Set<ISchedulingRule> rules = new HashSet<ISchedulingRule>();
for (IFile file : files) {
ISchedulingRule rule = createModifySchedulingRule(file);
if (rule != null) {
rules.add(rule);
}
}
return MultiRule.combine(rules.toArray(new ISchedulingRule[rules.size()]));
} else {
// Return workspace root as scheduling rule otherwise
return ResourcesPlugin.getWorkspace().getRoot();
}
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for creating the given {@link IResource resource}.
*
* @param resource
* The {@link IResource resource} to be created.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if no such could be created.
*/
public static ISchedulingRule createCreateSchedulingRule(IResource resource) {
if (resource != null) {
IResourceRuleFactory ruleFactory = resource.getWorkspace().getRuleFactory();
return ruleFactory.createRule(resource);
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for saving the given new {@link IResource resource}.
*
* @param resource
* The new {@link IResource resource} to be saved.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if no such could be created.
*/
public static ISchedulingRule createSaveNewSchedulingRule(IResource resource) {
if (resource != null) {
// Create modify rule for project behind given resource
/*
* !! Important Note !! It might be that not all the folders on the resource's path already exist and are
* supposed to be created along with the new resource being saved. We therefore cannot go along with a
* create rule just for the file being created but must use a modify rule for the underlying project.
* Otherwise the creation of not yet existing containing folders would lead to rule conflicts.
*/
IProject project = resource.getProject();
IResourceRuleFactory ruleFactory = resource.getWorkspace().getRuleFactory();
return MultiRule.combine(new ISchedulingRule[] { ruleFactory.modifyRule(project), ruleFactory.refreshRule(project) });
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for saving the new {@link IFile file} with given
* {@link IPath path}.
*
* @param filePath
* The {@link IPath path} of the new {@link IFile file} to be saved.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if no such could be created.
*/
public static ISchedulingRule createSaveNewSchedulingRule(IPath filePath) {
if (filePath != null) {
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(filePath);
return createSaveNewSchedulingRule(file);
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for saving the new {@link IFile file}s with the given
* collection of {@link IPath path}s.
*
* @param filePaths
* The collection of {@link IPath}s of the new {@link IFile file}s to be saved.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if provided collection of
* {@link IPath path}s is empty or no such could be created for some other reason.
*/
public static ISchedulingRule createSaveNewSchedulingRule(Collection<IPath> filePaths) {
if (filePaths != null) {
/*
* Performance optimization: Create a scheduling rule on a per file basis only if number of file paths is
* reasonably low.
*/
if (filePaths.size() < LIMIT_INDIVIDUAL_RESOURCES_SCHEDULING_RULE) {
Set<ISchedulingRule> rules = new HashSet<ISchedulingRule>();
for (IPath path : filePaths) {
ISchedulingRule rule = createSaveNewSchedulingRule(path);
if (rule != null) {
rules.add(rule);
}
}
return MultiRule.combine(rules.toArray(new ISchedulingRule[rules.size()]));
} else {
// Return workspace root as scheduling rule otherwise
return ResourcesPlugin.getWorkspace().getRoot();
}
}
return null;
}
/**
* Creates a {@link ISchedulingRule scheduling rule} required for saving the given {@link IResource resource}.
*
* @param resource
* The {@link IResource resource} to be saved.
* @return The resulting {@link ISchedulingRule scheduling rule} or <code>null</code> if no such could be created.
*/
public static ISchedulingRule createSaveSchedulingRule(IResource resource) {
if (resource != null) {
IResourceRuleFactory ruleFactory = resource.getWorkspace().getRuleFactory();
return MultiRule.combine(new ISchedulingRule[] { ruleFactory.modifyRule(resource), ruleFactory.refreshRule(resource) });
}
return null;
}
/**
* Indicates if given {@link IResource resource} is a project description file, i.e. a file named ".project" located
* directly under the root of a project.
*
* @param resource
* The {@link IResource resource} to be investigated.
* @return <code>true</code> if given {@link IResource resource} is a project description file, or
* <code>false</code> otherwise.
*/
public static boolean isProjectDescriptionFile(IResource resource) {
if (resource instanceof IFile) {
/*
* Performance optimization: test path segment number (which is fast) first and let path segment name
* comparison (which is slow) take place only if the path segment number condition is true
*/
IPath path = resource.getFullPath();
return path.segmentCount() == 2 && IProjectDescription.DESCRIPTION_FILE_NAME.equals(path.lastSegment());
}
return false;
}
/**
* Indicates if given {@link IResource resource} is a project properties folder, i.e. a folder named ".settings"
* located directly under the root of a project.
*
* @param resource
* The {@link IResource resource} to be investigated.
* @return <code>true</code> if given {@link IResource resource} is a project properties folder, or
* <code>false</code> otherwise.
*/
public static boolean isProjectPropertiesFolder(IResource resource) {
if (resource instanceof IFolder) {
/*
* Performance optimization: test path segment number (which is fast) first and let path segment name
* comparison (which is slow) take place only if the path segment number condition is true
*/
IPath path = resource.getFullPath();
return path.segmentCount() == 2 && ".settings".equals(path.lastSegment()); //$NON-NLS-1$
}
return false;
}
/**
* Indicates if given {@link IResource resource} is a project properties file, i.e. a *.prefs file in a folder named
* ".settings" and located directly under the root of a project.
*
* @param resource
* The {@link IResource resource} to be investigated.
* @return <code>true</code> if given {@link IResource resource} is a project properties file, or <code>false</code>
* otherwise.
*/
public static boolean isProjectPropertiesFile(IResource resource) {
if (resource instanceof IFile) {
return isProjectPropertiesFolder(resource.getParent()) && "prefs".equals(resource.getFileExtension()); //$NON-NLS-1$
}
return false;
}
/**
* Returns whether given {@link IResource resource} is team private (i.e., a resource marked for internal usage by
* CVS or SVN only).
* <p>
* This is a convenience method, fully equivalent to <code>isTeamPrivateResource(resource, IResource.NONE)</code>.
* </p>
*
* @return <code>true</code> if given {@link IResource resource} is team private, or <code>false</code> otherwise.
* @see IResource#isTeamPrivateMember(boolean)
* @since 0.7.0
*/
public static boolean isTeamPrivateResource(IResource resource) {
return isTeamPrivateResource(resource, IResource.NONE);
}
/**
* Returns whether given {@link IResource resource} is team private (i.e., a resource marked for internal usage by
* CVS or SVN only).
* <p>
* The {@link #CHECK_ANCESTORS} option flag indicates whether this method should consider {@link IResource ancestor
* resource}s in its calculation. If the {@link IResource#CHECK_ANCESTORS} flag is present, this method will return
* <code>true</code> if given {@link IResource resource}, or any {@link IResource parent resource}, is team private.
* If the {@link IResource#CHECK_ANCESTORS} option flag is not specified, this method returns false for children of
* team private resources.
* </p>
*
* @param options
* Bit-wise OR of option flag constants (only {@link IResource#CHECK_ANCESTORS} is applicable).
* @return <code>true</code> if given {@link IResource resource} is team private, or <code>false</code> otherwise.
* @see IResource#isTeamPrivateMember(boolean)
* @see IResource#CHECK_ANCESTORS
* @since 0.7.0
*/
public static boolean isTeamPrivateResource(IResource resource, int options) {
if (resource != null) {
// CVS private resources can be detected using native Eclipse API because CVS client is always shipped
// with Eclipse
if (resource.isTeamPrivateMember()) {
return true;
}
// Do extra name-based check for detecting SVN private resources because SVN client is not necessarily
// present in every client's Eclipse target platform
/*
* Performance optimization: test if ".svn" is contained in resource name; this enables direct detection of
* ".svn" folders and "*.svn-base" files and avoids recursive checking of ancestor resources in these cases.
*/
if (resource.getName().contains(".svn")) { //$NON-NLS-1$
return true;
}
// Check ancestors if the appropriate option is set
if ((options & IResource.CHECK_ANCESTORS) != 0) {
return isTeamPrivateResource(resource.getParent(), options);
}
}
return false;
}
/**
* Returns whether given {@link IResource resource} is platform private (i.e., a team private resource, project
* description file, or project properties folder or file).
*
* @return <code>true</code> if given {@link IResource resource} is platform private, or <code>false</code>
* otherwise.
* @see IResource#isTeamPrivateMember(boolean)
* @since 0.7.0
*/
public static boolean isPlatformPrivateResource(IResource resource) {
if (isTeamPrivateResource(resource)) {
return true;
} else if (isProjectDescriptionFile(resource)) {
return true;
} else if (isProjectPropertiesFolder(resource) || isProjectPropertiesFile(resource)) {
return true;
}
return false;
}
/**
* Returns all {@link IFile file}s contained in the {@link IFolder folder}. The {@link IFolder folder} is searched
* recursively. Thus, {@link IFile file}s contained in descending {@link IFolder folder}s are also returned.
*
* @param folder
* The {@link IFolder folder} owning the {@link IFile file}s to return.
* @return The list of {@link IFile file}s owned by the given {@link IFolder folder} (and its descendant
* {@link IFolder folder}s).
* @since 0.7.0
*/
public static final Collection<IFile> getAllFiles(IFolder folder) {
final List<IFile> files = new ArrayList<IFile>();
try {
folder.accept(new AbstractResourceVisitor() {
@Override
public boolean doVisit(IResource resource) throws CoreException {
if (resource instanceof IFile) {
IFile file = (IFile) resource;
if (file.isAccessible()) {
files.add(file);
}
}
return true;
}
});
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
return files;
}
/**
* @param project
* @param members
* @param files
* @param visitedProjects
* The already visited projects.
* @param deeply
* If <b><code>true</code></b>, goes through referenced projects.
* @since 0.7.0
*/
private static void collectAllFiles(IProject project, IResource[] members, final List<IFile> files, Collection<IProject> visitedProjects,
boolean deeply) {
Assert.isNotNull(project);
Assert.isNotNull(members);
Assert.isNotNull(files);
Assert.isNotNull(visitedProjects);
// Avoid infinite visiting of projects cyclically referencing each other by tracking project which already have
// been visited
visitedProjects.add(project);
// Retrieve all files from the given project
for (IResource resource : members) {
try {
resource.accept(new AbstractResourceVisitor() {
@Override
public boolean doVisit(IResource resource) throws CoreException {
if (resource instanceof IFile) {
IFile file = (IFile) resource;
if (file.isAccessible()) {
files.add(file);
}
}
return true;
}
});
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
}
if (deeply) {
// Go through referenced projects if any
for (IProject referencedProject : getReferencedProjectsSafely(project)) {
if (project.isAccessible() && referencedProject.isAccessible() && !visitedProjects.contains(referencedProject)) {
collectAllFiles(referencedProject, getMembersSafely(referencedProject), files, visitedProjects, deeply);
}
}
}
}
/**
* Returns all files of a project.
*
* @param project
* The project whose files must be returned.
* @param deeply
* If set to <b><code>true</code></b> also the files from referenced projects are returned.
* @return The list of files owned by the given project.<br>
* Goes through referenced projects according to <code>deeply</code> flag.
* @since 0.7.0
*/
public static final Collection<IFile> getAllFiles(IProject project, boolean deeply) {
List<IFile> files = new ArrayList<IFile>();
collectAllFiles(project, getMembersSafely(project), files, new HashSet<IProject>(), deeply);
return files;
}
/**
* Encapsulated method {@link IContainer#members()} in order to keep as much robustness as possible.
*
* @param container
* The container whose members must be returned.
* @return The resources that are members of the given container.
* @since 0.7.0
*/
public final static IResource[] getMembersSafely(IContainer container) {
Assert.isNotNull(container);
try {
if (container.isAccessible()) {
List<IResource> members = new ArrayList<IResource>();
for (IResource member : container.members()) {
if (!isPlatformPrivateResource(member)) {
members.add(member);
}
}
return members.toArray(new IResource[members.size()]);
}
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
return new IResource[0];
}
/**
* Encapsulated method {@link IProject#getReferencedProjects()} in order to keep as much robustness as possible.
*
* @see IProject#getReferencedProjects()
* @param project
* The project whose referenced projects must be returned.
* @return The list of projects that are referenced by the given project.
* @since 0.7.0
*/
public static IProject[] getReferencedProjectsSafely(IProject project) {
Assert.isNotNull(project);
IProject[] referencedProjects = new IProject[0];
try {
if (project.isAccessible()) {
referencedProjects = project.getReferencedProjects();
}
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
return referencedProjects;
}
/**
* Encapsulated method {@link IProject#getReferencingProjects()} in order to keep as much robustness as possible.
*
* @see IProject#getReferencingProjects()
* @param project
* The project whose referencing projects must be returned.
* @return The list of projects that are referencing the given project.
* @since 0.7.0
*/
public static IProject[] getReferencingProjectsSafely(IProject project) {
Assert.isNotNull(project);
return project.getReferencingProjects();
}
private static void collectProjectsInGroup(IProject project, boolean includeReferencingProjects, Set<IProject> projectGroup) {
if (projectGroup.add(project)) {
for (IProject p : getReferencedProjectsSafely(project)) {
collectProjectsInGroup(p, includeReferencingProjects, projectGroup);
}
if (includeReferencingProjects) {
for (IProject p : getReferencingProjectsSafely(project)) {
collectProjectsInGroup(p, includeReferencingProjects, projectGroup);
}
}
}
}
/**
* Computes a group of projects. This method will search recursively all referenced projects of the given project
* and if wanted also the projects that reference the given project.
*
* @param project
* A project whose scope will be computed.
* @param includeReferencingProjects
* If <code>true</code> also includes projects that reference the given project.
*/
public static Set<IProject> getProjectGroup(IProject project, boolean includeReferencingProjects) {
Assert.isNotNull(project);
// A set to which all projects in scope will be added
Set<IProject> projectGroup = new HashSet<IProject>();
// Collect projects in group safely
collectProjectsInGroup(project, includeReferencingProjects, projectGroup);
return projectGroup;
}
/**
* @param project
* @param referencedProjects
* @since 0.7.0
*/
private static void collectReferencedProjects(IProject project, Set<IProject> allReferencedProjects) {
if (project.isAccessible() && allReferencedProjects.add(project)) {
try {
for (IProject p : project.getReferencedProjects()) {
collectReferencedProjects(p, allReferencedProjects);
}
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
}
}
/**
* Returns a {@linkplain Collection collection} containing all the {@linkplain IProject project}s that are
* referenced by the specified project and recursively all the projects that are referenced by the projects that are
* referenced by the given {@link IProject project}.
*
* @param project
* The project whose all referenced projects must be returned.
* @return All the projects that are referenced by the specified {@link IProject project}.
* @since 0.7.0
*/
public static Collection<IProject> getAllReferencedProjects(IProject project) {
Assert.isNotNull(project);
// A set to which all referenced projects will be added
Set<IProject> referencedProjects = new HashSet<IProject>();
// Collect referenced projects safely
collectReferencedProjects(project, referencedProjects);
// Remove the specified project from the returned list
referencedProjects.remove(project);
return referencedProjects;
}
/**
* @param project
* @param referencingProjects
* @since 0.7.0
*/
private static void collectReferencingProjects(IProject project, Set<IProject> allReferencingProjects) {
if (project.isAccessible() && allReferencingProjects.add(project)) {
for (IProject p : project.getReferencingProjects()) {
collectReferencingProjects(p, allReferencingProjects);
}
}
}
/**
* Returns a {@linkplain Collection collection} containing all the {@linkplain IProject project}s that are
* referencing the specified project and recursively all the projects that are referencing the projects that are
* referencing by the given {@link IProject project}.
*
* @param project
* The project whose all referencing projects must be returned.
* @return All the projects that are referencing the specified {@link IProject project}.
* @since 0.7.0
*/
public static Collection<IProject> getAllReferencingProjects(IProject project) {
Assert.isNotNull(project);
// A set to which all referencing projects will be added
Set<IProject> referencingProjects = new HashSet<IProject>();
// Collect referencing projects safely
collectReferencingProjects(project, referencingProjects);
// Remove the specified project from the returned list
referencingProjects.remove(project);
return referencingProjects;
}
/**
* Returns wheather the given {@link IProject} is a root project. Root projects are projects which are not
* referenced from another project. From a root project an entire model can be loaded.
*
* @param project
* The project to be evaluated.
* @return <tt>true</tt> if given project is a root project, <tt>false</tt> otherwise.
*/
public static boolean isRootProject(IProject project) {
return project.getReferencingProjects().length == 0;
}
/**
* Returns the root project to which the given <code>project</code> contributes. Root projects are projects which
* are not referenced from another project. From a root project an entire model can be loaded.
* <p>
* If the given <code>project</code> has more than one root project the first one found will be returned. If the
* provided <code>project</code> is a root project itself it is returned.
*
* @param project
* The project for which the root project is to be determined.
* @return The root project of the given <code>project</code>.
*/
public static IProject getFirstRootProject(IProject project) {
Assert.isNotNull(project);
if (project.isAccessible()) {
List<IProject> rootProjects = new ArrayList<IProject>();
collectRootProjects(new HashSet<IProject>(), rootProjects, project);
if (!rootProjects.isEmpty()) {
return rootProjects.get(0);
}
}
return null;
}
private static void collectRootProjects(Collection<IProject> visitedProjects, Collection<IProject> rootProjects, IProject project) {
if (!project.isOpen()) {
return;
}
if (visitedProjects.contains(project)) {
// A cycle in references has been detected, ends the recursion
rootProjects.add(project);
} else {
// Mark the given project as visited (detection of references cycle)
visitedProjects.add(project);
// Retrieves projects referencing the given project
IProject[] referencingProjects = project.getReferencingProjects();
if (referencingProjects.length == 0) {
// Given project is the end of the references chain; keep it as a model project
rootProjects.add(project);
} else {
for (IProject referencingProject : referencingProjects) {
// Recursive call...
collectRootProjects(visitedProjects, rootProjects, referencingProject);
}
}
}
}
/**
* Returns all root projects in the workspace.
*
* @return All root projects in the workspace.
* @see #getFirstRootProject(IProject)
*/
public static Collection<IProject> getRootProjects() {
Set<IProject> rootProjects = new HashSet<IProject>();
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (!rootProjects.contains(project)) {
collectRootProjects(new HashSet<IProject>(), rootProjects, project);
}
}
return rootProjects;
}
/**
* Returns the projects in the workspace which have the given nature associated with them.
*
* @param natureId
* An identifier specifying the nature of the projects to be returned.
* @return A collection of projects having the specified nature.
*/
public static Collection<IProject> getProjects(String natureId) {
Collection<IProject> projects = new ArrayList<IProject>();
for (IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects()) {
if (project.isAccessible()) {
try {
if (project.hasNature(natureId)) {
projects.add(project);
}
} catch (CoreException ex) {
// fail silent
}
}
}
return projects;
}
/**
* Creates a file name that is unique among the names of the files present in {@link IContainer container} with
* given <code>containerFullPath</code>.
*
* @param containerFullPath
* @param candidateFileName
* @return
*/
public static String createUniqueFileName(IPath containerFullPath, String candidateFileName) {
Assert.isNotNull(containerFullPath);
if (candidateFileName == null || candidateFileName.trim().length() == 0) {
candidateFileName = "default"; //$NON-NLS-1$
}
IPath candidateFilePath = containerFullPath.append(candidateFileName);
String candidateFileBaseName = candidateFilePath.removeFileExtension().lastSegment();
String extension = candidateFilePath.getFileExtension();
for (int i = 1; ResourcesPlugin.getWorkspace().getRoot().exists(candidateFilePath); i++) {
candidateFilePath = containerFullPath.append(candidateFileBaseName + i);
if (extension != null) {
candidateFilePath = candidateFilePath.addFileExtension(extension);
}
}
return candidateFilePath.lastSegment();
}
/**
* Creates a unique {@link IPath path} starting from given <code>candidatePath</code> making sure that the result is
* different from any of the provided <code>allocatedPaths</code>. In case that given <code>candidatePath</code>
* already is unique wrt provided <code>allocatedPaths</code> the <code>candidatePath</code> is returned as is.
* Otherwise the <code>candidatePath</code> is made unique by appending an appropriate number to its last segment.
* Any file extension present on the <code>candiatePath</code> is be preserved.
*
* @param candidatePath
* The candidate {@link IPath path} from which a unique path is to be created.
* @param allocatedPaths
* The {@link IPath path}s from which the created path should be different.
* @return A {@link IPath path} that is unique wrt provided <code>allocatedPaths</code>.
*/
public static IPath createUniquePath(IPath candidatePath, Collection<IPath> allocatedPaths) {
Assert.isNotNull(candidatePath);
Assert.isNotNull(allocatedPaths);
IPath parentPath = candidatePath.removeLastSegments(1);
String candidateLastSegmentName = candidatePath.removeFileExtension().lastSegment();
String extension = candidatePath.getFileExtension();
for (int i = 1; allocatedPaths.contains(candidatePath); i++) {
candidatePath = parentPath.append(candidateLastSegmentName + i);
if (extension != null) {
candidatePath = candidatePath.addFileExtension(extension);
}
}
return candidatePath;
}
private static final String NO_CONTENT_TYPE_ID = "org.eclipse.sphinx.platform.noContentTypeId"; //$NON-NLS-1$
/**
* Returns the content-type identifier of the specified {@linkplain IFile file}.
* <p>
* In order to prevent performance losses, the content type id is cached as a <em>session property</em> on the file.
* It is then possible to retrieve that content type id later by calling
* {@linkplain IFile#getSessionProperty(QualifiedName)} where {@link QualifiedName qualifiedName} can be computed by
* using {@linkplain ExtendedPlatform#toQualifedName(String)} and specifying
* {@linkplain IExtendedPlatformConstants#RESOURCE_PROPERTY_CONTENT_TYPE_ID} as <code>name</code>.
*
* @param file
* The file whose content-type identifier must be returned.
* @return The content-type identifier of the given {@link IFile file} or <code>null</code> if no such could be
* determined.
* @throws CoreException
* @since 0.7.0
*/
public static String getContentTypeId(IFile file) throws CoreException {
// Try to retrieve cached content type id
if (hasCachedContentTypeId(file)) {
return getCachedContentTypeId(file);
}
// No cached content type id available, so retrieve it natively from the file's content description
String contentTypeId = nativeGetContentTypeId(file);
// Cache resulting content type id in order to avoid that it needs to be retrieved natively for a second time
setCachedContentTypeId(file, contentTypeId);
return contentTypeId;
}
/**
* Returns the content-type identifier of the specified {@linkplain File file}.
*
* @param file
* The file whose content-type identifier must be returned.
* @return The content-type identifier of the given {@link File file} or <code>null</code> if no such could be
* determined.
* @throws IOException
* @since 0.7.0
*/
public static String getContentTypeId(File file) throws IOException {
Assert.isNotNull(file);
FileInputStream inputStream = null;
try {
/*
* !! Important Note !! Don't attempt to determine content type id of non-existing files. Otherwise
* Platform.getContentTypeManager().getDescriptionFor() would try to deduce it from the file extension on
* the URI which is very likely to produce inadequate results (e.g., in case of a model file with an .xml
* extension and a metamodel-specifc content type which existed before but got deleted prior to calling this
* method).
*/
if (file.exists()) {
inputStream = new FileInputStream(file);
// FIXME File bug to Eclipse Platform: Platform.getContentTypeManager().getDescriptionFor() apparently
// unable to return content type description for .project contents
IContentDescription description = Platform.getContentTypeManager().getDescriptionFor(inputStream, file.getPath(), null);
if (description != null) {
IContentType contentType = description.getContentType();
if (contentType != null) {
return contentType.getId();
}
}
}
} catch (FileNotFoundException ex) {
// Ignore exception
} finally {
safeClose(inputStream);
}
return null;
}
private static String nativeGetContentTypeId(IFile file) throws CoreException {
if (file != null && file.isAccessible()) {
// Get the file's content description
IContentDescription contentDescription = null;
try {
contentDescription = file.getContentDescription();
} catch (ResourceException rex) {
// File out of sync?
if (!file.isSynchronized(IResource.DEPTH_ZERO)) {
// Refresh file and try again
try {
file.refreshLocal(IResource.DEPTH_ZERO, new NullProgressMonitor());
} catch (Exception ex) {
// Ignore exception
}
contentDescription = file.getContentDescription();
} else {
throw rex;
}
}
// Extract content type id
if (contentDescription != null) {
IContentType contentType = contentDescription.getContentType();
if (contentType != null) {
return contentType.getId();
}
}
}
return null;
}
public static boolean hasCachedContentTypeId(IFile file) {
return internalGetCachedContentTypeId(file) != null;
}
private static String getCachedContentTypeId(IFile file) {
// Retrieve encoded content type id
String rawContentTypeId = internalGetCachedContentTypeId(file);
if (rawContentTypeId == null) {
return null;
}
// Decode cached content type id and return it
if (rawContentTypeId.intern() != NO_CONTENT_TYPE_ID) {
// Ensure backward compatibility
if (rawContentTypeId.intern() != "org.eclipse.sphinx.platform.unspecifiedContentType") { //$NON-NLS-1$
return rawContentTypeId;
}
}
return null;
}
private static String internalGetCachedContentTypeId(IFile file) {
if (file != null && file.isAccessible()) {
// The resource property key for the file's content type id
QualifiedName key = ExtendedPlatform.toQualifedName(IExtendedPlatformConstants.RESOURCE_PROPERTY_CONTENT_TYPE_ID);
// Try to retrieve content type id from the file's session property
Object sessionProperty = null;
try {
sessionProperty = file.getSessionProperty(key);
} catch (Exception ex) {
// Ignore exception
}
if (sessionProperty instanceof String) {
return (String) sessionProperty;
}
// Try to retrieve content type id from the file's persistent property
String persistentProperty = null;
try {
persistentProperty = file.getPersistentProperty(key);
} catch (Exception ex) {
// Ignore exception
}
if (persistentProperty != null) {
// Re-cache content type id as session property on underlying file in order to avoid that it needs
// to be retrieved from the file's persistent property for a second time
try {
file.setSessionProperty(key, persistentProperty);
} catch (Exception ex) {
// Ignore exception
}
return persistentProperty;
}
}
return null;
}
public static void setCachedContentTypeId(IFile file, String contentTypeId) {
// Encode content type id and cache it
internalSetCachedContentTypeId(file, contentTypeId != null ? contentTypeId : NO_CONTENT_TYPE_ID);
}
private static void internalSetCachedContentTypeId(IFile file, String rawContentTypeId) {
if (file != null && file.isAccessible()) {
// The resource property key for the file's content type id
QualifiedName key = ExtendedPlatform.toQualifedName(IExtendedPlatformConstants.RESOURCE_PROPERTY_CONTENT_TYPE_ID);
// Cache content type id as session property on underlying file
/*
* !! Important Note !! Exclude project description (.project) files from content type caching with session
* properties because this would entail resource change notifications for those files. However,
* IResourceChangeListeners might interpret such notifications as a change of a project's nature, its linked
* resources, or other and trigger unintended and inappropriate operations behind.
*/
if (!isProjectDescriptionFile(file)) {
try {
file.setSessionProperty(key, rawContentTypeId);
} catch (Exception ex) {
// Ignore exception
}
}
}
}
public static void persistContentTypeIdProperties(final IProject project, final boolean includeReferencedProjects, boolean async,
IProgressMonitor monitor) {
if (async) {
Job job = new Job(Messages.job_persistingContentTypeIdProperties) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
runPersistContentTypeIdProperties(ExtendedPlatform.getAllFiles(project, includeReferencedProjects), monitor);
return Status.OK_STATUS;
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
}
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
};
job.setRule(createModifySchedulingRule(project));
job.setPriority(Job.BUILD);
job.setSystem(true);
job.schedule();
} else {
try {
runPersistContentTypeIdProperties(ExtendedPlatform.getAllFiles(project, includeReferencedProjects), monitor);
} catch (OperationCanceledException ex) {
// Ignore exception
}
}
}
public static void persistContentTypeIdProperties(final Collection<IFile> files, boolean async, IProgressMonitor monitor) {
if (async) {
Job job = new Job(Messages.job_persistingContentTypeIdProperties) {
@Override
protected IStatus run(IProgressMonitor monitor) {
try {
runPersistContentTypeIdProperties(files, monitor);
return Status.OK_STATUS;
} catch (OperationCanceledException ex) {
return Status.CANCEL_STATUS;
} catch (Exception ex) {
return StatusUtil.createErrorStatus(Activator.getDefault(), ex);
}
}
@Override
public boolean belongsTo(Object family) {
return IExtendedPlatformConstants.FAMILY_LONG_RUNNING.equals(family);
}
};
job.setRule(createModifySchedulingRule(files));
job.setPriority(Job.BUILD);
job.setSystem(true);
job.schedule();
} else {
try {
runPersistContentTypeIdProperties(files, monitor);
} catch (OperationCanceledException ex) {
// Ignore exception
}
}
}
private static void runPersistContentTypeIdProperties(Collection<IFile> files, IProgressMonitor monitor) throws OperationCanceledException {
SubMonitor progress = SubMonitor.convert(monitor, files.size());
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
QualifiedName key = ExtendedPlatform.toQualifedName(IExtendedPlatformConstants.RESOURCE_PROPERTY_CONTENT_TYPE_ID);
for (IFile file : files) {
progress.setTaskName(NLS.bind(Messages.task_persistingContentTypeIdPropertiesFor, file.getFullPath().toString()));
try {
if (file != null && file.isAccessible()) {
String persistentProperty = null;
try {
persistentProperty = file.getPersistentProperty(key);
} catch (CoreException ex) {
// Ignore exception
}
if (persistentProperty == null) {
Object sessionProperty = file.getSessionProperty(key);
if (sessionProperty instanceof String) {
String contentTypeId = (String) sessionProperty;
file.setPersistentProperty(key, contentTypeId);
}
}
}
} catch (ResourceException ex) {
PlatformLogUtil.logAsWarning(Activator.getDefault(), ex);
} catch (CoreException ex) {
PlatformLogUtil.logAsError(Activator.getDefault(), ex);
}
progress.worked(1);
if (progress.isCanceled()) {
throw new OperationCanceledException();
}
}
}
public static void removeCachedContentTypeId(IFile file) {
try {
if (file != null && file.isAccessible()) {
QualifiedName key = ExtendedPlatform.toQualifedName(IExtendedPlatformConstants.RESOURCE_PROPERTY_CONTENT_TYPE_ID);
if (file.getSessionProperty(key) != null) {
file.setSessionProperty(key, null);
}
if (file.getPersistentProperty(key) != null) {
file.setPersistentProperty(key, null);
}
}
} catch (CoreException ex) {
// Fail silently
}
}
/**
* Returns a set of file extensions supported by content-type with given id.
*
* @param contentTypeId
* The content-type id for which the supported file extensions are to be retrieved.
* @return The set of file extensions supported by content-type with given id or an empty collection if no such
* could be determined.
*/
public static Collection<String> getContentTypeFileExtensions(String contentTypeId) {
IContentType contentType = Platform.getContentTypeManager().getContentType(contentTypeId);
if (contentType != null) {
return Arrays.asList(contentType.getFileSpecs(IContentTypeSettings.FILE_EXTENSION_SPEC));
}
return Collections.emptySet();
}
/**
* Returns wether the given content type is applicable to the passed in file. Applicability will be checked based on
* the filename/extension. The contents of the file will not be considered which would induce a massive performance
* overhead.
*
* @param contentTypeId
* The identifier of the content type to check for applicability
* @param file
* The file to check for applicability
* @return true if the content is applicable false if file is null or a content with the specified ID cannot be
* found by the platform content type manager
* @see Platform#getContentTypeManager()
* @see IContentType#isAssociatedWith(String)
*/
public static boolean isContentTypeApplicable(String contentTypeId, IFile file) {
if (file == null) {
return false;
}
IContentType contentType = Platform.getContentTypeManager().getContentType(contentTypeId);
if (contentType == null) {
return false;
}
return contentType.isAssociatedWith(file.getName());
}
/**
* Converts the specified {@linkplain String name} into a {@linkplain QualifiedName qualified name}.
*
* @param name
* The name to convert.
* @return The qualified name resulting from the conversion of the given {@link String name}.
* @since 0.7.0
*/
public static QualifiedName toQualifedName(String name) {
QualifiedName key;
int dot = name.lastIndexOf('.');
if (dot != -1) {
key = new QualifiedName(name.substring(0, dot), name.substring(dot + 1));
} else {
key = new QualifiedName(null, name);
}
return key;
}
/**
* Adds specified {@link IProjectNature nature} to given {@link IProject project}.
*
* @param project
* The {@link IProject project} to be handled.
* @param natureId
* The id of the {@link IProjectNature project nature} to be added.
* @param monitor
* A {@link IProgressMonitor progress monitor}, or <code>null</code> if progress reporting is not
* desired.
* @throws CoreException
* If the {@link IProject project} does not exist or is not open.
*/
public static void addNature(IProject project, String natureId, IProgressMonitor monitor) throws CoreException {
Assert.isNotNull(project);
if (natureId == null || natureId.length() == 0) {
return;
}
IProjectDescription description = project.getDescription();
String[] previousNatures = description.getNatureIds();
for (String element : previousNatures) {
if (natureId.equals(element)) {
return;
}
}
String[] newNatures = new String[previousNatures.length + 1];
System.arraycopy(previousNatures, 0, newNatures, 0, previousNatures.length);
newNatures[previousNatures.length] = natureId;
description.setNatureIds(newNatures);
project.setDescription(description, monitor);
}
/**
* Removes specified {@link IProjectNature nature} from given {@link IProject project}.
*
* @param project
* The {@link IProject project} to be handled.
* @param natureId
* The id of the {@link IProjectNature project nature} to be removed.
* @param monitor
* A {@link IProgressMonitor progress monitor}, or <code>null</code> if progress reporting is not
* desired.
* @throws CoreException
* If the {@link IProject project} does not exist or is not open.
*/
public static void removeNature(IProject project, String natureId, IProgressMonitor monitor) throws CoreException {
Assert.isNotNull(project);
IProjectDescription description = project.getDescription();
String[] previousNatures = description.getNatureIds();
List<String> newNatures = new ArrayList<String>(Arrays.asList(previousNatures));
newNatures.remove(natureId);
description.setNatureIds(newNatures.toArray(new String[newNatures.size()]));
project.setDescription(description, monitor);
}
/**
* Closes a stream and ignores any resulting exception. This is useful when doing stream cleanup in a finally block
* where secondary exceptions are not worth logging.
*/
public static void safeClose(InputStream in) {
try {
if (in != null) {
in.close();
}
} catch (IOException e) {
// ignore
}
}
/**
* Closes a stream and ignores any resulting exception. This is useful when doing stream cleanup in a finally block
* where secondary exceptions are not worth logging.
*/
public static void safeClose(OutputStream out) {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
// ignore
}
}
/**
* Performs a full garbage collection to free heap memory space.
* <p>
* Runs garbage collector in a separate thread to be sure that it can reclaim heap space of objects that have been
* disposed in the current thread.
*/
public static final void performGarbageCollection() {
Job job = new Job(Messages.job_performingGarbageCollection) {
@Override
protected IStatus run(IProgressMonitor monitor) {
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
SubMonitor progress = SubMonitor.convert(monitor, 3);
// Asynchronous garbage collector might already run
System.gc();
progress.worked(1);
// To make sure it does a full GC call it twice
System.gc();
progress.worked(1);
// Let the finalizer finish its work and remove objects from its queue
System.runFinalization();
progress.worked(1);
return Status.OK_STATUS;
}
};
job.setPriority(Job.SHORT);
job.setSystem(true);
job.schedule();
}
/**
* Checks wether the resource is in-sync with the file system and updates its out-of-sync marker accordingly. If the
* resource is out-of-sync a problem marker is created otherwise any previously existing markers are removed. This
* method will not synchronize the resource.
*
* @param markerJob
* the marker job that will be used to asynchronously create or delete markers
* @param resource
* the resource to check
* @return true if the resource is in-sync false otherwise.
* @see IResourceSyncMarker#RESOURCE_SYNC_PROBLEM
*/
public static boolean isSynchronized(IResource resource) {
if (resource.isSynchronized(IResource.DEPTH_ZERO)) {
// remove out-of-sync markers that may have been created previously
MarkerJob.INSTANCE.addDeleteMarkerTask(resource, IResourceSyncMarker.RESOURCE_SYNC_PROBLEM);
return true;
} else {
// create an out-of-sync marker warning the user
MarkerJob.INSTANCE.addCreateMarkerTask(resource, IResourceSyncMarker.RESOURCE_SYNC_PROBLEM, IMarker.SEVERITY_WARNING,
NLS.bind(Messages.warning_resourceIsOutOfSync, resource.getFullPath()));
return false;
}
}
}