Package org.eclipse.core.internal.resources

Source Code of org.eclipse.core.internal.resources.Workspace

/*******************************************************************************
* Copyright (c) 2000, 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*     Red Hat Incorporated - loadProjectDescription(InputStream)
*******************************************************************************/
package org.eclipse.core.internal.resources;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.*;
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.refresh.RefreshManager;
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.ISchedulingRule;
import org.eclipse.core.runtime.jobs.Job;
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, IProgressMonitor)}
* 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$
  // whether the resources plugin is in debug mode.
  public static boolean DEBUG = false;

  /**
   * 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 IProject[] buildOrder = null;
  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 HashSet lifecycleListeners = new HashSet(10);
  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 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
   */
  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 hand both concurrent
   * reads and concurrent writes, write access to the tree is governed
   * by {@link WorkManager}.
   */
  protected 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 Thread treeLocked = null;

  /**
   * The currently installed file modification validator.
   */
  protected IFileModificationValidator validator = null;

  /**
   * 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) {
    boolean result = clearChildren(root);
    try {
      if (root.exists())
        result &= root.delete();
    } catch (Exception e) {
      result = false;
    }
    return result;
  }

  /**
   * Deletes all the files and directories from the given root down, except for
   * the root itself.
   * 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 clearChildren(java.io.File root) {
    boolean result = true;
    if (root.isDirectory()) {
      String[] list = root.list();
      // for some unknown reason, list() can return null. 
      // Just skip the children If it does.
      if (list != null)
        for (int i = 0; i < list.length; i++)
          result &= clear(new java.io.File(root, list[i]));
    }
    return result;
  }

  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.add(listener);
  }

  /* (non-Javadoc)
   * @see IWorkspace#addResourceChangeListener(IResourceChangeListener)
   */
  public void addResourceChangeListener(IResourceChangeListener listener) {
    notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
  }

  /* (non-Javadoc)
   * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int)
   */
  public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) {
    notificationManager.addListener(listener, eventMask);
  }

  /* (non-Javadoc)
   * @see IWorkspace#addSaveParticipant(Plugin, ISaveParticipant)
   */
  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, 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 (Iterator it = lifecycleListeners.iterator(); it.hasNext();) {
      ILifecycleListener listener = (ILifecycleListener) it.next();
      listener.handleEvent(event);
    }
  }

  public void broadcastPostChange() {
    ResourceChangeEvent event = new ResourceChangeEvent(this, IResourceChangeEvent.POST_CHANGE, 0, null);
    notificationManager.broadcastChanges(tree, event, true);
  }

  /* (non-Javadoc)
   * @see IWorkspace#build(int, IProgressMonitor)
   */
  public void build(int trigger, IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    final ISchedulingRule rule = getRuleFactory().buildRule();
    try {
      monitor.beginTask("", Policy.opWork); //$NON-NLS-1$
      try {
        prepareOperation(rule, monitor);
        beginOperation(true);
        aboutToBuild(this, trigger);
        IStatus result;
        try {
          result = getBuildManager().build(trigger, Policy.subMonitorFor(monitor, Policy.opWork));
        } finally {
          //must fire POST_BUILD if PRE_BUILD has occurred
          broadcastBuildEvent(this, IResourceChangeEvent.POST_BUILD, trigger);
        }
        if (!result.isOK())
          throw new ResourceException(result);
      } finally {
        //building may close the tree, but we are still inside an operation so open it
        if (tree.isImmutable())
          newWorkingTree();
        endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
      }
    } finally {
      monitor.done();
    }
  }

  /**
   * 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$
  }

  /* (non-Javadoc)
   * @see IWorkspace#checkpoint(boolean)
   */
  public void checkpoint(boolean build) {
    try {
      final ISchedulingRule rule = getWorkManager().getNotifyRule();
      try {
        prepareOperation(rule, null);
        beginOperation(true);
        broadcastPostChange();
      } finally {
        endOperation(rule, build, null);
      }
    } catch (CoreException e) {
      Policy.log(e.getStatus());
    }
  }

  /**
   * 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;
    monitor = Policy.monitorFor(monitor);
    try {
      String msg = Messages.resources_closing_0;
      int rootCount = tree.getChildCount(Path.ROOT);
      monitor.beginTask(msg, rootCount + 2);
      monitor.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);
        prepareOperation(getRoot(), monitor);
        //shutdown notification first to avoid calling third parties during shutdown
        notificationManager.shutdown(null);
        beginOperation(true);
        IProject[] projects = getRoot().getProjects();
        for (int i = 0; i < projects.length; i++) {
          //notify managers of closing so they can cleanup
          broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, projects[i]));
          monitor.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(Policy.subMonitorFor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
      }
    } finally {
      //release the scheduling rule to be a good job citizen
      Job.getJobManager().endRule(getRoot());
      monitor.done();
    }
  }

  /**
   * 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 ProjectOrder computeFullProjectOrder() {

    // determine the full set of accessible projects in the workspace
    // order the set in descending alphabetical order of project name
    SortedSet allAccessibleProjects = new TreeSet(new Comparator() {
      public int compare(Object x, Object y) {
        IProject px = (IProject) x;
        IProject py = (IProject) y;
        return py.getName().compareTo(px.getName());
      }
    });
    IProject[] allProjects = getRoot().getProjects();
    // List<IProject[]> edges
    List edges = new ArrayList(allProjects.length);
    for (int i = 0; i < allProjects.length; i++) {
      Project project = (Project) allProjects[i];
      // 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(false);
      allAccessibleProjects.add(project);
      for (int j = 0; j < refs.length; j++) {
        IProject ref = refs[j];
        // ignore self references and references to projects that are not accessible
        if (ref.isAccessible() && !ref.equals(project))
          edges.add(new IProject[] {project, ref});
      }
    }

    ProjectOrder fullProjectOrder = ComputeProjectOrder.computeProjectOrder(allAccessibleProjects, edges);
    return fullProjectOrder;
  }

  /**
   * Implementation of API method declared on IWorkspace.
   *
   * @see IWorkspace#computePrerequisiteOrder(IProject[])
   * @deprecated Replaced by <code>IWorkspace.computeProjectOrder</code>, which
   * produces a more usable result when there are cycles in project reference
   * graph.
   */
  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 bad = new HashSet();
    // Set<IProject> bad
    Set keepers = new HashSet(Arrays.asList(r.projects));
    for (int i = 0; i < r.knots.length; i++) {
      IProject[] knot = r.knots[i];
      for (int j = 0; j < knot.length; j++) {
        IProject project = knot[j];
        // 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 p = new LinkedList();
    p.addAll(Arrays.asList(r.projects));
    for (Iterator it = p.listIterator(); it.hasNext();) {
      IProject project = (IProject) 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};
  }

  /* (non-Javadoc)
   * @see IWorkspace#computeProjectOrder(IProject[])
   * @since 2.1
   */
  public ProjectOrder computeProjectOrder(IProject[] projects) {

    // compute the full project order for all accessible projects
    ProjectOrder fullProjectOrder = computeFullProjectOrder();

    // "fullProjectOrder.projects" contains no inaccessible projects
    // but might contain accessible projects omitted from "projects"
    // optimize common case where "projects" includes everything
    int accessibleCount = 0;
    for (int i = 0; i < projects.length; i++) {
      if (projects[i].isAccessible()) {
        accessibleCount++;
      }
    }
    // no filtering required if the subset accounts for the full list
    if (accessibleCount == fullProjectOrder.projects.length) {
      return fullProjectOrder;
    }

    // otherwise we need to eliminate mention of other projects...
    // ... from "fullProjectOrder.projects"...   
    // Set<IProject> keepers
    Set keepers = new HashSet(Arrays.asList(projects));
    // List<IProject> projects
    List reducedProjects = new ArrayList(fullProjectOrder.projects.length);
    for (int i = 0; i < fullProjectOrder.projects.length; i++) {
      IProject project = fullProjectOrder.projects[i];
      if (keepers.contains(project)) {
        // remove projects not in the initial subset
        reducedProjects.add(project);
      }
    }
    IProject[] p1 = new IProject[reducedProjects.size()];
    reducedProjects.toArray(p1);

    // ... and from "fullProjectOrder.knots"   
    // List<IProject[]> knots
    List reducedKnots = new ArrayList(fullProjectOrder.knots.length);
    for (int i = 0; i < fullProjectOrder.knots.length; i++) {
      IProject[] knot = fullProjectOrder.knots[i];
      List x = new ArrayList(knot.length);
      for (int j = 0; j < knot.length; j++) {
        IProject project = knot[j];
        if (keepers.contains(project)) {
          x.add(project);
        }
      }
      // keep knots containing 2 or more projects in the specified subset
      if (x.size() > 1) {
        reducedKnots.add(x.toArray(new IProject[x.size()]));
      }
    }
    IProject[][] k1 = new IProject[reducedKnots.size()][];
    // okay to use toArray here because reducedKnots elements are IProject[]
    reducedKnots.toArray(k1);
    return new ProjectOrder(p1, (k1.length > 0), k1);
  }

  /* (non-Javadoc)
   * @see IWorkspace#copy(IResource[], IPath, boolean, IProgressMonitor)
   */
  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);
  }

  /* (non-Javadoc)
   * @see IWorkspace#copy(IResource[], IPath, int, IProgressMonitor)
   */
  public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    try {
      Assert.isLegal(resources != null);
      int opWork = Math.max(resources.length, 1);
      int totalWork = Policy.totalWork * opWork / Policy.opWork;
      String message = Messages.resources_copying_0;
      monitor.beginTask(message, totalWork);
      if (resources.length == 0)
        return Status.OK_STATUS;
      // to avoid concurrent changes to this array
      resources = (IResource[]) resources.clone();
      IPath parentPath = null;
      message = Messages.resources_copyProblem;
      MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
      try {
        prepareOperation(getRoot(), monitor);
        beginOperation(true);
        for (int i = 0; i < resources.length; i++) {
          Policy.checkCanceled(monitor);
          IResource resource = resources[i];
          if (resource == null || isDuplicate(resources, i)) {
            monitor.worked(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 {
                monitor.worked(1);
                status.merge(requirements);
              }
            } catch (CoreException e) {
              monitor.worked(1);
              status.merge(e.getStatus());
            }
          } else {
            monitor.worked(1);
            message = NLS.bind(Messages.resources_notChild, resources[i].getFullPath(), parentPath);
            status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message));
          }
        }
      } catch (OperationCanceledException e) {
        getWorkManager().operationCanceled();
        throw e;
      } finally {
        endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
      }
      if (status.matches(IStatus.ERROR))
        throw new ResourceException(status);
      return status.isOK() ? Status.OK_STATUS : (IStatus) status;
    } finally {
      monitor.done();
    }
  }

  protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) 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, false, 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;
      if ((updateFlags & IResource.SHALLOW) != 0) {
        //for shallow move the destination is a linked resource with the same location
        newInfo.set(ICoreConstants.M_LINK);
        linkDescription = new LinkDescription(destinationResource, source.getLocationURI());
      } else {
        //for deep move the destination is not a linked resource
        newInfo.clear(ICoreConstants.M_LINK);
        linkDescription = null;
      }
      Project project = (Project) destinationResource.getProject();
      project.internalGetDescription().setLinkLocation(destinationResource.getProjectRelativePath(), linkDescription);
      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);
    }
    IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
    for (int i = 0, imax = children.length; i < imax; i++) {
      String childName = children[i].getName();
      if (!projectCopy || !childName.equals(IProjectDescription.DESCRIPTION_FILE_NAME)) {
        IPath childPath = destination.append(childName);
        copyTree(children[i], childPath, depth, updateFlags, keepSyncInfo);
      }
    }
  }

  /**
   * 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 = new IElementContentVisitor() {
          public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object 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 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);
    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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#delete(IResource[], boolean, IProgressMonitor)
   */
  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);
  }

  /* (non-Javadoc)
   * @see IWorkspace#delete(IResource[], int, IProgressMonitor)
   */
  public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    try {
      int opWork = Math.max(resources.length, 1);
      int totalWork = Policy.totalWork * opWork / Policy.opWork;
      String message = Messages.resources_deleting_0;
      monitor.beginTask(message, totalWork);
      message = Messages.resources_deleteProblem;
      MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
      if (resources.length == 0)
        return result;
      resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
      try {
        prepareOperation(getRoot(), monitor);
        beginOperation(true);
        for (int i = 0; i < resources.length; i++) {
          Policy.checkCanceled(monitor);
          Resource resource = (Resource) resources[i];
          if (resource == null) {
            monitor.worked(1);
            continue;
          }
          try {
            resource.delete(updateFlags, Policy.subMonitorFor(monitor, 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());
            }
          }
        }
        if (result.matches(IStatus.ERROR))
          throw new ResourceException(result);
        return result;
      } catch (OperationCanceledException e) {
        getWorkManager().operationCanceled();
        throw e;
      } finally {
        endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
      }
    } finally {
      monitor.done();
    }
  }

  /* (non-Javadoc)
   * @see IWorkspace#deleteMarkers(IMarker[])
   */
  public void deleteMarkers(IMarker[] markers) throws CoreException {
    Assert.isNotNull(markers);
    if (markers.length == 0)
      return;
    // clone to avoid outside changes
    markers = (IMarker[]) markers.clone();
    try {
      prepareOperation(null, null);
      beginOperation(true);
      for (int i = 0; i < markers.length; ++i)
        if (markers[i] != null && markers[i].getResource() != null)
          markerManager.removeMarker(markers[i].getResource(), markers[i].getId());
    } finally {
      endOperation(null, false, null);
    }
  }

  /**
   * 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();
      for (int i = 0; i < children.length; i++)
        tree.deleteElement(children[i].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, IProgressMonitor monitor) 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.  Only needed if the
   * description does not already have a build order.  That is, if this
   * is really a cache.
   */
  protected void flushBuildOrder() {
    if (description.getBuildOrder(false) == null)
      buildOrder = null;
  }

  /* (non-Javadoc)
   * @see IWorkspace#forgetSavedTree(String)
   */
  public void forgetSavedTree(String pluginId) {
    Assert.isNotNull(pluginId, "PluginId must not be null"); //$NON-NLS-1$
    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.
   * <p>
   * The project build order is based on information specified in the workspace
   * description. The projects 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
   * <code>IWorkspaceDescription.getBuildOrder</code> is non-null, the default
   * build order is used; again, only open projects 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 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()
   * @since 2.1
   */
  public IProject[] getBuildOrder() {
    if (buildOrder != null) {
      // return previously-computed and cached project build order
      return buildOrder;
    }
    // see if a particular build order is specified
    String[] order = description.getBuildOrder(false);
    if (order != null) {
      // convert from project names to project handles
      // and eliminate non-existent and closed projects
      List projectList = new ArrayList(order.length);
      for (int i = 0; i < order.length; i++) {
        IProject project = getRoot().getProject(order[i]);
        if (project.isAccessible()) {
          projectList.add(project);
        }
      }
      buildOrder = new IProject[projectList.size()];
      projectList.toArray(buildOrder);
    } else {
      // use default project build order
      // computed for all accessible projects in workspace
      buildOrder = computeFullProjectOrder().projects;
    }
    return buildOrder;
  }

  public CharsetManager getCharsetManager() {
    return charsetManager;
  }

  public ContentDescriptionManager getContentDescriptionManager() {
    return contentDescriptionManager;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getDanglingReferences()
   */
  public Map getDanglingReferences() {
    IProject[] projects = getRoot().getProjects();
    Map result = new HashMap(projects.length);
    for (int i = 0; i < projects.length; i++) {
      Project project = (Project) projects[i];
      if (!project.isAccessible())
        continue;
      IProject[] refs = project.internalGetDescription().getReferencedProjects(false);
      List dangling = new ArrayList(refs.length);
      for (int j = 0; j < refs.length; j++)
        if (!refs[i].exists())
          dangling.add(refs[i]);
      if (!dangling.isEmpty())
        result.put(projects[i], dangling.toArray(new IProject[dangling.size()]));
    }
    return result;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getDescription()
   */
  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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getNatureDescriptor(String)
   */
  public IProjectNatureDescriptor getNatureDescriptor(String natureId) {
    return natureManager.getNatureDescriptor(natureId);
  }

  /* (non-Javadoc)
   * @see IWorkspace#getNatureDescriptors()
   */
  public IProjectNatureDescriptor[] getNatureDescriptors() {
    return natureManager.getNatureDescriptors();
  }

  /**
   * Returns the nature manager for this workspace.
   */
  public NatureManager getNatureManager() {
    return natureManager;
  }

  public NotificationManager getNotificationManager() {
    return notificationManager;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getPathVariableManager()
   */
  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;
    }
  }

  /* (non-Javadoc)
   * @see IWorkspace#getRoot()
   */
  public IWorkspaceRoot getRoot() {
    return defaultRoot;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getRuleFactory()
   */
  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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#getSynchronizer()
   */
  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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#isAutoBuilding()
   */
  public boolean isAutoBuilding() {
    return description.isAutoBuilding();
  }

  public boolean isOpen() {
    return openFlag;
  }

  /* (non-Javadoc)
   * @see IWorkspace#isTreeLocked()
   */
  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);
  }

  /* (non-Javadoc)
   * @see IWorkspace#loadProjectDescription(InputStream)
   * @since 3.1
   */
  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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#loadProjectDescription(IPath)
   * @since 2.0
   */
  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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#move(IResource[], IPath, boolean, IProgressMonitor)
   */
  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);
  }

  /* (non-Javadoc)
   * @see IWorkspace#move(IResource[], IPath, int, IProgressMonitor)
   */
  public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    try {
      Assert.isLegal(resources != null);
      int opWork = Math.max(resources.length, 1);
      int totalWork = Policy.totalWork * opWork / Policy.opWork;
      String message = Messages.resources_moving_0;
      monitor.beginTask(message, totalWork);
      if (resources.length == 0)
        return Status.OK_STATUS;
      resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
      IPath parentPath = null;
      message = Messages.resources_moveProblem;
      MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
      try {
        prepareOperation(getRoot(), monitor);
        beginOperation(true);
        for (int i = 0; i < resources.length; i++) {
          Policy.checkCanceled(monitor);
          Resource resource = (Resource) resources[i];
          if (resource == null || isDuplicate(resources, i)) {
            monitor.worked(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, Policy.subMonitorFor(monitor, 1));
                } catch (CoreException e) {
                  status.merge(e.getStatus());
                }
              } else {
                monitor.worked(1);
                status.merge(requirements);
              }
            } catch (CoreException e) {
              monitor.worked(1);
              status.merge(e.getStatus());
            }
          } else {
            monitor.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 {
        endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
      }
      if (status.matches(IStatus.ERROR))
        throw new ResourceException(status);
      return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status;
    } finally {
      monitor.done();
    }
  }

  /**
   * 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);
    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;
  }

  /* (non-Javadoc)
   * @see IWorkspace#newProjectDescription(String)
   */
  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();

    IProgressMonitor nullMonitor = Policy.monitorFor(null);
    startup(nullMonitor);
    //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 (rule != null)
        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 (int i = 0; i < args.length; i++)
      if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP))
        return true;
    return false;
  }

  /* (non-Javadoc)
   * @see IWorkspace#removeResourceChangeListener(IResourceChangeListener)
   */
  public void removeResourceChangeListener(IResourceChangeListener listener) {
    notificationManager.removeListener(listener);
  }

  /* (non-Javadoc)
   * @see IWorkspace#removeSaveParticipant(Plugin)
   */
  public void removeSaveParticipant(Plugin plugin) {
    Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$
    saveManager.removeParticipant(plugin);
  }

  /* (non-Javadoc)
   * @see IWorkspace#run(IWorkspaceRunnable, IProgressMonitor)
   */
  public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException {
    run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor);
  }

  /* (non-Javadoc)
   * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, IProgressMonitor)
   */
  public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    try {
      monitor.beginTask("", Policy.totalWork); //$NON-NLS-1$
      int depth = -1;
      boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0;
      try {
        prepareOperation(rule, monitor);
        beginOperation(true);
        if (avoidNotification)
          avoidNotification = notificationManager.beginAvoidNotify();
        depth = getWorkManager().beginUnprotected();
        action.run(Policy.subMonitorFor(monitor, Policy.opWork, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
      } catch (OperationCanceledException e) {
        getWorkManager().operationCanceled();
        throw e;
      } finally {
        if (avoidNotification)
          notificationManager.endAvoidNotify();
        if (depth >= 0)
          getWorkManager().endUnprotected(depth);
        endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.endOpWork));
      }
    } finally {
      monitor.done();
    }
  }

  /* (non-Javadoc)
   * @see IWorkspace#save(boolean, IProgressMonitor)
   */
  public IStatus save(boolean full, 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, 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, null);
    }
  }

  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)
        System.out.println(msg);
    }
  }

  /* (non-Javadoc)
   * @see IWorkspace#setDescription(IWorkspaceDescription)
   */
  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)
      buildOrder = null;
    description.copyFrom(newDescription);
    ResourcesPlugin.getPlugin().savePluginPreferences();
  }

  public void setTreeLocked(boolean locked) {
    treeLocked = locked ? Thread.currentThread() : null;
  }

  /**
   * @deprecated
   */
  public void setWorkspaceLock(WorkspaceLock lock) {
    // do nothing
  }

  /**
   * Shuts down the workspace managers.
   */
  protected void shutdown(IProgressMonitor monitor) throws CoreException {
    monitor = Policy.monitorFor(monitor);
    try {
      IManager[] managers = {buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, markerManager, _workManager, aliasManager, refreshManager, contentDescriptionManager};
      monitor.beginTask("", managers.length); //$NON-NLS-1$
      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 (int i = 0; i < managers.length; i++) {
        IManager manager = managers[i];
        if (manager == null)
          monitor.worked(1);
        else {
          try {
            manager.shutdown(Policy.subMonitorFor(monitor, 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 {
      monitor.done();
    }
  }

  /* (non-Javadoc)
   * @see IWorkspace#sortNatureSet(String[])
   */
  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);
      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);
      refreshManager = new RefreshManager(this);
      saveManager = new SaveManager(this);
      saveManager.startup(null);
      //must start after save manager, because (read) access to tree is needed
      refreshManager.startup(null);
      aliasManager = new AliasManager(this);
      aliasManager.startup(null);
      propertyManager = ResourcesCompatibilityHelper.createPropertyManager();
      propertyManager.startup(monitor);
      charsetManager = new CharsetManager(this);
      charsetManager.startup(null);
      contentDescriptionManager = new ContentDescriptionManager();
      contentDescriptionManager.startup(null);
    } finally {
      //unlock tree even in case of failure, otherwise shutdown will also fail
      treeLocked = null;
    }
  }

  /**
   * Returns a string representation of this working state's
   * structure suitable for debug purposes.
   */
  public String toDebugString() {
    final StringBuffer buffer = new StringBuffer("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
    buffer.append("  parent: " + tree.getParent()); //$NON-NLS-1$
    IElementContentVisitor visitor = new IElementContentVisitor() {
      public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object 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();
  }

  /* (non-javadoc)
   * @see IWorkspace#validateEdit(IFile[], Object)
   */
  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 (int i = 0; i < files.length; i++) {
        if (files[i].isReadOnly()) {
          IPath filePath = files[i].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() {
      public void handleException(Throwable exception) {
        status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception);
      }

      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];
  }

  /* (non-Javadoc)
   * @see IWorkspace#validateLinkLocation(IResource, IPath)
   */
  public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) {
    return locationValidator.validateLinkLocation(resource, unresolvedLocation);
  }

  /* (non-Javadoc)
   * @see IWorkspace#validateLinkLocation(IResource, IPath)
   */
  public IStatus validateLinkLocationURI(IResource resource, URI unresolvedLocation) {
    return locationValidator.validateLinkLocationURI(resource, unresolvedLocation);
  }

  /* (non-Javadoc)
   * @see IWorkspace#validateName(String, int)
   */
  public IStatus validateName(String segment, int type) {
    return locationValidator.validateName(segment, type);
  }

  /* (non-Javadoc)
   * @see IWorkspace#validateNatureSet(String[])
   */
  public IStatus validateNatureSet(String[] natureIds) {
    return natureManager.validateNatureSet(natureIds);
  }

  /* (non-Javadoc)
   * @see IWorkspace#validatePath(String, int)
   */
  public IStatus validatePath(String path, int type) {
    return locationValidator.validatePath(path, type);
  }

  /* (non-Javadoc)
   * @see IWorkspace#validateProjectLocation(IProject, IPath)
   */
  public IStatus validateProjectLocation(IProject context, IPath location) {
    return locationValidator.validateProjectLocation(context, location);
  }

  /*
   * (non-Javadoc)
   * @see org.eclipse.core.resources.IWorkspace#validateProjectLocation(org.eclipse.core.resources.IProject, java.net.URI)
   */
  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() {
      public void handleException(Throwable exception) {
        status[0] = new ResourceStatus(IStatus.ERROR, null, Messages.resources_errorValidator, exception);
      }

      public void run() throws Exception {
        status[0] = validator.validateSave(file);
      }
    };
    SafeRunner.run(body);
    if (!status[0].isOK())
      throw new ResourceException(status[0]);
  }

}
TOP

Related Classes of org.eclipse.core.internal.resources.Workspace

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.