| /******************************************************************************* |
| * Copyright (c) 2000, 2015 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Red Hat Incorporated - loadProjectDescription(InputStream) |
| * Serge Beauchamp (Freescale Semiconductor) - [252996] add resource filtering |
| * Serge Beauchamp (Freescale Semiconductor) - [229633] Group and Project Path Variable Support |
| * Broadcom Corporation - ongoing development |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.*; |
| import java.util.concurrent.CopyOnWriteArrayList; |
| import java.util.function.Predicate; |
| import org.eclipse.core.filesystem.EFS; |
| import org.eclipse.core.filesystem.IFileStore; |
| import org.eclipse.core.filesystem.URIUtil; |
| import org.eclipse.core.internal.events.*; |
| import org.eclipse.core.internal.localstore.FileSystemResourceManager; |
| import org.eclipse.core.internal.properties.IPropertyManager; |
| import org.eclipse.core.internal.properties.PropertyManager2; |
| import org.eclipse.core.internal.refresh.RefreshManager; |
| import org.eclipse.core.internal.resources.ComputeProjectOrder.Digraph; |
| import org.eclipse.core.internal.resources.ComputeProjectOrder.VertexOrder; |
| import org.eclipse.core.internal.utils.*; |
| import org.eclipse.core.internal.watson.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.resources.team.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.jobs.*; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Bundle; |
| import org.xml.sax.InputSource; |
| |
| /** |
| * The workspace class is the monolithic nerve center of the resources plugin. |
| * All interesting functionality stems from this class. |
| * </p> |
| * <p> |
| * The lifecycle of the resources plugin is encapsulated by the {@link #open(IProgressMonitor)} |
| * and {@link #close(IProgressMonitor)} methods. A closed workspace is completely |
| * unusable - any attempt to access or modify interesting workspace state on a closed |
| * workspace will fail. |
| * </p> |
| * <p> |
| * All modifications to the workspace occur within the context of a workspace operation. |
| * A workspace operation is implemented using the following sequence: |
| * <pre> |
| * try { |
| * prepareOperation(...); |
| * //check preconditions |
| * beginOperation(...); |
| * //perform changes |
| * } finally { |
| * endOperation(...); |
| * } |
| * </pre> |
| * Workspace operations can be nested arbitrarily. A "top level" workspace operation |
| * is an operation that is not nested within another workspace operation in the current |
| * thread. |
| * See the javadoc of {@link #prepareOperation(ISchedulingRule, IProgressMonitor)}, |
| * {@link #beginOperation(boolean)}, and {@link #endOperation(ISchedulingRule, boolean)} |
| * for more details. |
| * </p> |
| * <p> |
| * Major areas of functionality are farmed off to various manager classes. Open a |
| * type hierarchy on {@link IManager} to see all the different managers. Each |
| * manager is typically referenced three times in this class: Once in {@link #startup(IProgressMonitor)} |
| * when it is instantiated, once in {@link #shutdown(IProgressMonitor)} when it |
| * is destroyed, and once in a manager accessor method. |
| * </p> |
| */ |
| public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants { |
| public static final boolean caseSensitive = Platform.OS_MACOSX.equals(Platform.getOS()) ? false : new java.io.File("a").compareTo(new java.io.File("A")) != 0; //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| /** |
| * Work manager should never be accessed directly because accessor |
| * asserts that workspace is still open. |
| */ |
| protected WorkManager _workManager; |
| protected AliasManager aliasManager; |
| protected BuildManager buildManager; |
| protected volatile IBuildConfiguration[] buildOrder = null; |
| protected volatile Digraph<IBuildConfiguration> buildOrderGraph; |
| protected JobGroup buildJobGroup; |
| protected CharsetManager charsetManager; |
| protected ContentDescriptionManager contentDescriptionManager; |
| /** indicates if the workspace crashed in a previous session */ |
| protected boolean crashed = false; |
| protected final IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this); |
| protected WorkspacePreferences description; |
| protected FileSystemResourceManager fileSystemManager; |
| protected final CopyOnWriteArrayList<ILifecycleListener> lifecycleListeners = new CopyOnWriteArrayList<>(); |
| protected LocalMetaArea localMetaArea; |
| /** |
| * Helper class for performing validation of resource names and locations. |
| */ |
| protected final LocationValidator locationValidator = new LocationValidator(this); |
| protected MarkerManager markerManager; |
| /** |
| * The currently installed Move/Delete hook. |
| */ |
| protected IMoveDeleteHook moveDeleteHook = null; |
| protected NatureManager natureManager; |
| protected FilterTypeManager filterManager; |
| protected long nextMarkerId = 0; |
| protected long nextNodeId = 1; |
| |
| protected NotificationManager notificationManager; |
| protected boolean openFlag = false; |
| protected ElementTree operationTree; // tree at the start of the current operation |
| protected PathVariableManager pathVariableManager; |
| protected IPropertyManager propertyManager; |
| |
| protected RefreshManager refreshManager; |
| |
| /** |
| * Scheduling rule factory. This field is null if the factory has not been used |
| * yet. The accessor method should be used rather than accessing this field |
| * directly. |
| */ |
| private IResourceRuleFactory ruleFactory; |
| |
| protected SaveManager saveManager; |
| /** |
| * File modification validation. If it is true and validator is null, we try/initialize |
| * validator first time through. If false, there is no validator. |
| */ |
| protected boolean shouldValidate = true; |
| |
| /** |
| * Job that performs periodic string pool canonicalization. |
| */ |
| private StringPoolJob stringPoolJob; |
| |
| /** |
| * The synchronizer |
| */ |
| protected Synchronizer synchronizer; |
| |
| /** |
| * The currently installed team hook. |
| */ |
| protected TeamHook teamHook = null; |
| |
| /** |
| * The workspace tree. The tree is an in-memory representation |
| * of the resources that make up the workspace. The tree caches |
| * the structure and state of files and directories on disk (their existence |
| * and last modified times). When external parties make changes to |
| * the files on disk, this representation becomes out of sync. A local refresh |
| * reconciles the state of the files on disk with this tree (@link {@link IResource#refreshLocal(int, IProgressMonitor)}). |
| * The tree is also used to store metadata associated with resources in |
| * the workspace (markers, properties, etc). |
| * |
| * While the ElementTree data structure can handle both concurrent |
| * reads and concurrent writes, write access to the tree is governed |
| * by {@link WorkManager}. |
| */ |
| protected volatile ElementTree tree; |
| |
| /** |
| * This field is used to control access to the workspace tree during |
| * resource change notifications. It tracks which thread, if any, is |
| * in the middle of a resource change notification. This is used to cause |
| * attempts to modify the workspace during notifications to fail. |
| */ |
| protected volatile Thread treeLocked = null; |
| |
| /** |
| * The currently installed file modification validator. |
| */ |
| protected IFileModificationValidator validator = null; |
| |
| /** |
| * Data structure for holding the multi-part outcome of |
| * <code>IWorkspace.computeProjectBuildConfigOrder</code>. |
| * <p> |
| * This class is not intended to be instantiated by clients. |
| * </p> |
| * |
| * @see Workspace#computeProjectBuildConfigOrder(IBuildConfiguration[]) |
| * @since 3.7 |
| */ |
| public static final class ProjectBuildConfigOrder { |
| /** |
| * Creates an instance with the given values. |
| * <p> |
| * This class is not intended to be instantiated by clients. |
| * </p> |
| * |
| * @param buildConfigurations initial value of <code>buildConfigurations</code> field |
| * @param hasCycles initial value of <code>hasCycles</code> field |
| * @param knots initial value of <code>knots</code> field |
| */ |
| public ProjectBuildConfigOrder(IBuildConfiguration[] buildConfigurations, boolean hasCycles, IBuildConfiguration[][] knots) { |
| this.buildConfigurations = buildConfigurations; |
| this.hasCycles = hasCycles; |
| this.knots = knots; |
| } |
| |
| /** |
| * A list of project buildConfigs ordered so as to honor the build configuration reference |
| * relationships between these project buildConfigs wherever possible. The elements |
| * are a subset of the ones passed as the <code>buildConfigurations</code> |
| * parameter to <code>IWorkspace.computeProjectOrder</code>, where |
| * inaccessible (closed or non-existent) projects have been omitted. |
| */ |
| public IBuildConfiguration[] buildConfigurations; |
| /** |
| * Indicates whether any of the accessible project buildConfigs in |
| * <code>buildConfigurations</code> are involved in non-trivial cycles. |
| * <code>true</code> if the reference graph contains at least |
| * one cycle involving two or more of the project buildConfigs in |
| * <code>buildConfigurations</code>, and <code>false</code> if none of the |
| * project buildConfigs in <code>buildConfigurations</code> are involved in cycles. |
| */ |
| public boolean hasCycles; |
| /** |
| * A list of knots in the reference graph. This list is empty if |
| * the reference graph does not contain cycles. If the |
| * reference graph contains cycles, each element is a knot of two or |
| * more accessible project buildConfigs from <code>buildConfigurations</code> that are |
| * involved in a cycle of mutually dependent references. |
| */ |
| public IBuildConfiguration[][] knots; |
| } |
| |
| // Comparator used to provide a stable ordering of project buildConfigs |
| private static class BuildConfigurationComparator implements Comparator<IBuildConfiguration> { |
| public BuildConfigurationComparator() { |
| } |
| |
| @Override |
| public int compare(IBuildConfiguration px, IBuildConfiguration py) { |
| int cmp = py.getProject().getName().compareTo(px.getProject().getName()); |
| if (cmp == 0) |
| cmp = py.getName().compareTo(px.getName()); |
| return cmp; |
| } |
| } |
| |
| /** |
| * Deletes all the files and directories from the given root down (inclusive). |
| * Returns false if we could not delete some file or an exception occurred |
| * at any point in the deletion. |
| * Even if an exception occurs, a best effort is made to continue deleting. |
| */ |
| public static boolean clear(java.io.File root) { |
| IFileStore fileStore = EFS.getLocalFileSystem().fromLocalFile(root); |
| try { |
| fileStore.delete(EFS.NONE, new NullProgressMonitor()); |
| } catch (CoreException e) { |
| return false; |
| } |
| return true; |
| } |
| |
| public static WorkspaceDescription defaultWorkspaceDescription() { |
| return new WorkspaceDescription("Workspace"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Returns true if the object at the specified position has any |
| * other copy in the given array. |
| */ |
| private static boolean isDuplicate(Object[] array, int position) { |
| if (array == null || position >= array.length) |
| return false; |
| for (int j = position - 1; j >= 0; j--) |
| if (array[j].equals(array[position])) |
| return true; |
| return false; |
| } |
| |
| public Workspace() { |
| super(); |
| localMetaArea = new LocalMetaArea(); |
| tree = new ElementTree(); |
| /* tree should only be modified during operations */ |
| tree.immutable(); |
| treeLocked = Thread.currentThread(); |
| tree.setTreeData(newElement(IResource.ROOT)); |
| } |
| |
| /** |
| * Indicates that a build is about to occur. Broadcasts the necessary |
| * deltas before the build starts. Note that this will cause POST_BUILD |
| * to be automatically done at the end of the operation in which |
| * the build occurs. |
| */ |
| protected void aboutToBuild(Object source, int trigger) { |
| //fire a POST_CHANGE first to ensure everyone is up to date before firing PRE_BUILD |
| broadcastPostChange(); |
| broadcastBuildEvent(source, IResourceChangeEvent.PRE_BUILD, trigger); |
| } |
| |
| /** |
| * Adds a listener for internal workspace lifecycle events. There is no way to |
| * remove lifecycle listeners. |
| */ |
| public void addLifecycleListener(ILifecycleListener listener) { |
| lifecycleListeners.addIfAbsent(listener); |
| } |
| |
| @Override |
| public void addResourceChangeListener(IResourceChangeListener listener) { |
| notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE); |
| } |
| |
| @Override |
| public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) { |
| notificationManager.addListener(listener, eventMask); |
| } |
| |
| /** |
| * @deprecated Use {@link #addSaveParticipant(String, ISaveParticipant)} instead |
| */ |
| @Deprecated |
| @Override |
| public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException { |
| Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ |
| Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ |
| return saveManager.addParticipant(plugin.getBundle().getSymbolicName(), participant); |
| } |
| |
| @Override |
| public ISavedState addSaveParticipant(String pluginId, ISaveParticipant participant) throws CoreException { |
| Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ |
| Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$ |
| return saveManager.addParticipant(pluginId, participant); |
| } |
| |
| public void beginOperation(boolean createNewTree) throws CoreException { |
| WorkManager workManager = getWorkManager(); |
| workManager.incrementNestedOperations(); |
| if (!workManager.isBalanced()) |
| Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$ |
| if (workManager.getPreparedOperationDepth() > 1) { |
| if (createNewTree && tree.isImmutable()) |
| newWorkingTree(); |
| return; |
| } |
| // stash the current tree as the basis for this operation. |
| operationTree = tree; |
| if (createNewTree && tree.isImmutable()) |
| newWorkingTree(); |
| } |
| |
| public void broadcastBuildEvent(Object source, int type, int buildTrigger) { |
| ResourceChangeEvent event = new ResourceChangeEvent(source, type, buildTrigger, null); |
| notificationManager.broadcastChanges(tree, event, false); |
| } |
| |
| /** |
| * Broadcasts an internal workspace lifecycle event to interested |
| * internal listeners. |
| */ |
| protected void broadcastEvent(LifecycleEvent event) throws CoreException { |
| for (ILifecycleListener listener : lifecycleListeners) |
| listener.handleEvent(event); |
| } |
| |
| public void broadcastPostChange() { |
| ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null); |
| notificationManager.broadcastChanges(tree, event, true); |
| } |
| |
| /** |
| * Add all IBuildConfigurations reachable from config to the configs collection. |
| * @param configs collection of configurations to extend |
| * @param config config to find reachable configurations to. |
| */ |
| private void recursivelyAddBuildConfigs(Collection/*<IBuildConfiguration>*/<IBuildConfiguration> configs, IBuildConfiguration config) { |
| try { |
| IBuildConfiguration[] referenced = config.getProject().getReferencedBuildConfigs(config.getName(), false); |
| for (IBuildConfiguration element : referenced) { |
| if (configs.contains(element)) |
| continue; |
| configs.add(element); |
| recursivelyAddBuildConfigs(configs, element); |
| } |
| } catch (CoreException e) { |
| // Not possible, we've checked that the project + configuration are accessible. |
| Assert.isTrue(false); |
| } |
| } |
| |
| @Override |
| public void build(int trigger, IProgressMonitor monitor) throws CoreException { |
| buildInternal(EMPTY_BUILD_CONFIG_ARRAY, trigger, true, monitor); |
| } |
| |
| @Override |
| public void build(IBuildConfiguration[] configs, int trigger, boolean buildReferences, IProgressMonitor monitor) throws CoreException { |
| if (configs.length == 0) |
| return; |
| buildInternal(configs, trigger, buildReferences, monitor); |
| } |
| |
| /** |
| * Build the passed in configurations or the whole workspace. |
| * @param requestedConfigs to build or EMPTY_BUILD_CONFIG_ARRAY for the whole workspace |
| * @param trigger build trigger |
| * @param buildReferences transitively build referenced build configurations |
| */ |
| private void buildInternal(IBuildConfiguration[] requestedConfigs, int trigger, boolean buildReferences, |
| IProgressMonitor monitor) throws CoreException { |
| |
| |
| // Bug 343256 use a relaxed scheduling rule if the config we're building uses a relaxed rule. |
| // Otherwise fall-back to WR. |
| // PRE + POST_BUILD, and the build itself are allowed to modify resources, so require the current thread's scheduling rule |
| // to either contain the WR or be null. Therefore, if not null, ensure it contains the WR rule... |
| final boolean noEnclosingRule = Job.getJobManager().currentRule() == null; |
| boolean relaxed = noEnclosingRule && requestedConfigs.length > 0 && allRelaxed(requestedConfigs, trigger); |
| final ISchedulingRule notificationRule = getRuleFactory().buildRule(); |
| ISchedulingRule currentRule = null; |
| boolean buildParallel = noEnclosingRule && getDescription().getMaxConcurrentBuilds() > 1 && getDescription().getBuildOrder() == null; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, 100); |
| try { |
| try { |
| |
| // Must run the PRE_BUILD with the WRule held before acquiring WS lock |
| // Can remove this if we run notifications without the WS lock held: bug 249951 |
| prepareOperation(notificationRule, subMonitor.newChild(1)); |
| currentRule = notificationRule; |
| beginOperation(true); |
| aboutToBuild(this, trigger); |
| } finally { |
| if (relaxed) { |
| endOperation(currentRule, false); |
| prepareOperation(null, subMonitor.newChild(1)); |
| currentRule = null; |
| beginOperation(false); |
| } |
| } |
| |
| IStatus result; |
| try { |
| // Calculate the build-order having called the pre-build notification (which may |
| // change build order) |
| // If configs == EMPTY_BUILD_CONFIG_ARRAY => This is a full workspace build. |
| IBuildConfiguration[] allConfigs = requestedConfigs; |
| Digraph<IBuildConfiguration> buildGraph = null; |
| if (allConfigs == EMPTY_BUILD_CONFIG_ARRAY) { |
| if (trigger != IncrementalProjectBuilder.CLEAN_BUILD) { |
| if (getDescription().getBuildOrder() != null) { |
| allConfigs = getBuildOrder(); |
| } else { |
| buildGraph = getBuildGraph(); |
| } |
| } else { |
| // clean all accessible configurations |
| List<IBuildConfiguration> configArr = new ArrayList<>(); |
| IProject[] prjs = getRoot().getProjects(); |
| for (IProject prj : prjs) |
| if (prj.isAccessible()) |
| configArr.addAll(Arrays.asList(prj.getBuildConfigs())); |
| allConfigs = configArr.toArray(new IBuildConfiguration[configArr.size()]); |
| } |
| } else { |
| // Order the passed in build configurations + resolve references if requested |
| Set<IBuildConfiguration> refsList = new HashSet<>(); |
| for (IBuildConfiguration config : allConfigs) { |
| // Check project + build configuration are accessible. |
| if (!config.getProject().isAccessible() |
| || !config.getProject().hasBuildConfig(config.getName())) |
| continue; |
| refsList.add(config); |
| // Find transitive closure of referenced project buildConfigs |
| if (buildReferences) |
| recursivelyAddBuildConfigs(refsList, config); |
| } |
| |
| // Order the referenced project buildConfigs |
| buildGraph = computeProjectBuildConfigOrderGraph(refsList); |
| } |
| if (buildGraph != null) { |
| buildGraph.freeze(); |
| if (Policy.DEBUG_BUILD_CYCLE && buildGraph.containsCycles()) { |
| List<IBuildConfiguration[]> nonTrivialComponents = buildGraph.nonTrivialComponents(); |
| for (IBuildConfiguration[] iBuildConfigurations : nonTrivialComponents) { |
| Policy.debug("Cycle: " + Arrays.toString(iBuildConfigurations)); //$NON-NLS-1$ |
| } |
| } |
| allConfigs = ComputeProjectOrder.computeVertexOrder(buildGraph, IBuildConfiguration.class).vertexes; |
| } |
| |
| buildParallel &= (buildGraph != null && buildGraph.vertexList.size() > 1); |
| if (buildParallel) { |
| relaxed = noEnclosingRule && allRelaxed(allConfigs, trigger); |
| buildParallel &= relaxed; |
| } |
| if (buildParallel) { |
| endOperation(currentRule, false); // operation needs to be ended to allow concurrent edits |
| currentRule = null; |
| result = getBuildManager().buildParallel(buildGraph, requestedConfigs, trigger, getBuildJobGroup(), |
| subMonitor.newChild(97)); |
| } else { |
| result = getBuildManager().build(allConfigs, requestedConfigs, trigger, |
| subMonitor.newChild(97)); |
| } |
| } finally { |
| // Run the POST_BUILD with the WRule held |
| if (relaxed) { |
| if (!buildParallel) { |
| endOperation(currentRule, false); |
| } |
| prepareOperation(notificationRule, subMonitor.newChild(1)); |
| currentRule = notificationRule; |
| beginOperation(false); |
| } |
| // must fire POST_BUILD if PRE_BUILD has occurred |
| broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger); |
| } |
| if (!result.isOK()) |
| throw new ResourceException(result); |
| } finally { |
| subMonitor.done(); |
| // building may close the tree, but we are still inside an operation so open it |
| if (tree.isImmutable()) |
| newWorkingTree(); |
| // Rule will be the build-rule from the POST_BUILD refresh |
| endOperation(currentRule, false); |
| } |
| } |
| |
| private JobGroup getBuildJobGroup() { |
| if (buildJobGroup == null || buildJobGroup.getMaxThreads() != description.getMaxConcurrentBuilds()) { |
| buildJobGroup = new JobGroup(getClass().getName(), description.getMaxConcurrentBuilds(), 0); |
| } |
| return buildJobGroup; |
| } |
| |
| private boolean allRelaxed(IBuildConfiguration[] requestedConfigs, int trigger) { |
| return Arrays.stream(requestedConfigs).map(cfg -> getBuildManager().getRule(cfg, trigger, null, null)).allMatch(this::isRelaxedRule); |
| } |
| |
| boolean isRelaxedRule(ISchedulingRule rule) { |
| return rule == null || !rule.contains(getRoot()); |
| } |
| |
| /** |
| * Returns whether creating executable extensions is acceptable |
| * at this point in time. In particular, returns <code>false</code> |
| * when the system bundle is shutting down, which only occurs |
| * when the entire framework is exiting. |
| */ |
| private boolean canCreateExtensions() { |
| return Platform.getBundle("org.eclipse.osgi").getState() != Bundle.STOPPING; //$NON-NLS-1$ |
| } |
| |
| @Override |
| public void checkpoint(boolean build) { |
| try { |
| final ISchedulingRule rule = getWorkManager().getNotifyRule(); |
| try { |
| prepareOperation(rule, null); |
| beginOperation(true); |
| broadcastPostChange(); |
| } finally { |
| endOperation(rule, build); |
| } |
| } catch (CoreException e) { |
| Policy.log(e.getStatus().getSeverity(), e.getMessage(), e); |
| } |
| } |
| |
| /** |
| * Closes this workspace; ignored if this workspace is not open. |
| * The state of this workspace is not saved before the workspace |
| * is shut down. |
| * <p> |
| * If the workspace was saved immediately prior to closing, |
| * it will have the same set of projects |
| * (open or closed) when reopened for a subsequent session. |
| * Otherwise, closing a workspace may lose some or all of the |
| * changes made since the last save or snapshot. |
| * </p> |
| * <p> |
| * Note that session properties are discarded when a workspace is closed. |
| * </p> |
| * <p> |
| * This method is long-running; progress and cancellation are provided |
| * by the given progress monitor. |
| * </p> |
| * |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @exception CoreException if the workspace could not be shutdown. |
| */ |
| public void close(IProgressMonitor monitor) throws CoreException { |
| //nothing to do if the workspace failed to open |
| if (!isOpen()) |
| return; |
| try { |
| String msg = Messages.resources_closing_0; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, msg, 20); |
| subMonitor.subTask(msg); |
| //this operation will never end because the world is going away |
| try { |
| stringPoolJob.cancel(); |
| //shutdown save manager now so a last snapshot can be taken before we close |
| //note: you can't call #save() from within a nested operation |
| saveManager.shutdown(null); |
| saveManager.reportSnapshotRequestor(); |
| prepareOperation(getRoot(), subMonitor.newChild(1)); |
| //shutdown notification first to avoid calling third parties during shutdown |
| notificationManager.shutdown(null); |
| beginOperation(true); |
| IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| subMonitor.setWorkRemaining(projects.length + 2); |
| for (IProject project : projects) { |
| //notify managers of closing so they can cleanup |
| broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, project)); |
| subMonitor.worked(1); |
| } |
| //empty the workspace tree so we leave in a clean state |
| deleteResource(getRoot()); |
| openFlag = false; |
| // endOperation not needed here |
| } finally { |
| // Shutdown needs to be executed regardless of failures |
| shutdown(subMonitor.newChild(2, SubMonitor.SUPPRESS_SUBTASK)); |
| } |
| } finally { |
| //release the scheduling rule to be a good job citizen |
| Job.getJobManager().endRule(getRoot()); |
| } |
| } |
| |
| /** |
| * Computes the global total ordering of all open projects in the |
| * workspace based on project references. If an existing and open project P |
| * references another existing and open project Q also included in the list, |
| * then Q should come before P in the resulting ordering. Closed and non- |
| * existent projects are ignored, and will not appear in the result. References |
| * to non-existent or closed projects are also ignored, as are any self- |
| * references. |
| * <p> |
| * When there are choices, the choice is made in a reasonably stable way. For |
| * example, given an arbitrary choice between two projects, the one with the |
| * lower collating project name is usually selected. |
| * </p> |
| * <p> |
| * When the project reference graph contains cyclic references, it is |
| * impossible to honor all of the relationships. In this case, the result |
| * ignores as few relationships as possible. For example, if P2 references P1, |
| * P4 references P3, and P2 and P3 reference each other, then exactly one of the |
| * relationships between P2 and P3 will have to be ignored. The outcome will be |
| * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains |
| * complete details of any cycles present. |
| * </p> |
| * |
| * @return result describing the global project order |
| * @since 2.1 |
| */ |
| private VertexOrder<IProject> computeFullProjectOrder() { |
| // determine the full set of accessible projects in the workspace |
| // order the set in descending alphabetical order of project name |
| SortedSet<IProject> allAccessibleProjects = new TreeSet<>((px, py) -> py.getName().compareTo(px.getName())); |
| IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| // List<IProject[]> edges |
| List<IProject[]> edges = new ArrayList<>(allProjects.length); |
| for (IProject p : allProjects) { |
| Project project = (Project) p; |
| // ignore projects that are not accessible |
| if (!project.isAccessible()) |
| continue; |
| ProjectDescription desc = project.internalGetDescription(); |
| if (desc == null) |
| continue; |
| //obtain both static and dynamic project references |
| IProject[] refs = desc.getAllReferences(project, false); |
| allAccessibleProjects.add(project); |
| for (IProject ref : refs) { |
| // ignore self references and references to projects that are not accessible |
| if (ref.isAccessible() && !ref.equals(project)) |
| edges.add(new IProject[] {project, ref}); |
| } |
| } |
| return ComputeProjectOrder.computeVertexOrder(allAccessibleProjects, edges, IProject.class); |
| } |
| |
| /** |
| * Computes the global total ordering of all open projects' active buildConfigs in the |
| * workspace based on build configuration references. If an existing and open project's build config P |
| * references another existing and open project's build config Q, then Q should come before P |
| * in the resulting ordering. If a build config references a non-active build config it is |
| * added to the resulting ordered list. Closed and non-existent projects/buildConfigs are |
| * ignored, and will not appear in the result. References to non-existent or closed |
| * projects/buildConfigs are also ignored, as are any self-references. |
| * <p> |
| * When there are choices, the choice is made in a reasonably stable way. For |
| * example, given an arbitrary choice between two project buildConfigs, the one with the |
| * lower collating project name and build config name will appear earlier in the list. |
| * </p> |
| * <p> |
| * When the build configuration reference graph contains cyclic references, it is |
| * impossible to honor all of the relationships. In this case, the result |
| * ignores as few relationships as possible. For example, if P2 references P1, |
| * P4 references P3, and P2 and P3 reference each other, then exactly one of the |
| * relationships between P2 and P3 will have to be ignored. The outcome will be |
| * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains |
| * complete details of any cycles present. |
| * </p> |
| * |
| * @return result describing the global active build configuration order |
| */ |
| private VertexOrder<IBuildConfiguration> computeActiveBuildConfigOrder() { |
| Digraph<IBuildConfiguration> activeBuildConfigurationGraph = computeActiveBuildConfigGraph(); |
| return ComputeProjectOrder.computeVertexOrder(activeBuildConfigurationGraph, IBuildConfiguration.class); |
| } |
| |
| private Digraph<IBuildConfiguration> computeActiveBuildConfigGraph() { |
| // Determine the full set of accessible active project buildConfigs in the workspace, |
| // and all the accessible project buildConfigs that they reference. This forms a set |
| // of all the project buildConfigs that will be returned. |
| // Order the set in descending alphabetical order of project name then build config name, |
| // as a secondary sort applied after sorting based on references, to achieve a stable |
| // ordering. |
| SortedSet<IBuildConfiguration> allAccessibleBuildConfigs = new TreeSet<>(new BuildConfigurationComparator()); |
| |
| // For each project's active build config, perform a depth first search in the reference graph |
| // rooted at that build config. |
| // This generates the required subset of the reference graph that is required to order all |
| // the dependencies of the active project buildConfigs. |
| IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| List<IBuildConfiguration[]> edges = new ArrayList<>(allProjects.length); |
| |
| for (IProject allProject : allProjects) { |
| Project project = (Project) allProject; |
| // Ignore projects that are not accessible |
| if (!project.isAccessible()) |
| continue; |
| |
| // If the active build configuration hasn't already been explored |
| // perform a depth first search rooted at it |
| if (!allAccessibleBuildConfigs.contains(project.internalGetActiveBuildConfig())) { |
| allAccessibleBuildConfigs.add(project.internalGetActiveBuildConfig()); |
| Stack<IBuildConfiguration> stack = new Stack<>(); |
| stack.push(project.internalGetActiveBuildConfig()); |
| |
| while (!stack.isEmpty()) { |
| IBuildConfiguration buildConfiguration = stack.pop(); |
| |
| // Add all referenced buildConfigs from the current configuration |
| // (it is guaranteed to be accessible as it was pushed onto the stack) |
| Project subProject = (Project) buildConfiguration.getProject(); |
| IBuildConfiguration[] refs = subProject.internalGetReferencedBuildConfigs(buildConfiguration.getName(), false); |
| for (IBuildConfiguration ref : refs) { |
| // Ignore self references and references to projects that are not accessible |
| if (ref.equals(buildConfiguration)) |
| continue; |
| |
| // Add the referenced accessible configuration |
| edges.add(new IBuildConfiguration[] {buildConfiguration, ref}); |
| |
| // If we have already explored the referenced configuration, don't explore it again |
| if (allAccessibleBuildConfigs.contains(ref)) |
| continue; |
| |
| allAccessibleBuildConfigs.add(ref); |
| |
| // Push the referenced configuration onto the stack so that it is explored by the depth first search |
| stack.push(ref); |
| } |
| } |
| } |
| } |
| return ComputeProjectOrder.computeGraph(allAccessibleBuildConfigs, edges, IBuildConfiguration.class); |
| } |
| |
| /** |
| * Computes the global total ordering of all project buildConfigs in the workspace based |
| * on build config references. If an existing and open build config P |
| * references another existing and open project build config Q, then Q should come before P |
| * in the resulting ordering. Closed and non-existent projects/buildConfigs are |
| * ignored, and will not appear in the result. References to non-existent or closed |
| * projects/buildConfigs are also ignored, as are any self-references. |
| * <p> |
| * When there are choices, the choice is made in a reasonably stable way. For |
| * example, given an arbitrary choice between two project buildConfigs, the one with the |
| * lower collating project name and build config name will appear earlier in the list. |
| * </p> |
| * <p> |
| * When the build config reference graph contains cyclic references, it is |
| * impossible to honor all of the relationships. In this case, the result |
| * ignores as few relationships as possible. For example, if P2 references P1, |
| * P4 references P3, and P2 and P3 reference each other, then exactly one of the |
| * relationships between P2 and P3 will have to be ignored. The outcome will be |
| * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains |
| * complete details of any cycles present. |
| * </p> |
| * |
| * @return result describing the global project build configuration order |
| * @deprecated Use {@link #computeFullBuildConfigGraph()} instead |
| */ |
| @Deprecated |
| private VertexOrder<IBuildConfiguration> computeFullBuildConfigOrder() { |
| Digraph<IBuildConfiguration> graph = computeFullBuildConfigGraph(); |
| return ComputeProjectOrder.computeVertexOrder(graph, IBuildConfiguration.class); |
| } |
| |
| private Digraph<IBuildConfiguration> computeFullBuildConfigGraph() { |
| // Compute the order for all accessible project buildConfigs |
| SortedSet<IBuildConfiguration> allAccessibleBuildConfigurations = new TreeSet<>(new BuildConfigurationComparator()); |
| |
| IProject[] allProjects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| List<IBuildConfiguration[]> edges = new ArrayList<>(allProjects.length); |
| |
| for (IProject p : allProjects) { |
| Project project = (Project) p; |
| // Ignore projects that are not accessible |
| if (!project.isAccessible()) |
| continue; |
| |
| IBuildConfiguration[] configs = project.internalGetBuildConfigs(false); |
| for (IBuildConfiguration config : configs) { |
| allAccessibleBuildConfigurations.add(config); |
| IBuildConfiguration[] refs = project.internalGetReferencedBuildConfigs(config.getName(), false); |
| for (IBuildConfiguration ref : refs) { |
| // Ignore self references |
| if (ref.equals(config)) |
| continue; |
| |
| // Add the reference to the set of reachable configs + add an edge |
| allAccessibleBuildConfigurations.add(ref); |
| edges.add(new IBuildConfiguration[] {config, ref}); |
| } |
| } |
| } |
| return ComputeProjectOrder.computeGraph(allAccessibleBuildConfigurations, edges, IBuildConfiguration.class); |
| } |
| |
| private static ProjectOrder vertexOrderToProjectOrder(VertexOrder<IProject> order) { |
| IProject[] projects = new IProject[order.vertexes.length]; |
| System.arraycopy(order.vertexes, 0, projects, 0, order.vertexes.length); |
| IProject[][] knots = new IProject[order.knots.length][]; |
| for (int i = 0; i < order.knots.length; i++) { |
| knots[i] = new IProject[order.knots[i].length]; |
| System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); |
| } |
| return new ProjectOrder(projects, order.hasCycles, knots); |
| } |
| |
| private static ProjectBuildConfigOrder vertexOrderToProjectBuildConfigOrder(VertexOrder<IBuildConfiguration> order) { |
| IBuildConfiguration[] buildConfigs = new IBuildConfiguration[order.vertexes.length]; |
| System.arraycopy(order.vertexes, 0, buildConfigs, 0, order.vertexes.length); |
| IBuildConfiguration[][] knots = new IBuildConfiguration[order.knots.length][]; |
| for (int i = 0; i < order.knots.length; i++) { |
| knots[i] = new IBuildConfiguration[order.knots[i].length]; |
| System.arraycopy(order.knots[i], 0, knots[i], 0, order.knots[i].length); |
| } |
| return new ProjectBuildConfigOrder(buildConfigs, order.hasCycles, knots); |
| } |
| |
| @Deprecated |
| @Override |
| public IProject[][] computePrerequisiteOrder(IProject[] targets) { |
| return computePrerequisiteOrder1(targets); |
| } |
| |
| /* |
| * Compatible reimplementation of |
| * <code>IWorkspace.computePrerequisiteOrder</code> using |
| * <code>IWorkspace.computeProjectOrder</code>. |
| * |
| * @since 2.1 |
| */ |
| private IProject[][] computePrerequisiteOrder1(IProject[] projects) { |
| IWorkspace.ProjectOrder r = computeProjectOrder(projects); |
| if (!r.hasCycles) { |
| return new IProject[][] {r.projects, new IProject[0]}; |
| } |
| // when there are cycles, we need to remove all knotted projects from |
| // r.projects to form result[0] and merge all knots to form result[1] |
| // Set<IProject> bad |
| Set<IProject> bad = new HashSet<>(); |
| // Set<IProject> bad |
| Set<IProject> keepers = new HashSet<>(Arrays.asList(r.projects)); |
| for (IProject[] knot : r.knots) { |
| for (IProject project : knot) { |
| // keep only selected projects in knot |
| if (keepers.contains(project)) { |
| bad.add(project); |
| } |
| } |
| } |
| IProject[] result2 = new IProject[bad.size()]; |
| bad.toArray(result2); |
| // List<IProject> p |
| List<IProject> p = new LinkedList<>(); |
| p.addAll(Arrays.asList(r.projects)); |
| for (Iterator<IProject> it = p.listIterator(); it.hasNext();) { |
| IProject project = it.next(); |
| if (bad.contains(project)) { |
| // remove knotted projects from the main answer |
| it.remove(); |
| } |
| } |
| IProject[] result1 = new IProject[p.size()]; |
| p.toArray(result1); |
| return new IProject[][] {result1, result2}; |
| } |
| |
| @Override |
| public ProjectOrder computeProjectOrder(IProject[] projects) { |
| // Compute the full project order for all accessible projects |
| VertexOrder<IProject> fullProjectOrder = computeFullProjectOrder(); |
| |
| // Create a filter to remove all projects that are not in the list asked for |
| final Set<IProject> projectSet = new HashSet<>(projects.length); |
| projectSet.addAll(Arrays.asList(projects)); |
| Predicate<IProject> filter = vertex -> !projectSet.contains(vertex); |
| |
| // Filter the order and return it |
| return vertexOrderToProjectOrder(ComputeProjectOrder.filterVertexOrder(fullProjectOrder, filter, IProject.class)); |
| } |
| |
| /** |
| * Computes a total ordering of the given projects buildConfigs based on both static and |
| * dynamic project references. If an existing and open project's build configuratioin P references |
| * another existing and open project's configuration Q also included in the list, then Q |
| * should come before P in the resulting ordering. Closed and non-existent |
| * projects are ignored, and will not appear in the result. References to |
| * non-existent or closed projects/buildConfigs are also ignored, as are any |
| * self-references. The total ordering is always consistent with the global |
| * total ordering of all open projects' buildConfigs in the workspace. |
| * <p> |
| * When there are choices, the choice is made in a reasonably stable way. |
| * For example, given an arbitrary choice between two project buildConfigs, the one with |
| * the lower collating configuration name is usually selected. |
| * </p> |
| * <p> |
| * When the project reference graph contains cyclic references, it is |
| * impossible to honor all of the relationships. In this case, the result |
| * ignores as few relationships as possible. For example, if P2 references |
| * P1, P4 references P3, and P2 and P3 reference each other, then exactly |
| * one of the relationships between P2 and P3 will have to be ignored. The |
| * outcome will be either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result |
| * also contains complete details of any cycles present. |
| * </p> |
| * <p> |
| * This method is time-consuming and should not be called unnecessarily. |
| * There are a very limited set of changes to a workspace that could affect |
| * the outcome: creating, renaming, or deleting a project; opening or |
| * closing a project; deleting a build configuration; adding or removing a build configuration reference. |
| * </p> |
| * |
| * @param buildConfigs the build configurations to order |
| * @return result describing the build configuration order |
| * @since 3.7 |
| */ |
| public ProjectBuildConfigOrder computeProjectBuildConfigOrder(IBuildConfiguration[] buildConfigs) { |
| // Compute the full project order for all accessible projects |
| VertexOrder<IBuildConfiguration> fullBuildConfigOrder = computeFullBuildConfigOrder(); |
| |
| // Create a filter to remove all project buildConfigs that are not in the list asked for |
| final Set<IBuildConfiguration> projectConfigSet = new HashSet<>(buildConfigs.length); |
| projectConfigSet.addAll(Arrays.asList(buildConfigs)); |
| Predicate<IBuildConfiguration> filter = vertex -> !projectConfigSet.contains(vertex); |
| |
| // Filter the order and return it |
| return vertexOrderToProjectBuildConfigOrder(ComputeProjectOrder.filterVertexOrder(fullBuildConfigOrder, filter, IBuildConfiguration.class)); |
| } |
| |
| private Digraph<IBuildConfiguration> computeProjectBuildConfigOrderGraph(Collection<IBuildConfiguration> buildConfigs) { |
| Digraph<IBuildConfiguration> fullBuildConfigOrder = computeFullBuildConfigGraph(); |
| |
| // Create a filter to remove all project buildConfigs that are not in the list asked for |
| final Set<IBuildConfiguration> projectConfigSet = new HashSet<>(buildConfigs); |
| Predicate<IBuildConfiguration> filter = vertex -> !projectConfigSet.contains(vertex); |
| |
| // Filter the order and return it |
| return ComputeProjectOrder.buildFilteredDigraph(fullBuildConfigOrder, filter, IBuildConfiguration.class); |
| } |
| |
| @Override |
| public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| return copy(resources, destination, updateFlags, monitor); |
| } |
| |
| @Override |
| public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) |
| throws CoreException { |
| Assert.isLegal(resources != null); |
| |
| if (resources.length == 0) { |
| return Status.OK_STATUS; |
| } |
| // to avoid concurrent changes to this array |
| resources = resources.clone(); |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.resources_copying_0, resources.length); |
| IPath parentPath = null; |
| String message = Messages.resources_copyProblem; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null); |
| try { |
| prepareOperation(getRoot(), subMonitor); |
| beginOperation(true); |
| for (int i = 0; i < resources.length; i++) { |
| IResource resource = resources[i]; |
| if (resource == null || isDuplicate(resources, i)) { |
| subMonitor.split(1); |
| continue; |
| } |
| // test siblings |
| if (parentPath == null) |
| parentPath = resource.getFullPath().removeLastSegments(1); |
| if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { |
| // test copy requirements |
| try { |
| IPath destinationPath = destination.append(resource.getName()); |
| IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags); |
| if (requirements.isOK()) { |
| try { |
| resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1)); |
| } catch (CoreException e) { |
| status.merge(e.getStatus()); |
| } |
| } else { |
| status.merge(requirements); |
| } |
| } catch (CoreException e) { |
| status.merge(e.getStatus()); |
| } |
| } else { |
| message = NLS.bind(Messages.resources_notChild, resources[i].getFullPath(), parentPath); |
| status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message)); |
| } |
| subMonitor.worked(1); |
| } |
| } catch (OperationCanceledException e) { |
| getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| subMonitor.done(); |
| endOperation(getRoot(), true); |
| } |
| if (status.matches(IStatus.ERROR)) |
| throw new ResourceException(status); |
| return status.isOK() ? Status.OK_STATUS : (IStatus) status; |
| } |
| |
| protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { |
| copyTree(source, destination, depth, updateFlags, keepSyncInfo, false, source.getType() == IResource.PROJECT); |
| } |
| |
| private void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo, boolean moveResources, boolean movingProject) throws CoreException { |
| |
| // retrieve the resource at the destination if there is one (phantoms included). |
| // if there isn't one, then create a new handle based on the type that we are |
| // trying to copy |
| IResource destinationResource = getRoot().findMember(destination, true); |
| int destinationType; |
| if (destinationResource == null) { |
| if (source.getType() == IResource.FILE) |
| destinationType = IResource.FILE; |
| else if (destination.segmentCount() == 1) |
| destinationType = IResource.PROJECT; |
| else |
| destinationType = IResource.FOLDER; |
| destinationResource = newResource(destination, destinationType); |
| } else |
| destinationType = destinationResource.getType(); |
| |
| // create the resource at the destination |
| ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false); |
| if (destinationType != source.getType()) { |
| sourceInfo = (ResourceInfo) sourceInfo.clone(); |
| sourceInfo.setType(destinationType); |
| } |
| ResourceInfo newInfo = createResource(destinationResource, sourceInfo, false, true, keepSyncInfo); |
| // get/set the node id from the source's resource info so we can later put it in the |
| // info for the destination resource. This will help us generate the proper deltas, |
| // indicating a move rather than a add/delete |
| newInfo.setNodeId(sourceInfo.getNodeId()); |
| |
| // preserve local sync info but not location info |
| newInfo.setFlags(newInfo.getFlags() | (sourceInfo.getFlags() & M_LOCAL_EXISTS)); |
| newInfo.setFileStoreRoot(null); |
| |
| // forget content-related caching flags |
| newInfo.clear(M_CONTENT_CACHE); |
| |
| // update link locations in project descriptions |
| if (source.isLinked()) { |
| LinkDescription linkDescription; |
| URI sourceLocationURI = transferVariableDefinition(source, destinationResource, source.getLocationURI()); |
| if (((updateFlags & IResource.SHALLOW) != 0) || ((Resource) source).isUnderVirtual()) { |
| //for shallow move the destination is a linked resource with the same location |
| newInfo.set(ICoreConstants.M_LINK); |
| linkDescription = new LinkDescription(destinationResource, sourceLocationURI); |
| } else { |
| //for deep move the destination is not a linked resource |
| newInfo.clear(ICoreConstants.M_LINK); |
| linkDescription = null; |
| } |
| if (moveResources && !movingProject) { |
| if (((Project) source.getProject()).internalGetDescription().setLinkLocation(source.getProjectRelativePath(), null)) |
| ((Project) source.getProject()).writeDescription(updateFlags); |
| } |
| Project project = (Project) destinationResource.getProject(); |
| project.internalGetDescription().setLinkLocation(destinationResource.getProjectRelativePath(), linkDescription); |
| project.writeDescription(updateFlags); |
| newInfo.setFileStoreRoot(null); |
| } |
| |
| // update filters in project descriptions |
| if (source.getProject().exists() && source instanceof Container && ((Container) source).hasFilters()) { |
| Project sourceProject = (Project) source.getProject(); |
| LinkedList<FilterDescription> originalDescriptions = sourceProject.internalGetDescription().getFilter(source.getProjectRelativePath()); |
| LinkedList<FilterDescription> filterDescriptions = FilterDescription.copy(originalDescriptions, destinationResource); |
| if (moveResources && !movingProject) { |
| if (((Project) source.getProject()).internalGetDescription().setFilters(source.getProjectRelativePath(), null)) |
| ((Project) source.getProject()).writeDescription(updateFlags); |
| } |
| Project project = (Project) destinationResource.getProject(); |
| project.internalGetDescription().setFilters(destinationResource.getProjectRelativePath(), filterDescriptions); |
| project.writeDescription(updateFlags); |
| } |
| |
| // do the recursion. if we have a file then it has no members so return. otherwise |
| // recursively call this method on the container's members if the depth tells us to |
| if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE) |
| return; |
| if (depth == IResource.DEPTH_ONE) |
| depth = IResource.DEPTH_ZERO; |
| //copy .project file first if project is being copied, otherwise links won't be able to update description |
| boolean projectCopy = source.getType() == IResource.PROJECT && destinationType == IResource.PROJECT; |
| if (projectCopy) { |
| IResource dotProject = ((Project) source).findMember(IProjectDescription.DESCRIPTION_FILE_NAME); |
| if (dotProject != null) |
| copyTree(dotProject, destination.append(dotProject.getName()), depth, updateFlags, keepSyncInfo, moveResources, movingProject); |
| } |
| IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS | IContainer.INCLUDE_HIDDEN); |
| for (IResource element : children) { |
| String childName = element.getName(); |
| if (!projectCopy || !childName.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) { |
| IPath childPath = destination.append(childName); |
| copyTree(element, childPath, depth, updateFlags, keepSyncInfo, moveResources, movingProject); |
| } |
| } |
| } |
| |
| public URI transferVariableDefinition(IResource source, IResource dest, URI sourceURI) throws CoreException { |
| IPath srcLoc = source.getLocation(); |
| IPath srcRawLoc = source.getRawLocation(); |
| if ((srcLoc != null) && (srcRawLoc != null) && !srcLoc.equals(srcRawLoc)) { |
| // the location is variable relative |
| if (!source.getProject().equals(dest.getProject())) { |
| String variable = srcRawLoc.segment(0); |
| variable = copyVariable(source, dest, variable); |
| IPath newLocation = Path.fromPortableString(variable).append(srcRawLoc.removeFirstSegments(1)); |
| sourceURI = toURI(newLocation); |
| } else { |
| sourceURI = toURI(srcRawLoc); |
| } |
| } |
| return sourceURI; |
| } |
| |
| URI toURI(IPath path) { |
| if (path.isAbsolute()) |
| return org.eclipse.core.filesystem.URIUtil.toURI(path); |
| try { |
| return new URI(null, null, path.toPortableString(), null); |
| } catch (URISyntaxException e) { |
| return org.eclipse.core.filesystem.URIUtil.toURI(path); |
| } |
| } |
| |
| String copyVariable(IResource source, IResource dest, String variable) throws CoreException { |
| IPathVariableManager destPathVariableManager = dest.getPathVariableManager(); |
| IPathVariableManager srcPathVariableManager = source.getPathVariableManager(); |
| |
| IPath srcValue = URIUtil.toPath(srcPathVariableManager.getURIValue(variable)); |
| if (srcValue == null) // if the variable doesn't exist, return another |
| // variable that doesn't exist either |
| return PathVariableUtil.getUniqueVariableName(variable, dest); |
| IPath resolvedSrcValue = URIUtil.toPath(srcPathVariableManager.resolveURI(URIUtil.toURI(srcValue))); |
| |
| boolean variableExisted = false; |
| // look if the exact same variable exists |
| if (destPathVariableManager.isDefined(variable)) { |
| variableExisted = true; |
| IPath destValue = URIUtil.toPath(destPathVariableManager.getURIValue(variable)); |
| if (destValue != null && URIUtil.toPath(destPathVariableManager.resolveURI(URIUtil.toURI(destValue))).equals(resolvedSrcValue)) |
| return variable; |
| } |
| // look if one variable in the destination project matches |
| String[] variables = destPathVariableManager.getPathVariableNames(); |
| for (String other : variables) { |
| if (!PathVariableUtil.isPreferred(other)) |
| continue; |
| IPath resolveDestVariable = URIUtil.toPath(destPathVariableManager.resolveURI(destPathVariableManager.getURIValue(other))); |
| if (resolveDestVariable != null && resolveDestVariable.equals(resolvedSrcValue)) { |
| return other; |
| } |
| } |
| // if the variable doesn't exist in the dest project, or |
| // if the value is different than the source project, we have to create |
| // an equivalent. |
| String destVariable = PathVariableUtil.getUniqueVariableName(variable, dest); |
| |
| boolean shouldConvertToRelative = true; |
| if (!srcValue.equals(resolvedSrcValue) && !variableExisted) { |
| // the variable content contains references to more variables |
| |
| String[] referencedVariables = PathVariableUtil.splitVariableNames(srcValue.toPortableString()); |
| shouldConvertToRelative = false; |
| // If the variable value is of type ${PARENT-COUNT-VAR}, |
| // we can avoid generating an intermediate variable and convert it directly. |
| if (referencedVariables.length == 1) { |
| if (PathVariableUtil.isParentVariable(referencedVariables[0])) |
| shouldConvertToRelative = true; |
| } |
| |
| if (!shouldConvertToRelative) { |
| String[] segments = PathVariableUtil.splitVariablesAndContent(srcValue.toPortableString()); |
| StringBuilder result = new StringBuilder(); |
| for (String segment : segments) { |
| String var = PathVariableUtil.extractVariable(segment); |
| if (var.length() > 0) { |
| String copiedVariable = copyVariable(source, dest, var); |
| int index = segment.indexOf(var); |
| if (index != -1) { |
| result.append(segment.substring(0, index)); |
| result.append(copiedVariable); |
| int start = index + var.length(); |
| int end = segment.length(); |
| result.append(segment.substring(start, end)); |
| } |
| } else |
| result.append(segment); |
| } |
| srcValue = Path.fromPortableString(result.toString()); |
| } |
| } |
| if (shouldConvertToRelative) { |
| IPath relativeSrcValue = PathVariableUtil.convertToPathRelativeMacro(destPathVariableManager, resolvedSrcValue, dest, true, null); |
| if (relativeSrcValue != null) |
| srcValue = relativeSrcValue; |
| } |
| destPathVariableManager.setURIValue(destVariable, URIUtil.toURI(srcValue)); |
| return destVariable; |
| } |
| |
| /** |
| * Returns the number of resources in a subtree of the resource tree. |
| * |
| * @param root The subtree to count resources for |
| * @param depth The depth of the subtree to count |
| * @param phantom If true, phantoms are included, otherwise they are ignored. |
| */ |
| public int countResources(IPath root, int depth, final boolean phantom) { |
| if (!tree.includes(root)) |
| return 0; |
| switch (depth) { |
| case IResource.DEPTH_ZERO : |
| return 1; |
| case IResource.DEPTH_ONE : |
| return 1 + tree.getChildCount(root); |
| case IResource.DEPTH_INFINITE : |
| final int[] count = new int[1]; |
| IElementContentVisitor visitor = (aTree, requestor, elementContents) -> { |
| if (phantom || !((ResourceInfo) elementContents).isSet(M_PHANTOM)) |
| count[0]++; |
| return true; |
| }; |
| new ElementTreeIterator(tree, root).iterate(visitor); |
| return count[0]; |
| } |
| return 0; |
| } |
| |
| /* |
| * Creates the given resource in the tree and returns the new resource info object. |
| * If phantom is true, the created element is marked as a phantom. |
| * If there is already be an element in the tree for the given resource |
| * in the given state (i.e., phantom), a CoreException is thrown. |
| * If there is already a phantom in the tree and the phantom flag is false, |
| * the element is overwritten with the new element. (but the synchronization |
| * information is preserved) |
| */ |
| public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException { |
| return createResource(resource, null, phantom, false, false); |
| } |
| |
| /** |
| * Creates a resource, honoring update flags requesting that the resource |
| * be immediately made derived, hidden and/or team private |
| */ |
| public ResourceInfo createResource(IResource resource, int updateFlags) throws CoreException { |
| ResourceInfo info = createResource(resource, null, false, false, false); |
| if ((updateFlags & IResource.DERIVED) != 0) |
| info.set(M_DERIVED); |
| if ((updateFlags & IResource.TEAM_PRIVATE) != 0) |
| info.set(M_TEAM_PRIVATE_MEMBER); |
| if ((updateFlags & IResource.HIDDEN) != 0) |
| info.set(M_HIDDEN); |
| // if ((updateFlags & IResource.VIRTUAL) != 0) |
| // info.set(M_VIRTUAL); |
| return info; |
| } |
| |
| /* |
| * Creates the given resource in the tree and returns the new resource info object. |
| * If phantom is true, the created element is marked as a phantom. |
| * If there is already be an element in the tree for the given resource |
| * in the given state (i.e., phantom), a CoreException is thrown. |
| * If there is already a phantom in the tree and the phantom flag is false, |
| * the element is overwritten with the new element. (but the synchronization |
| * information is preserved) If the specified resource info is null, then create |
| * a new one. |
| * |
| * If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT |
| * cleared before being created and thus any sync info already existing at that namespace |
| * (as indicated by an already existing phantom resource) will be lost. |
| */ |
| public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException { |
| info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone(); |
| ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false); |
| if (phantom) { |
| info.set(M_PHANTOM); |
| info.clearModificationStamp(); |
| } |
| // if nothing existed at the destination then just create the resource in the tree |
| if (original == null) { |
| // we got here from a copy/move. we don't want to copy over any sync info |
| // from the source so clear it. |
| if (!keepSyncInfo) |
| info.setSyncInfo(null); |
| tree.createElement(resource.getFullPath(), info); |
| } else { |
| // if overwrite==true then slam the new info into the tree even if one existed before |
| if (overwrite || (!phantom && original.isSet(M_PHANTOM))) { |
| // copy over the sync info and flags from the old resource info |
| // since we are replacing a phantom with a real resource |
| // DO NOT set the sync info dirty flag because we want to |
| // preserve the old sync info so its not dirty |
| // XXX: must copy over the generic sync info from the old info to the new |
| // XXX: do we really need to clone the sync info here? |
| if (!keepSyncInfo) |
| info.setSyncInfo(original.getSyncInfo(true)); |
| // mark the markers bit as dirty so we snapshot an empty marker set for |
| // the new resource |
| info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY); |
| tree.setElementData(resource.getFullPath(), info); |
| } else { |
| String message = NLS.bind(Messages.resources_mustNotExist, resource.getFullPath()); |
| throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null); |
| } |
| } |
| return info; |
| } |
| |
| @Override |
| public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException { |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= IResource.KEEP_HISTORY; |
| return delete(resources, updateFlags, monitor); |
| } |
| |
| @Override |
| public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| |
| String message = Messages.resources_deleteProblem; |
| MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, |
| null); |
| if (resources.length == 0) { |
| return result; |
| } |
| resources = resources.clone(); // to avoid concurrent changes to this array |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, resources.length); |
| try { |
| prepareOperation(getRoot(), subMonitor); |
| beginOperation(true); |
| for (IResource r : resources) { |
| Resource resource = (Resource) r; |
| if (resource == null) { |
| subMonitor.split(1); |
| continue; |
| } |
| try { |
| resource.delete(updateFlags, subMonitor.newChild(1)); |
| } catch (CoreException e) { |
| // Don't really care about the exception unless the resource is still around. |
| ResourceInfo info = resource.getResourceInfo(false, false); |
| if (resource.exists(resource.getFlags(info), false)) { |
| message = NLS.bind(Messages.resources_couldnotDelete, resource.getFullPath()); |
| result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), |
| message)); |
| result.merge(e.getStatus()); |
| } |
| } |
| subMonitor.worked(1); |
| } |
| if (result.matches(IStatus.ERROR)) |
| throw new ResourceException(result); |
| return result; |
| } catch (OperationCanceledException e) { |
| getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| subMonitor.done(); |
| endOperation(getRoot(), true); |
| } |
| } |
| |
| @Override |
| public void deleteMarkers(IMarker[] markers) throws CoreException { |
| Assert.isNotNull(markers); |
| if (markers.length == 0) |
| return; |
| // clone to avoid outside changes |
| markers = markers.clone(); |
| try { |
| prepareOperation(null, null); |
| beginOperation(true); |
| for (IMarker marker : markers) |
| if (marker != null && marker.getResource() != null) |
| markerManager.removeMarker(marker.getResource(), marker.getId()); |
| } finally { |
| endOperation(null, false); |
| } |
| } |
| |
| /** |
| * Delete the given resource from the current tree of the receiver. |
| * This method simply removes the resource from the tree. No cleanup or |
| * other management is done. Use IResource.delete for proper deletion. |
| * If the given resource is the root, all of its children (i.e., all projects) are |
| * deleted but the root is left. |
| */ |
| void deleteResource(IResource resource) { |
| IPath path = resource.getFullPath(); |
| if (path.equals(Path.ROOT)) { |
| IProject[] children = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| for (IProject element : children) |
| tree.deleteElement(element.getFullPath()); |
| } else |
| tree.deleteElement(path); |
| } |
| |
| /** |
| * End an operation (group of resource changes). |
| * Notify interested parties that resource changes have taken place. All |
| * registered resource change listeners are notified. If autobuilding is |
| * enabled, a build is run. |
| */ |
| public void endOperation(ISchedulingRule rule, boolean build) throws CoreException { |
| WorkManager workManager = getWorkManager(); |
| //don't do any end operation work if we failed to check in |
| if (workManager.checkInFailed(rule)) |
| return; |
| // This is done in a try finally to ensure that we always decrement the operation count |
| // and release the workspace lock. This must be done at the end because snapshot |
| // and "hasChanges" comparison have to happen without interference from other threads. |
| boolean hasTreeChanges = false; |
| boolean depthOne = false; |
| try { |
| workManager.setBuild(build); |
| // if we are not exiting a top level operation then just decrement the count and return |
| depthOne = workManager.getPreparedOperationDepth() == 1; |
| if (!(notificationManager.shouldNotify() || depthOne)) { |
| notificationManager.requestNotify(); |
| return; |
| } |
| // do the following in a try/finally to ensure that the operation tree is nulled at the end |
| // as we are completing a top level operation. |
| try { |
| notificationManager.beginNotify(); |
| // check for a programming error on using beginOperation/endOperation |
| Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$ |
| |
| // At this time we need to re-balance the nested operations. It is necessary because |
| // build() and snapshot() should not fail if they are called. |
| workManager.rebalanceNestedOperations(); |
| |
| //find out if any operation has potentially modified the tree |
| hasTreeChanges = workManager.shouldBuild(); |
| //double check if the tree has actually changed |
| if (hasTreeChanges) |
| hasTreeChanges = operationTree != null && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getBuildComparator(), true); |
| broadcastPostChange(); |
| // Request a snapshot if we are sufficiently out of date. |
| saveManager.snapshotIfNeeded(hasTreeChanges); |
| } finally { |
| // make sure the tree is immutable if we are ending a top-level operation. |
| if (depthOne) { |
| tree.immutable(); |
| operationTree = null; |
| } else |
| newWorkingTree(); |
| } |
| } finally { |
| workManager.checkOut(rule); |
| } |
| if (depthOne) |
| buildManager.endTopLevel(hasTreeChanges); |
| } |
| |
| /** |
| * Flush the build order cache for the workspace. The buildOrder cache contains the total |
| * order of the build configurations in the workspace, including projects not mentioned in |
| * the workspace description. |
| */ |
| protected void flushBuildOrder() { |
| buildOrder = null; |
| buildOrderGraph = null; |
| } |
| |
| @Override |
| public void forgetSavedTree(String pluginId) { |
| saveManager.forgetSavedTree(pluginId); |
| } |
| |
| public AliasManager getAliasManager() { |
| return aliasManager; |
| } |
| |
| /** |
| * Returns this workspace's build manager |
| */ |
| public BuildManager getBuildManager() { |
| return buildManager; |
| } |
| |
| /** |
| * Returns the order in which open projects in this workspace will be built. |
| * The result returned is a list of project buildConfigs, that need to be built |
| * in order to successfully build the active config of every project in this |
| * workspace. |
| * <p> |
| * The build configuration order is based on information specified in the workspace |
| * description. The project build configs are built in the order specified by |
| * <code>IWorkspaceDescription.getBuildOrder</code>; closed or non-existent |
| * projects are ignored and not included in the result. If any open projects are |
| * not specified in this order, they are appended to the end of the build order |
| * sorted by project name (to provide a stable ordering). |
| * </p> |
| * <p> |
| * If <code>IWorkspaceDescription.getBuildOrder</code> is non-null, the default |
| * build order is used (calculated based on references); again, only open projects' |
| * buildConfigs are included in the result. |
| * </p> |
| * <p> |
| * The returned value is cached in the <code>buildOrder</code> field. |
| * </p> |
| * |
| * @return the list of currently open projects active buildConfigs (and the project buildConfigs |
| * they depend on) in the workspace in the order in which they would be built by <code>IWorkspace.build</code>. |
| * @see IWorkspace#build(int, IProgressMonitor) |
| * @see IWorkspaceDescription#getBuildOrder() |
| */ |
| public IBuildConfiguration[] getBuildOrder() { |
| // Return the build order cache. |
| if (buildOrder != null) |
| return buildOrder; |
| |
| // see if a particular build order is specified |
| String[] order = description.getBuildOrder(false); |
| if (order != null) { |
| LinkedHashSet<IBuildConfiguration> configs = new LinkedHashSet<>(); |
| |
| // convert from project names to active project buildConfigs |
| // and eliminate non-existent and closed projects |
| for (String element : order) { |
| IProject project = getRoot().getProject(element); |
| if (project.isAccessible()) |
| configs.add(((Project) project).internalGetActiveBuildConfig()); |
| } |
| |
| // Add projects not mentioned in the build order to the end, in a sensible reference order |
| configs.addAll(Arrays.asList(vertexOrderToProjectBuildConfigOrder(computeActiveBuildConfigOrder()).buildConfigurations)); |
| |
| // Update the cache - Java 5 volatile memory barrier semantics |
| IBuildConfiguration[] bo = new IBuildConfiguration[configs.size()]; |
| configs.toArray(bo); |
| this.buildOrder = bo; |
| } else { |
| // use default project build order |
| // computed for all accessible projects in workspace |
| Digraph<IBuildConfiguration> buildGraph = getBuildGraph(); |
| buildOrder = vertexOrderToProjectBuildConfigOrder(ComputeProjectOrder.computeVertexOrder(buildGraph, IBuildConfiguration.class)).buildConfigurations; |
| } |
| |
| return buildOrder; |
| } |
| |
| /** |
| * |
| * @return the build graph, or null if a build order has been specified (in which case, use {@link #getBuildOrder()} directly |
| */ |
| private Digraph<IBuildConfiguration> getBuildGraph() { |
| if (buildOrderGraph != null) { |
| return buildOrderGraph; |
| } |
| String[] order = description.getBuildOrder(false); |
| if (order != null) { |
| return null; |
| } |
| buildOrderGraph = computeActiveBuildConfigGraph(); |
| return buildOrderGraph; |
| } |
| |
| public CharsetManager getCharsetManager() { |
| return charsetManager; |
| } |
| |
| public ContentDescriptionManager getContentDescriptionManager() { |
| return contentDescriptionManager; |
| } |
| |
| @Override |
| public Map<IProject, IProject[]> getDanglingReferences() { |
| IProject[] projects = getRoot().getProjects(IContainer.INCLUDE_HIDDEN); |
| Map<IProject, IProject[]> result = new HashMap<>(projects.length); |
| for (IProject project : projects) { |
| if (!project.isAccessible()) { |
| continue; |
| } |
| IProject[] refs = ((Project)project).internalGetDescription().getReferencedProjects(false); |
| List<IProject> dangling = new ArrayList<>(refs.length); |
| for (IProject ref : refs) { |
| if (!ref.exists()) { |
| dangling.add(ref); |
| } |
| } |
| if (!dangling.isEmpty()) { |
| result.put(project, dangling.toArray(new IProject[dangling.size()])); |
| } |
| } |
| return result; |
| } |
| |
| @Override |
| public IWorkspaceDescription getDescription() { |
| WorkspaceDescription workingCopy = defaultWorkspaceDescription(); |
| description.copyTo(workingCopy); |
| return workingCopy; |
| } |
| |
| /** |
| * Returns the current element tree for this workspace |
| */ |
| public ElementTree getElementTree() { |
| return tree; |
| } |
| |
| public FileSystemResourceManager getFileSystemManager() { |
| return fileSystemManager; |
| } |
| |
| /** |
| * Returns the marker manager for this workspace |
| */ |
| public MarkerManager getMarkerManager() { |
| return markerManager; |
| } |
| |
| public LocalMetaArea getMetaArea() { |
| return localMetaArea; |
| } |
| |
| protected IMoveDeleteHook getMoveDeleteHook() { |
| if (moveDeleteHook == null) |
| initializeMoveDeleteHook(); |
| return moveDeleteHook; |
| } |
| |
| @Override |
| public IFilterMatcherDescriptor getFilterMatcherDescriptor(String filterMatcherId) { |
| return filterManager.getFilterDescriptor(filterMatcherId); |
| } |
| |
| @Override |
| public IFilterMatcherDescriptor[] getFilterMatcherDescriptors() { |
| return filterManager.getFilterDescriptors(); |
| } |
| |
| @Override |
| public IProjectNatureDescriptor getNatureDescriptor(String natureId) { |
| return natureManager.getNatureDescriptor(natureId); |
| } |
| |
| @Override |
| public IProjectNatureDescriptor[] getNatureDescriptors() { |
| return natureManager.getNatureDescriptors(); |
| } |
| |
| /** |
| * Returns the nature manager for this workspace. |
| */ |
| public NatureManager getNatureManager() { |
| return natureManager; |
| } |
| |
| public NotificationManager getNotificationManager() { |
| return notificationManager; |
| } |
| |
| @Override |
| public IPathVariableManager getPathVariableManager() { |
| return pathVariableManager; |
| } |
| |
| public IPropertyManager getPropertyManager() { |
| return propertyManager; |
| } |
| |
| /** |
| * Returns the refresh manager for this workspace |
| */ |
| public RefreshManager getRefreshManager() { |
| return refreshManager; |
| } |
| |
| /** |
| * Returns the resource info for the identified resource. |
| * null is returned if no such resource can be found. |
| * If the phantom flag is true, phantom resources are considered. |
| * If the mutable flag is true, the info is opened for change. |
| * |
| * This method DOES NOT throw an exception if the resource is not found. |
| */ |
| public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) { |
| try { |
| if (path.segmentCount() == 0) { |
| ResourceInfo info = (ResourceInfo) tree.getTreeData(); |
| Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$ |
| return info; |
| } |
| ResourceInfo result = null; |
| if (!tree.includes(path)) |
| return null; |
| if (mutable) |
| result = (ResourceInfo) tree.openElementData(path); |
| else |
| result = (ResourceInfo) tree.getElementData(path); |
| if (result != null && (!phantom && result.isSet(M_PHANTOM))) |
| return null; |
| return result; |
| } catch (IllegalArgumentException e) { |
| return null; |
| } |
| } |
| |
| @Override |
| public IWorkspaceRoot getRoot() { |
| return defaultRoot; |
| } |
| |
| @Override |
| public IResourceRuleFactory getRuleFactory() { |
| //note that the rule factory is created lazily because it |
| //requires loading the teamHook extension |
| if (ruleFactory == null) |
| ruleFactory = new Rules(this); |
| return ruleFactory; |
| } |
| |
| public SaveManager getSaveManager() { |
| return saveManager; |
| } |
| |
| @Override |
| public ISynchronizer getSynchronizer() { |
| return synchronizer; |
| } |
| |
| /** |
| * Returns the installed team hook. Never returns null. |
| */ |
| protected TeamHook getTeamHook() { |
| if (teamHook == null) |
| initializeTeamHook(); |
| return teamHook; |
| } |
| |
| /** |
| * We should not have direct references to this field. All references should go through |
| * this method. |
| */ |
| public WorkManager getWorkManager() throws CoreException { |
| if (_workManager == null) { |
| String message = Messages.resources_shutdown; |
| throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message)); |
| } |
| return _workManager; |
| } |
| |
| /** |
| * A move/delete hook hasn't been initialized. Check the extension point and |
| * try to create a new hook if a user has one defined as an extension. Otherwise |
| * use the Core's implementation as the default. |
| */ |
| protected void initializeMoveDeleteHook() { |
| try { |
| if (!canCreateExtensions()) |
| return; |
| IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK); |
| // no-one is plugged into the extension point so disable validation |
| if (configs == null || configs.length == 0) { |
| return; |
| } |
| // can only have one defined at a time. log a warning |
| if (configs.length > 1) { |
| //XXX: should provide a meaningful status code |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneHook, null); |
| Policy.log(status); |
| return; |
| } |
| // otherwise we have exactly one hook extension. Try to create a new instance |
| // from the user-specified class. |
| try { |
| IConfigurationElement config = configs[0]; |
| moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$ |
| } catch (CoreException e) { |
| //ignore the failure if we are shutting down (expected since extension |
| //provider plugin has probably already shut down |
| if (canCreateExtensions()) { |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initHook, e); |
| Policy.log(status); |
| } |
| } |
| } finally { |
| // for now just use Core's implementation |
| if (moveDeleteHook == null) |
| moveDeleteHook = new MoveDeleteHook(); |
| } |
| } |
| |
| /** |
| * A team hook hasn't been initialized. Check the extension point and |
| * try to create a new hook if a user has one defined as an extension. |
| * Otherwise use the Core's implementation as the default. |
| */ |
| protected void initializeTeamHook() { |
| try { |
| if (!canCreateExtensions()) |
| return; |
| IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK); |
| // no-one is plugged into the extension point so disable validation |
| if (configs == null || configs.length == 0) { |
| return; |
| } |
| // can only have one defined at a time. log a warning |
| if (configs.length > 1) { |
| //XXX: should provide a meaningful status code |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneTeamHook, null); |
| Policy.log(status); |
| return; |
| } |
| // otherwise we have exactly one hook extension. Try to create a new instance |
| // from the user-specified class. |
| try { |
| IConfigurationElement config = configs[0]; |
| teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$ |
| } catch (CoreException e) { |
| //ignore the failure if we are shutting down (expected since extension |
| //provider plugin has probably already shut down |
| if (canCreateExtensions()) { |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initTeamHook, e); |
| Policy.log(status); |
| } |
| } |
| } finally { |
| // default to use Core's implementation |
| //create anonymous subclass because TeamHook is abstract |
| if (teamHook == null) |
| teamHook = new TeamHook() { |
| // empty |
| }; |
| } |
| } |
| |
| /** |
| * A file modification validator hasn't been initialized. Check the extension point and |
| * try to create a new validator if a user has one defined as an extension. |
| */ |
| protected void initializeValidator() { |
| shouldValidate = false; |
| if (!canCreateExtensions()) |
| return; |
| IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR); |
| // no-one is plugged into the extension point so disable validation |
| if (configs == null || configs.length == 0) { |
| return; |
| } |
| // can only have one defined at a time. log a warning, disable validation, but continue with |
| // the #setContents (e.g. don't throw an exception) |
| if (configs.length > 1) { |
| //XXX: should provide a meaningful status code |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_oneValidator, null); |
| Policy.log(status); |
| return; |
| } |
| // otherwise we have exactly one validator extension. Try to create a new instance |
| // from the user-specified class. |
| try { |
| IConfigurationElement config = configs[0]; |
| validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$ |
| shouldValidate = true; |
| } catch (CoreException e) { |
| //ignore the failure if we are shutting down (expected since extension |
| //provider plugin has probably already shut down |
| if (canCreateExtensions()) { |
| IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Messages.resources_initValidator, e); |
| Policy.log(status); |
| } |
| } |
| } |
| |
| public WorkspaceDescription internalGetDescription() { |
| return description; |
| } |
| |
| @Override |
| public boolean isAutoBuilding() { |
| return description.isAutoBuilding(); |
| } |
| |
| public boolean isOpen() { |
| return openFlag; |
| } |
| |
| @Override |
| public boolean isTreeLocked() { |
| return treeLocked == Thread.currentThread(); |
| } |
| |
| /** |
| * Link the given tree into the receiver's tree at the specified resource. |
| */ |
| protected void linkTrees(IPath path, ElementTree[] newTrees) { |
| tree = tree.mergeDeltaChain(path, newTrees); |
| } |
| |
| @Override |
| public IProjectDescription loadProjectDescription(InputStream stream) throws CoreException { |
| IProjectDescription result = null; |
| result = new ProjectDescriptionReader().read(new InputSource(stream)); |
| if (result == null) { |
| String message = NLS.bind(Messages.resources_errorReadProject, stream.toString()); |
| IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, null); |
| throw new ResourceException(status); |
| } |
| return result; |
| } |
| |
| @Override |
| public IProjectDescription loadProjectDescription(IPath path) throws CoreException { |
| IProjectDescription result = null; |
| IOException e = null; |
| try { |
| result = new ProjectDescriptionReader().read(path); |
| if (result != null) { |
| // check to see if we are using in the default area or not. use java.io.File for |
| // testing equality because it knows better w.r.t. drives and case sensitivity |
| IPath user = path.removeLastSegments(1); |
| IPath platform = getRoot().getLocation().append(result.getName()); |
| if (!user.toFile().equals(platform.toFile())) |
| result.setLocation(user); |
| } |
| } catch (IOException ex) { |
| e = ex; |
| } |
| if (result == null || e != null) { |
| String message = NLS.bind(Messages.resources_errorReadProject, path.toOSString()); |
| IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e); |
| throw new ResourceException(status); |
| } |
| return result; |
| } |
| |
| @Override |
| public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException { |
| int updateFlags = force ? IResource.FORCE : IResource.NONE; |
| updateFlags |= IResource.KEEP_HISTORY; |
| return move(resources, destination, updateFlags, monitor); |
| } |
| |
| @Override |
| public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException { |
| Assert.isLegal(resources != null); |
| |
| if (resources.length == 0) { |
| return Status.OK_STATUS; |
| } |
| resources = resources.clone(); // to avoid concurrent changes to this array |
| |
| String message = Messages.resources_moving_0; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, message, resources.length); |
| IPath parentPath = null; |
| message = Messages.resources_moveProblem; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, |
| null); |
| try { |
| prepareOperation(getRoot(), subMonitor); |
| beginOperation(true); |
| for (int i = 0; i < resources.length; i++) { |
| Resource resource = (Resource) resources[i]; |
| if (resource == null || isDuplicate(resources, i)) { |
| subMonitor.split(1); |
| continue; |
| } |
| // test siblings |
| if (parentPath == null) |
| parentPath = resource.getFullPath().removeLastSegments(1); |
| if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) { |
| // test move requirements |
| try { |
| IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), |
| resource.getType(), updateFlags); |
| if (requirements.isOK()) { |
| try { |
| resource.move(destination.append(resource.getName()), updateFlags, |
| subMonitor.newChild(1)); |
| } catch (CoreException e) { |
| status.merge(e.getStatus()); |
| } |
| } else { |
| subMonitor.worked(1); |
| status.merge(requirements); |
| } |
| } catch (CoreException e) { |
| subMonitor.worked(1); |
| status.merge(e.getStatus()); |
| } |
| } else { |
| subMonitor.worked(1); |
| message = NLS.bind(Messages.resources_notChild, resource.getFullPath(), parentPath); |
| status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message)); |
| } |
| } |
| } catch (OperationCanceledException e) { |
| getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| subMonitor.done(); |
| endOperation(getRoot(), true); |
| } |
| if (status.matches(IStatus.ERROR)) |
| throw new ResourceException(status); |
| return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status; |
| } |
| |
| /** |
| * Moves this resource's subtree to the destination. This operation should only be |
| * used by move methods. Destination must be a valid destination for this resource. |
| * The keepSyncInfo boolean is used to indicated whether or not the sync info should |
| * be moved from the source to the destination. |
| */ |
| |
| /* package */ |
| void move(Resource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException { |
| // overlay the tree at the destination path, preserving any important info |
| // in any already existing resource information |
| copyTree(source, destination, depth, updateFlags, keepSyncInfo, true, source.getType() == IResource.PROJECT); |
| source.fixupAfterMoveSource(); |
| } |
| |
| /** |
| * Create and return a new tree element of the given type. |
| */ |
| protected ResourceInfo newElement(int type) { |
| ResourceInfo result = null; |
| switch (type) { |
| case IResource.FILE : |
| case IResource.FOLDER : |
| result = new ResourceInfo(); |
| break; |
| case IResource.PROJECT : |
| result = new ProjectInfo(); |
| break; |
| case IResource.ROOT : |
| result = new RootInfo(); |
| break; |
| } |
| result.setNodeId(nextNodeId()); |
| updateModificationStamp(result); |
| result.setType(type); |
| return result; |
| } |
| |
| @Override |
| public IBuildConfiguration newBuildConfig(String projectName, String configName) { |
| return new BuildConfiguration(getRoot().getProject(projectName), configName); |
| } |
| |
| @Override |
| public IProjectDescription newProjectDescription(String projectName) { |
| IProjectDescription result = new ProjectDescription(); |
| result.setName(projectName); |
| return result; |
| } |
| |
| public Resource newResource(IPath path, int type) { |
| String message; |
| switch (type) { |
| case IResource.FOLDER : |
| if (path.segmentCount() < ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH) { |
| message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ |
| Assert.isLegal(false, message); |
| } |
| return new Folder(path.makeAbsolute(), this); |
| case IResource.FILE : |
| if (path.segmentCount() < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) { |
| message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$ |
| Assert.isLegal(false, message); |
| } |
| return new File(path.makeAbsolute(), this); |
| case IResource.PROJECT : |
| return (Resource) getRoot().getProject(path.lastSegment()); |
| case IResource.ROOT : |
| return (Resource) getRoot(); |
| } |
| Assert.isLegal(false); |
| // will never get here because of assertion. |
| return null; |
| } |
| |
| /** |
| * Opens a new mutable element tree layer, thus allowing |
| * modifications to the tree. |
| */ |
| public ElementTree newWorkingTree() { |
| tree = tree.newEmptyDelta(); |
| return tree; |
| } |
| |
| /** |
| * Returns the next, previously unassigned, marker id. |
| */ |
| protected long nextMarkerId() { |
| return nextMarkerId++; |
| } |
| |
| protected long nextNodeId() { |
| return nextNodeId++; |
| } |
| |
| /** |
| * Opens this workspace using the data at its location in the local file system. |
| * This workspace must not be open. |
| * If the operation succeeds, the result will detail any serious |
| * (but non-fatal) problems encountered while opening the workspace. |
| * The status code will be <code>OK</code> if there were no problems. |
| * An exception is thrown if there are fatal problems opening the workspace, |
| * in which case the workspace is left closed. |
| * <p> |
| * This method is long-running; progress and cancellation are provided |
| * by the given progress monitor. |
| * </p> |
| * |
| * @param monitor a progress monitor, or <code>null</code> if progress |
| * reporting and cancellation are not desired |
| * @return status with code <code>OK</code> if no problems; |
| * otherwise status describing any serious but non-fatal problems. |
| * |
| * @exception CoreException if the workspace could not be opened. |
| * Reasons include: |
| * <ul> |
| * <li> There is no valid workspace structure at the given location |
| * in the local file system.</li> |
| * <li> The workspace structure on disk appears to be hopelessly corrupt.</li> |
| * </ul> |
| * @see ResourcesPlugin#getWorkspace() |
| */ |
| public IStatus open(IProgressMonitor monitor) throws CoreException { |
| // This method is not inside an operation because it is the one responsible for |
| // creating the WorkManager object (who takes care of operations). |
| String message = Messages.resources_workspaceOpen; |
| Assert.isTrue(!isOpen(), message); |
| if (!getMetaArea().hasSavedWorkspace()) { |
| message = Messages.resources_readWorkspaceMeta; |
| throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null); |
| } |
| description = new WorkspacePreferences(); |
| |
| // if we have an old description file, read it (getting rid of it) |
| WorkspaceDescription oldDescription = getMetaArea().readOldWorkspace(); |
| if (oldDescription != null) { |
| description.copyFrom(oldDescription); |
| ResourcesPlugin.getPlugin().savePluginPreferences(); |
| } |
| |
| // create root location |
| localMetaArea.locationFor(getRoot()).toFile().mkdirs(); |
| |
| SubMonitor subMonitor = SubMonitor.convert(null); |
| startup(subMonitor); |
| // restart the notification manager so it is initialized with the right tree |
| notificationManager.startup(null); |
| openFlag = true; |
| if (crashed || refreshRequested()) { |
| try { |
| refreshManager.refresh(getRoot()); |
| } catch (RuntimeException e) { |
| //don't fail entire open if refresh failed, just report as warning |
| return new ResourceStatus(IResourceStatus.INTERNAL_ERROR, Path.ROOT, Messages.resources_errorMultiRefresh, e); |
| } |
| } |
| //finally register a string pool participant |
| stringPoolJob = new StringPoolJob(); |
| stringPoolJob.addStringPoolParticipant(saveManager, getRoot()); |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Called before checking the pre-conditions of an operation. Optionally supply |
| * a scheduling rule to determine when the operation is safe to run. If a scheduling |
| * rule is supplied, this method will block until it is safe to run. |
| * |
| * @param rule the scheduling rule that describes what this operation intends to modify. |
| */ |
| public void prepareOperation(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException { |
| try { |
| //make sure autobuild is not running if it conflicts with this operation |
| ISchedulingRule buildRule = getRuleFactory().buildRule(); |
| if (rule != null && buildRule != null && (rule.isConflicting(buildRule) || buildRule.isConflicting(rule))) |
| buildManager.interrupt(); |
| } finally { |
| getWorkManager().checkIn(rule, monitor); |
| } |
| if (!isOpen()) { |
| String message = Messages.resources_workspaceClosed; |
| throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null); |
| } |
| } |
| |
| protected boolean refreshRequested() { |
| String[] args = Platform.getCommandLineArgs(); |
| for (String arg : args) |
| if (arg.equalsIgnoreCase(REFRESH_ON_STARTUP)) |
| return true; |
| return false; |
| } |
| |
| @Override |
| public void removeResourceChangeListener(IResourceChangeListener listener) { |
| notificationManager.removeListener(listener); |
| } |
| |
| @Deprecated |
| @Override |
| public void removeSaveParticipant(Plugin plugin) { |
| Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$ |
| saveManager.removeParticipant(plugin.getBundle().getSymbolicName()); |
| } |
| |
| @Override |
| public void removeSaveParticipant(String pluginId) { |
| Assert.isNotNull(pluginId, "Plugin id must not be null"); //$NON-NLS-1$ |
| saveManager.removeParticipant(pluginId); |
| } |
| |
| @Override |
| public void run(ICoreRunnable action, IProgressMonitor monitor) throws CoreException { |
| run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); |
| } |
| |
| @Override |
| public void run(ICoreRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, Policy.totalWork); // $NON-NLS-1$ |
| int depth = -1; |
| boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0; |
| try { |
| prepareOperation(rule, subMonitor); |
| beginOperation(true); |
| if (avoidNotification) |
| avoidNotification = notificationManager.beginAvoidNotify(); |
| depth = getWorkManager().beginUnprotected(); |
| action.run(subMonitor.newChild(Policy.opWork)); |
| } catch (OperationCanceledException e) { |
| getWorkManager().operationCanceled(); |
| throw e; |
| } catch (CoreException e) { |
| if (e.getStatus().getSeverity() == IStatus.CANCEL) |
| getWorkManager().operationCanceled(); |
| throw e; |
| } finally { |
| subMonitor.done(); |
| if (avoidNotification) |
| notificationManager.endAvoidNotify(); |
| if (depth >= 0) |
| getWorkManager().endUnprotected(depth); |
| endOperation(rule, false); |
| } |
| } |
| |
| @Override |
| public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException { |
| run((ICoreRunnable) action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor); |
| } |
| |
| @Override |
| public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException { |
| run((ICoreRunnable) action, rule, options, monitor); |
| } |
| |
| @Override |
| public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException { |
| return this.save(full, false, monitor); |
| } |
| |
| public IStatus save(boolean full, boolean keepConsistencyWhenCanceled, IProgressMonitor monitor) throws CoreException { |
| String message; |
| if (full) { |
| //according to spec it is illegal to start a full save inside another operation |
| if (getWorkManager().isLockAlreadyAcquired()) { |
| message = Messages.resources_saveOp; |
| throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, new IllegalStateException()); |
| } |
| return saveManager.save(ISaveContext.FULL_SAVE, keepConsistencyWhenCanceled, null, monitor); |
| } |
| // A snapshot was requested. Start an operation (if not already started) and |
| // signal that a snapshot should be done at the end. |
| try { |
| prepareOperation(getRoot(), monitor); |
| beginOperation(false); |
| saveManager.requestSnapshot(); |
| message = Messages.resources_snapRequest; |
| return new ResourceStatus(IStatus.OK, message); |
| } finally { |
| endOperation(getRoot(), false); |
| } |
| } |
| |
| public void setCrashed(boolean value) { |
| crashed = value; |
| if (crashed) { |
| String msg = "The workspace exited with unsaved changes in the previous session; refreshing workspace to recover changes."; //$NON-NLS-1$ |
| Policy.log(new ResourceStatus(ICoreConstants.CRASH_DETECTED, msg)); |
| if (Policy.DEBUG) |
| Policy.debug(msg); |
| } |
| } |
| |
| @Override |
| public void setDescription(IWorkspaceDescription value) { |
| // if both the old and new description's build orders are null, leave the |
| // workspace's build order slot because it is caching the computed order. |
| // Otherwise, set the slot to null to force recomputing or building from the description. |
| WorkspaceDescription newDescription = (WorkspaceDescription) value; |
| String[] newOrder = newDescription.getBuildOrder(false); |
| if (description.getBuildOrder(false) != null || newOrder != null) { |
| flushBuildOrder(); |
| } |
| description.copyFrom(newDescription); |
| ResourcesPlugin.getPlugin().savePluginPreferences(); |
| } |
| |
| public void setTreeLocked(boolean locked) { |
| Assert.isTrue(!locked || treeLocked == null, "The workspace tree is already locked"); //$NON-NLS-1$ |
| treeLocked = locked ? Thread.currentThread() : null; |
| } |
| |
| /** |
| * Shuts down the workspace managers. |
| */ |
| protected void shutdown(IProgressMonitor monitor) throws CoreException { |
| IManager[] managers = { buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, |
| markerManager, _workManager, aliasManager, refreshManager, contentDescriptionManager, natureManager, |
| filterManager }; |
| SubMonitor subMonitor = SubMonitor.convert(monitor, managers.length); // $NON-NLS-1$ |
| try { |
| String message = Messages.resources_shutdownProblems; |
| MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, |
| null); |
| // best effort to shutdown every object and free resources |
| for (IManager manager : managers) { |
| if (manager == null) |
| subMonitor.worked(1); |
| else { |
| try { |
| manager.shutdown(subMonitor.newChild(1)); |
| } catch (Exception e) { |
| message = Messages.resources_shutdownProblems; |
| status.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, |
| message, e)); |
| } |
| } |
| } |
| buildManager = null; |
| notificationManager = null; |
| propertyManager = null; |
| pathVariableManager = null; |
| fileSystemManager = null; |
| markerManager = null; |
| synchronizer = null; |
| saveManager = null; |
| _workManager = null; |
| aliasManager = null; |
| refreshManager = null; |
| charsetManager = null; |
| contentDescriptionManager = null; |
| if (!status.isOK()) |
| throw new CoreException(status); |
| } finally { |
| subMonitor.done(); |
| } |
| } |
| |
| @Override |
| public String[] sortNatureSet(String[] natureIds) { |
| return natureManager.sortNatureSet(natureIds); |
| } |
| |
| /** |
| * Starts all the workspace manager classes. |
| */ |
| protected void startup(IProgressMonitor monitor) throws CoreException { |
| // ensure the tree is locked during the startup notification |
| try { |
| _workManager = new WorkManager(this); |
| _workManager.startup(null); |
| fileSystemManager = new FileSystemResourceManager(this); |
| fileSystemManager.startup(monitor); |
| pathVariableManager = new PathVariableManager(); |
| pathVariableManager.startup(null); |
| natureManager = new NatureManager(); |
| natureManager.startup(null); |
| filterManager = new FilterTypeManager(); |
| filterManager.startup(null); |
| buildManager = new BuildManager(this, getWorkManager().getLock()); |
| buildManager.startup(null); |
| notificationManager = new NotificationManager(this); |
| notificationManager.startup(null); |
| markerManager = new MarkerManager(this); |
| markerManager.startup(null); |
| synchronizer = new Synchronizer(this); |
| saveManager = new SaveManager(this); |
| saveManager.startup(null); |
| propertyManager = new PropertyManager2((Workspace) ResourcesPlugin.getWorkspace()); |
| propertyManager.startup(monitor); |
| charsetManager = new CharsetManager(this); |
| charsetManager.startup(null); |
| contentDescriptionManager = new ContentDescriptionManager(); |
| contentDescriptionManager.startup(null); |
| //must start after save manager, because (read) access to tree is needed |
| //must start after other managers to avoid potential cyclic dependency on uninitialized managers (see bug 316182) |
| //must start before alias manager (see bug 94829) |
| refreshManager = new RefreshManager(this); |
| refreshManager.startup(null); |
| //must start at the end to avoid potential cyclic dependency on other uninitialized managers (see bug 369177) |
| aliasManager = new AliasManager(this); |
| aliasManager.startup(null); |
| } finally { |
| //unlock tree even in case of failure, otherwise shutdown will also fail |
| treeLocked = null; |
| _workManager.postWorkspaceStartup(); |
| } |
| } |
| |
| /** |
| * Returns a string representation of this working state's |
| * structure suitable for debug purposes. |
| */ |
| public String toDebugString() { |
| final StringBuilder buffer = new StringBuilder("\nDump of " + this + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$ |
| buffer.append(" parent: " + tree.getParent()); //$NON-NLS-1$ |
| IElementContentVisitor visitor = (aTree, requestor, elementContents) -> { |
| buffer.append("\n " + requestor.requestPath() + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$ |
| return true; |
| }; |
| new ElementTreeIterator(tree, Path.ROOT).iterate(visitor); |
| return buffer.toString(); |
| } |
| |
| public void updateModificationStamp(ResourceInfo info) { |
| info.incrementModificationStamp(); |
| } |
| |
| @Override |
| public IStatus validateEdit(final IFile[] files, final Object context) { |
| // if validation is turned off then just return |
| if (!shouldValidate) { |
| String message = Messages.resources_readOnly2; |
| MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.READ_ONLY_LOCAL, message, null); |
| for (IFile file : files) { |
| if (file.isReadOnly()) { |
| IPath filePath = file.getFullPath(); |
| message = NLS.bind(Messages.resources_readOnly, filePath); |
| result.add(new ResourceStatus(IResourceStatus.READ_ONLY_LOCAL, filePath, message)); |
| } |
| } |
| return result.getChildren().length == 0 ? Status.OK_STATUS : (IStatus) result; |
| } |
| // first time through the validator hasn't been initialized so try and create it |
| if (validator == null) |
| initializeValidator(); |
| // we were unable to initialize the validator. Validation has been turned off and |
| // a warning has already been logged so just return. |
| if (validator == null) |
| return Status.OK_STATUS; |
| // otherwise call the API and throw an exception if appropriate |
| final IStatus[] status = new IStatus[1]; |
| ISafeRunnable body = new ISafeRunnable() { |
| @Override |
| public void handleException(Throwable exception) { |
| status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); |
| } |
| |
| @Override |
| public void run() throws Exception { |
| Object c = context; |
| //must null any reference to FileModificationValidationContext for backwards compatibility |
| if (!(validator instanceof FileModificationValidator)) |
| if (c instanceof FileModificationValidationContext) |
| c = null; |
| status[0] = validator.validateEdit(files, c); |
| } |
| }; |
| SafeRunner.run(body); |
| return status[0]; |
| } |
| |
| @Override |
| public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) { |
| return locationValidator.validateLinkLocation(resource, unresolvedLocation); |
| } |
| |
| @Override |
| public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) { |
| return locationValidator.validateLinkLocationURI(resource, unresolvedLocation); |
| } |
| |
| @Override |
| public IStatus validateName(String segment, int type) { |
| return locationValidator.validateName(segment, type); |
| } |
| |
| @Override |
| public IStatus validateNatureSet(String[] natureIds) { |
| return natureManager.validateNatureSet(natureIds); |
| } |
| |
| @Override |
| public IStatus validatePath(String path, int type) { |
| return locationValidator.validatePath(path, type); |
| } |
| |
| @Override |
| public IStatus validateProjectLocation(IProject context, IPath location) { |
| return locationValidator.validateProjectLocation(context, location); |
| } |
| |
| @Override |
| public IStatus validateProjectLocationURI(IProject project, URI location) { |
| return locationValidator.validateProjectLocationURI(project, location); |
| } |
| |
| /** |
| * Internal method. To be called only from the following methods: |
| * <ul> |
| * <li><code>IFile#appendContents</code></li> |
| * <li><code>IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)</code></li> |
| * <li><code>IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)</code></li> |
| * </ul> |
| * |
| * @see IFileModificationValidator#validateSave(IFile) |
| */ |
| protected void validateSave(final IFile file) throws CoreException { |
| // if validation is turned off then just return |
| if (!shouldValidate) |
| return; |
| // first time through the validator hasn't been initialized so try and create it |
| if (validator == null) |
| initializeValidator(); |
| // we were unable to initialize the validator. Validation has been turned off and |
| // a warning has already been logged so just return. |
| if (validator == null) |
| return; |
| // otherwise call the API and throw an exception if appropriate |
| final IStatus[] status = new IStatus[1]; |
| ISafeRunnable body = new ISafeRunnable() { |
| @Override |
| public void handleException(Throwable exception) { |
| status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception); |
| } |
| |
| @Override |
| public void run() throws Exception { |
| status[0] = validator.validateSave(file); |
| } |
| }; |
| SafeRunner.run(body); |
| if (!status[0].isOK()) |
| throw new ResourceException(status[0]); |
| } |
| |
| @Override |
| public IStatus validateFiltered(IResource resource) { |
| try { |
| if (((Resource) resource).isFilteredWithException(true)) |
| return new ResourceStatus(IStatus.ERROR, Messages.resources_errorResourceIsFiltered); |
| } catch (CoreException e) { |
| // if we can't validate it, we return OK |
| } |
| return Status.OK_STATUS; |
| } |
| } |