Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/eclipse/core/internal/resources/Workspace.java


1   /*******************************************************************************
2    * Copyright (c) 2000, 2004 IBM Corporation and others.
3    * All rights reserved.   This program and the accompanying materials
4    * are made available under the terms of the Common Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/cpl-v10.html
7    * 
8    * Contributors:
9    *     IBM Corporation - initial API and implementation
10   *******************************************************************************/
11  package org.eclipse.core.internal.resources;
12  
13  import java.io.IOException;
14  import java.util.*;
15  import org.eclipse.core.internal.events.*;
16  import org.eclipse.core.internal.localstore.CoreFileSystemLibrary;
17  import org.eclipse.core.internal.localstore.FileSystemResourceManager;
18  import org.eclipse.core.internal.properties.PropertyManager;
19  import org.eclipse.core.internal.refresh.RefreshManager;
20  import org.eclipse.core.internal.utils.Assert;
21  import org.eclipse.core.internal.utils.Policy;
22  import org.eclipse.core.internal.watson.*;
23  import org.eclipse.core.resources.*;
24  import org.eclipse.core.resources.team.IMoveDeleteHook;
25  import org.eclipse.core.resources.team.TeamHook;
26  import org.eclipse.core.runtime.*;
27  import org.eclipse.core.runtime.jobs.ISchedulingRule;
28  
29  public class Workspace extends PlatformObject implements IWorkspace, ICoreConstants {
30    // whether the resources plugin is in debug mode.
31    public static boolean DEBUG = false;
32    protected static final String REFRESH_ON_STARTUP = "-refresh"; //$NON-NLS-1$
33    protected WorkspacePreferences description;
34    protected LocalMetaArea localMetaArea;
35    protected boolean openFlag = false;
36    protected ElementTree tree;
37    protected ElementTree operationTree; // tree at the start of the current operation
38    protected SaveManager saveManager;
39    protected BuildManager buildManager;
40    protected NatureManager natureManager;
41    protected NotificationManager notificationManager;
42    protected FileSystemResourceManager fileSystemManager;
43    protected PathVariableManager pathVariableManager;
44    protected PropertyManager propertyManager;
45    protected MarkerManager markerManager;
46    protected ContentDescriptionManager contentDescriptionManager;
47    /**
48     * Work manager should never be accessed directly because accessor
49     * asserts that workspace is still open.
50     */
51    protected WorkManager _workManager;
52    protected AliasManager aliasManager;
53    protected CharsetManager charsetManager;
54    protected RefreshManager refreshManager;
55  
56    protected long nextNodeId = 1;
57    protected long nextModificationStamp = 0;
58    protected long nextMarkerId = 0;
59    protected Synchronizer synchronizer;
60    protected IProject[] buildOrder = null;
61    protected IWorkspaceRoot defaultRoot = new WorkspaceRoot(Path.ROOT, this);
62  
63    protected final HashSet lifecycleListeners = new HashSet(10);
64  
65    /**
66     * Scheduling rule factory
67     */
68    private IResourceRuleFactory ruleFactory;
69  
70    /**
71     * File modification validation.  If it is true and validator is null, we try/initialize 
72     * validator first time through.  If false, there is no validator.
73     */
74    protected boolean shouldValidate = true;
75    /**
76     * The currently installed file modification validator.
77     */
78    protected IFileModificationValidator validator = null;
79  
80    /**
81     * The currently installed Move/Delete hook.
82     */
83    protected IMoveDeleteHook moveDeleteHook = null;
84  
85    /**
86     * The currently installed team hook.
87     */
88    protected TeamHook teamHook = null;
89  
90    /**
91     * This field is used to control access to the workspace tree during
92     * resource change notifications. It tracks which thread, if any, is
93     * in the middle of a resource change notification.  This is used to cause
94     * attempts to modify the workspace during notifications to fail.
95     */
96    protected Thread treeLocked = null;
97  
98    /** indicates if the workspace crashed in a previous session */
99    protected boolean crashed = false;
100 
101   public Workspace() {
102     super();
103     localMetaArea = new LocalMetaArea();
104     tree = new ElementTree();
105     /* tree should only be modified during operations */
106     tree.immutable();
107     treeLocked = Thread.currentThread();
108     tree.setTreeData(newElement(IResource.ROOT));
109   }
110 
111   /**
112    * Indicates that a build is about to occur. Broadcasts the necessary
113    * deltas before the build starts. Note that this will cause POST_BUILD
114    * to be automatically done at the end of the operation in which
115    * the build occurs.
116    */
117   protected void aboutToBuild() throws CoreException {
118     //fire a POST_CHANGE first to ensure everyone is up to date
119     //before firing PRE_BUILD
120     broadcastChanges(IResourceChangeEvent.POST_CHANGE, true);
121     broadcastChanges(IResourceChangeEvent.PRE_BUILD, false);
122   }
123 
124   /**
125    * Adds a listener for internal workspace lifecycle events.  There is no way to
126    * remove lifecycle listeners.
127    */
128   public void addLifecycleListener(ILifecycleListener listener) {
129     lifecycleListeners.add(listener);
130   }
131 
132   /* (non-Javadoc)
133    * @see IWorkspace#addResourceChangeListener(IResourceChangeListener)
134    */
135   public void addResourceChangeListener(IResourceChangeListener listener) {
136     notificationManager.addListener(listener, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.PRE_DELETE | IResourceChangeEvent.POST_CHANGE);
137   }
138 
139   /* (non-Javadoc)
140    * @see IWorkspace#addResourceChangeListener(IResourceChangeListener, int)
141    */
142   public void addResourceChangeListener(IResourceChangeListener listener, int eventMask) {
143     notificationManager.addListener(listener, eventMask);
144   }
145 
146   /* (non-Javadoc)
147    * @see IWorkspace#addSaveParticipant(Plugin, ISaveParticipant)
148    */
149   public ISavedState addSaveParticipant(Plugin plugin, ISaveParticipant participant) throws CoreException {
150     Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$
151     Assert.isNotNull(participant, "Participant must not be null"); //$NON-NLS-1$
152     return saveManager.addParticipant(plugin, participant);
153   }
154 
155   public void beginOperation(boolean createNewTree) throws CoreException {
156     WorkManager workManager = getWorkManager();
157     workManager.incrementNestedOperations();
158     if (!workManager.isBalanced())
159       Assert.isTrue(false, "Operation was not prepared."); //$NON-NLS-1$
160     if (workManager.getPreparedOperationDepth() > 1) {
161       if (createNewTree && tree.isImmutable())
162         newWorkingTree();
163       return;
164     }
165     // stash the current tree as the basis for this operation.
166     operationTree = tree;
167     if (createNewTree && tree.isImmutable())
168       newWorkingTree();
169   }
170 
171   protected void broadcastChanges(int type, boolean lockTree) throws CoreException {
172     notificationManager.broadcastChanges(tree, type, lockTree);
173   }
174 
175   /**
176    * Broadcasts an internal workspace lifecycle event to interested
177    * internal listeners.
178    */
179   protected void broadcastEvent(LifecycleEvent event) throws CoreException {
180     for (Iterator it = lifecycleListeners.iterator(); it.hasNext();) {
181       ILifecycleListener listener = (ILifecycleListener) it.next();
182       listener.handleEvent(event);
183     }
184   }
185 
186   /* (non-Javadoc)
187    * @see IWorkspace#build(int, IProgressMonitor)
188    */
189   public void build(int trigger, IProgressMonitor monitor) throws CoreException {
190     monitor = Policy.monitorFor(monitor);
191     final ISchedulingRule rule = getRuleFactory().buildRule();
192     try {
193       monitor.beginTask(null, Policy.opWork);
194       try {
195         prepareOperation(rule, monitor);
196         beginOperation(true);
197         aboutToBuild();
198         getBuildManager().build(trigger, Policy.subMonitorFor(monitor, Policy.opWork));
199         broadcastChanges(IResourceChangeEvent.POST_BUILD, false);
200       } finally {
201         //building may close the tree, but we are still inside an operation so open it
202         if (tree.isImmutable())
203           newWorkingTree();
204         endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.buildWork));
205       }
206     } finally {
207       monitor.done();
208     }
209   }
210 
211   /* (non-Javadoc)
212    * @see IWorkspace#checkpoint(boolean)
213    */
214   public void checkpoint(boolean build) {
215     try {
216       final ISchedulingRule rule = getWorkManager().getNotifyRule();
217       try {
218         prepareOperation(rule, null);
219         beginOperation(true);
220         broadcastChanges(IResourceChangeEvent.POST_CHANGE, true);
221       } finally {
222         endOperation(rule, build, null);
223       }
224     } catch (CoreException e) {
225       ResourcesPlugin.getPlugin().getLog().log(e.getStatus());
226     }
227   }
228 
229   /**
230    * Deletes all the files and directories from the given root down (inclusive).
231    * Returns false if we could not delete some file or an exception occurred
232    * at any point in the deletion.
233    * Even if an exception occurs, a best effort is made to continue deleting.
234    */
235   public static boolean clear(java.io.File root) {
236     boolean result = clearChildren(root);
237     try {
238       if (root.exists())
239         result &= root.delete();
240     } catch (Exception e) {
241       result = false;
242     }
243     return result;
244   }
245 
246   /**
247    * Deletes all the files and directories from the given root down, except for 
248    * the root itself.
249    * Returns false if we could not delete some file or an exception occurred
250    * at any point in the deletion.
251    * Even if an exception occurs, a best effort is made to continue deleting.
252    */
253   public static boolean clearChildren(java.io.File root) {
254     boolean result = true;
255     if (root.isDirectory()) {
256       String[] list = root.list();
257       // for some unknown reason, list() can return null.  
258       // Just skip the children If it does.
259       if (list != null)
260         for (int i = 0; i < list.length; i++)
261           result &= clear(new java.io.File(root, list[i]));
262     }
263     return result;
264   }
265 
266   /**
267    * Closes this workspace; ignored if this workspace is not open.
268    * The state of this workspace is not saved before the workspace
269    * is shut down.
270    * <p> 
271    * If the workspace was saved immediately prior to closing,
272    * it will have the same set of projects
273    * (open or closed) when reopened for a subsequent session.
274    * Otherwise, closing a workspace may lose some or all of the
275    * changes made since the last save or snapshot.
276    * </p>
277    * <p>
278    * Note that session properties are discarded when a workspace is closed.
279    * </p>
280    * <p>
281    * This method is long-running; progress and cancellation are provided
282    * by the given progress monitor.
283    * </p>
284    *
285    * @param monitor a progress monitor, or <code>null</code> if progress
286    *    reporting and cancellation are not desired
287    * @exception CoreException if the workspace could not be shutdown.
288    */
289   public void close(IProgressMonitor monitor) throws CoreException {
290     monitor = Policy.monitorFor(monitor);
291     try {
292       String msg = Policy.bind("resources.closing.0"); //$NON-NLS-1$
293       int rootCount = tree.getChildCount(Path.ROOT);
294       monitor.beginTask(msg, rootCount + 2);
295       monitor.subTask(msg);
296       //this operation will never end because the world is going away
297       try {
298         prepareOperation(getRoot(), monitor);
299         //shutdown notification first to avoid calling third parties during shutdown
300         notificationManager.shutdown(null);
301         if (isOpen()) {
302           beginOperation(true);
303           IProject[] projects = getRoot().getProjects();
304           for (int i = 0; i < projects.length; i++) {
305             //notify managers of closing so they can cleanup
306             broadcastEvent(LifecycleEvent.newEvent(LifecycleEvent.PRE_PROJECT_CLOSE, projects[i]));
307             monitor.worked(1);
308           }
309           //empty the workspace tree so we leave in a clean state
310           deleteResource(getRoot());
311           openFlag = false;
312         }
313         // endOperation not needed here
314       } finally {
315         // Shutdown needs to be executed anyway. Doesn't matter if the workspace was not open.
316         shutdown(Policy.subMonitorFor(monitor, 2, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
317       }
318     } finally {
319       monitor.done();
320     }
321   }
322 
323   /**
324    * Implementation of API method declared on IWorkspace.
325    * 
326    * @see IWorkspace#computePrerequisiteOrder(IProject[])
327    * @deprecated Replaced by <code>IWorkspace.computeProjectOrder</code>, which
328    * produces a more usable result when there are cycles in project reference
329    * graph.
330    */
331   public IProject[][] computePrerequisiteOrder(IProject[] targets) {
332     return computePrerequisiteOrder1(targets);
333   }
334 
335   /*
336    * Compatible reimplementation of 
337    * <code>IWorkspace.computePrerequisiteOrder</code> using 
338    * <code>IWorkspace.computeProjectOrder</code>.
339    * 
340    * @since 2.1
341    */
342   private IProject[][] computePrerequisiteOrder1(IProject[] projects) {
343     IWorkspace.ProjectOrder r = computeProjectOrder(projects);
344     if (!r.hasCycles) {
345       return new IProject[][] {r.projects, new IProject[0]};
346     }
347     // when there are cycles, we need to remove all knotted projects from
348     // r.projects to form result[0] and merge all knots to form result[1]
349     // Set<IProject> bad
350     Set bad = new HashSet();
351     // Set<IProject> bad
352     Set keepers = new HashSet(Arrays.asList(r.projects));
353     for (int i = 0; i < r.knots.length; i++) {
354       IProject[] knot = r.knots[i];
355       for (int j = 0; j < knot.length; j++) {
356         IProject project = knot[j];
357         // keep only selected projects in knot
358         if (keepers.contains(project)) {
359           bad.add(project);
360         }
361       }
362     }
363     IProject[] result2 = new IProject[bad.size()];
364     bad.toArray(result2);
365     // List<IProject> p
366     List p = new LinkedList();
367     p.addAll(Arrays.asList(r.projects));
368     for (Iterator it = p.listIterator(); it.hasNext();) {
369       IProject project = (IProject) it.next();
370       if (bad.contains(project)) {
371         // remove knotted projects from the main answer
372         it.remove();
373       }
374     }
375     IProject[] result1 = new IProject[p.size()];
376     p.toArray(result1);
377     return new IProject[][] {result1, result2};
378   }
379 
380   /* (non-Javadoc)
381    * @see IWorkspace#computeProjectOrder(IProject[])
382    * @since 2.1
383    */
384   public ProjectOrder computeProjectOrder(IProject[] projects) {
385 
386     // compute the full project order for all accessible projects
387     ProjectOrder fullProjectOrder = computeFullProjectOrder();
388 
389     // "fullProjectOrder.projects" contains no inaccessible projects
390     // but might contain accessible projects omitted from "projects"
391     // optimize common case where "projects" includes everything
392     int accessibleCount = 0;
393     for (int i = 0; i < projects.length; i++) {
394       if (projects[i].isAccessible()) {
395         accessibleCount++;
396       }
397     }
398     // no filtering required if the subset accounts for the full list
399     if (accessibleCount == fullProjectOrder.projects.length) {
400       return fullProjectOrder;
401     }
402 
403     // otherwise we need to eliminate mention of other projects...
404     // ... from "fullProjectOrder.projects"...    
405     // Set<IProject> keepers
406     Set keepers = new HashSet(Arrays.asList(projects));
407     // List<IProject> p
408     List reducedProjects = new ArrayList(fullProjectOrder.projects.length);
409     for (int i = 0; i < fullProjectOrder.projects.length; i++) {
410       IProject project = fullProjectOrder.projects[i];
411       if (keepers.contains(project)) {
412         // remove projects not in the initial subset
413         reducedProjects.add(project);
414       }
415     }
416     IProject[] p1 = new IProject[reducedProjects.size()];
417     reducedProjects.toArray(p1);
418 
419     // ... and from "fullProjectOrder.knots"    
420     // List<IProject[]> k
421     List reducedKnots = new ArrayList(fullProjectOrder.knots.length);
422     for (int i = 0; i < fullProjectOrder.knots.length; i++) {
423       IProject[] knot = fullProjectOrder.knots[i];
424       List x = new ArrayList(knot.length);
425       for (int j = 0; j < knot.length; j++) {
426         IProject project = knot[j];
427         if (keepers.contains(project)) {
428           x.add(project);
429         }
430       }
431       // keep knots containing 2 or more projects in the specified subset
432       if (x.size() > 1) {
433         reducedKnots.add(x.toArray(new IProject[x.size()]));
434       }
435     }
436     IProject[][] k1 = new IProject[reducedKnots.size()][];
437     // okay to use toArray here because reducedKnots elements are IProject[]
438     reducedKnots.toArray(k1);
439     return new ProjectOrder(p1, (k1.length > 0), k1);
440   }
441 
442   /**
443    * Computes the global total ordering of all open projects in the
444    * workspace based on project references. If an existing and open project P
445    * references another existing and open project Q also included in the list,
446    * then Q should come before P in the resulting ordering. Closed and non-
447    * existent projects are ignored, and will not appear in the result. References
448    * to non-existent or closed projects are also ignored, as are any self-
449    * references.
450    * <p>
451    * When there are choices, the choice is made in a reasonably stable way. For
452    * example, given an arbitrary choice between two projects, the one with the
453    * lower collating project name is usually selected.
454    * </p>
455    * <p>
456    * When the project reference graph contains cyclic references, it is
457    * impossible to honor all of the relationships. In this case, the result
458    * ignores as few relationships as possible.  For example, if P2 references P1,
459    * P4 references P3, and P2 and P3 reference each other, then exactly one of the
460    * relationships between P2 and P3 will have to be ignored. The outcome will be
461    * either [P1, P2, P3, P4] or [P1, P3, P2, P4]. The result also contains
462    * complete details of any cycles present.
463    * </p>
464    *
465    * @return result describing the global project order
466    * @since 2.1
467    */
468   private ProjectOrder computeFullProjectOrder() {
469 
470     // determine the full set of accessible projects in the workspace
471     // order the set in descending alphabetical order of project name
472     SortedSet allAccessibleProjects = new TreeSet(new Comparator() {
473       public int compare(Object x, Object y) {
474         IProject px = (IProject) x;
475         IProject py = (IProject) y;
476         return py.getName().compareTo(px.getName());
477       }
478     });
479     IProject[] allProjects = getRoot().getProjects();
480     // List<IProject[]> edges
481     List edges = new ArrayList(allProjects.length);
482     for (int i = 0; i < allProjects.length; i++) {
483       Project project = (Project) allProjects[i];
484       // ignore projects that are not accessible
485       if (!project.isAccessible())
486         continue;
487       ProjectDescription description = project.internalGetDescription();
488       if (description == null)
489         continue;
490       //obtain both static and dynamic project references
491       IProject[] refs = description.getAllReferences(false);
492       allAccessibleProjects.add(project);
493       for (int j = 0; j < refs.length; j++) {
494         IProject ref = refs[j];
495         // ignore self references and references to projects that are not accessible
496         if (ref.isAccessible() && !ref.equals(project))
497           edges.add(new IProject[] {project, ref});
498       }
499     }
500 
501     ProjectOrder fullProjectOrder = ComputeProjectOrder.computeProjectOrder(allAccessibleProjects, edges);
502     return fullProjectOrder;
503   }
504 
505   /* (non-Javadoc)
506    * @see IWorkspace#copy(IResource[], IPath, int, IProgressMonitor)
507    */
508   public IStatus copy(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
509     monitor = Policy.monitorFor(monitor);
510     try {
511       int opWork = Math.max(resources.length, 1);
512       int totalWork = Policy.totalWork * opWork / Policy.opWork;
513       String message = Policy.bind("resources.copying.0"); //$NON-NLS-1$
514       monitor.beginTask(message, totalWork);
515       Assert.isLegal(resources != null);
516       if (resources.length == 0)
517         return Status.OK_STATUS;
518       // to avoid concurrent changes to this array
519       resources = (IResource[]) resources.clone();
520       IPath parentPath = null;
521       message = Policy.bind("resources.copyProblem"); //$NON-NLS-1$
522       MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
523       try {
524         prepareOperation(getRoot(), monitor);
525         beginOperation(true);
526         for (int i = 0; i < resources.length; i++) {
527           Policy.checkCanceled(monitor);
528           IResource resource = resources[i];
529           if (resource == null || isDuplicate(resources, i)) {
530             monitor.worked(1);
531             continue;
532           }
533           // test siblings
534           if (parentPath == null)
535             parentPath = resource.getFullPath().removeLastSegments(1);
536           if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) {
537             // test copy requirements
538             try {
539               IPath destinationPath = destination.append(resource.getName());
540               IStatus requirements = ((Resource) resource).checkCopyRequirements(destinationPath, resource.getType(), updateFlags);
541               if (requirements.isOK()) {
542                 try {
543                   resource.copy(destinationPath, updateFlags, Policy.subMonitorFor(monitor, 1));
544                 } catch (CoreException e) {
545                   status.merge(e.getStatus());
546                 }
547               } else {
548                 monitor.worked(1);
549                 status.merge(requirements);
550               }
551             } catch (CoreException e) {
552               monitor.worked(1);
553               status.merge(e.getStatus());
554             }
555           } else {
556             monitor.worked(1);
557             message = Policy.bind("resources.notChild", resources[i].getFullPath().toString(), parentPath.toString()); //$NON-NLS-1$
558             status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resources[i].getFullPath(), message));
559           }
560         }
561       } catch (OperationCanceledException e) {
562         getWorkManager().operationCanceled();
563         throw e;
564       } finally {
565         endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
566       }
567       if (status.matches(IStatus.ERROR))
568         throw new ResourceException(status);
569       return status.isOK() ? Status.OK_STATUS : (IStatus) status;
570     } finally {
571       monitor.done();
572     }
573   }
574 
575   /* (non-Javadoc)
576    * @see IWorkspace#copy(IResource[], IPath, boolean, IProgressMonitor)
577    */
578   public IStatus copy(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
579     int updateFlags = force ? IResource.FORCE : IResource.NONE;
580     return copy(resources, destination, updateFlags, monitor);
581   }
582 
583   protected void copyTree(IResource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException {
584     // retrieve the resource at the destination if there is one (phantoms included).
585     // if there isn't one, then create a new handle based on the type that we are
586     // trying to copy
587     IResource destinationResource = getRoot().findMember(destination, true);
588     if (destinationResource == null) {
589       int destinationType;
590       if (source.getType() == IResource.FILE)
591         destinationType = IResource.FILE;
592       else if (destination.segmentCount() == 1)
593         destinationType = IResource.PROJECT;
594       else
595         destinationType = IResource.FOLDER;
596       destinationResource = newResource(destination, destinationType);
597     }
598 
599     // create the resource at the destination
600     ResourceInfo sourceInfo = ((Resource) source).getResourceInfo(true, false);
601     if (destinationResource.getType() != source.getType()) {
602       sourceInfo = (ResourceInfo) sourceInfo.clone();
603       sourceInfo.setType(destinationResource.getType());
604     }
605     ResourceInfo newInfo = createResource(destinationResource, sourceInfo, false, false, keepSyncInfo);
606     // get/set the node id from the source's resource info so we can later put it in the
607     // info for the destination resource. This will help us generate the proper deltas,
608     // indicating a move rather than a add/delete
609     long nodeid = ((Resource) source).getResourceInfo(true, false).getNodeId();
610     newInfo.setNodeId(nodeid);
611 
612     // preserve local sync info
613     ResourceInfo oldInfo = ((Resource) source).getResourceInfo(true, false);
614     newInfo.setFlags(newInfo.getFlags() | (oldInfo.getFlags() & M_LOCAL_EXISTS));
615     
616     // forget content-related caching flags
617     newInfo.clear(M_CONTENT_CACHE);
618 
619     // update link locations in project descriptions
620     if (source.isLinked()) {
621       LinkDescription linkDescription;
622       if ((updateFlags & IResource.SHALLOW) != 0) {
623         //for shallow move the destination is also a linked resource
624         newInfo.set(ICoreConstants.M_LINK);
625         linkDescription = new LinkDescription(destinationResource, source.getRawLocation());
626       } else {
627         //for deep move the destination is not a linked resource
628         newInfo.clear(ICoreConstants.M_LINK);
629         linkDescription = null;
630       }
631       Project project = (Project) destinationResource.getProject();
632       project.internalGetDescription().setLinkLocation(destinationResource.getName(), linkDescription);
633       project.writeDescription(updateFlags);
634     }
635 
636     // do the recursion. if we have a file then it has no members so return. otherwise
637     // recursively call this method on the container's members if the depth tells us to
638     if (depth == IResource.DEPTH_ZERO || source.getType() == IResource.FILE)
639       return;
640     if (depth == IResource.DEPTH_ONE)
641       depth = IResource.DEPTH_ZERO;
642     IResource[] children = ((IContainer) source).members(IContainer.INCLUDE_TEAM_PRIVATE_MEMBERS);
643     for (int i = 0; i < children.length; i++) {
644       IResource child = children[i];
645       IPath childPath = destination.append(child.getName());
646       copyTree(child, childPath, depth, updateFlags, keepSyncInfo);
647     }
648   }
649 
650   /**
651    * Returns the number of resources in a subtree of the resource tree.
652    * 
653    * @param root The subtree to count resources for
654    * @param depth The depth of the subtree to count
655    * @param phantom If true, phantoms are included, otherwise they are ignored.
656    */
657   public int countResources(IPath root, int depth, final boolean phantom) {
658     if (!tree.includes(root))
659       return 0;
660     switch (depth) {
661       case IResource.DEPTH_ZERO :
662         return 1;
663       case IResource.DEPTH_ONE :
664         return 1 + tree.getChildCount(root);
665       case IResource.DEPTH_INFINITE :
666         final int[] count = new int[1];
667         IElementContentVisitor visitor = new IElementContentVisitor() {
668           public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) {
669             if (phantom || !((ResourceInfo) elementContents).isSet(M_PHANTOM))
670               count[0]++;
671             return true;
672           }
673         };
674         new ElementTreeIterator(tree, root).iterate(visitor);
675         return count[0];
676     }
677     return 0;
678   }
679 
680   public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite) throws CoreException {
681     return createResource(resource, info, phantom, overwrite, false);
682   }
683 
684   /*
685    * Creates the given resource in the tree and returns the new resource info object.  
686    * If phantom is true, the created element is marked as a phantom.
687    * If there is already be an element in the tree for the given resource
688    * in the given state (i.e., phantom), a CoreException is thrown.  
689    * If there is already a phantom in the tree and the phantom flag is false, 
690    * the element is overwritten with the new element. (but the synchronization
691    * information is preserved) If the specified resource info is null, then create
692    * a new one.
693    * 
694    * If keepSyncInfo is set to be true, the sync info in the given ResourceInfo is NOT
695    * cleared before being created and thus any sync info already existing at that namespace
696    * (as indicated by an already existing phantom resource) will be lost.
697    */
698   public ResourceInfo createResource(IResource resource, ResourceInfo info, boolean phantom, boolean overwrite, boolean keepSyncInfo) throws CoreException {
699     info = info == null ? newElement(resource.getType()) : (ResourceInfo) info.clone();
700     ResourceInfo original = getResourceInfo(resource.getFullPath(), true, false);
701     if (phantom) {
702       info.set(M_PHANTOM);
703       info.setModificationStamp(IResource.NULL_STAMP);
704     }
705     // if nothing existed at the destination then just create the resource in the tree
706     if (original == null) {
707       // we got here from a copy/move. we don't want to copy over any sync info
708       // from the source so clear it.
709       if (!keepSyncInfo)
710         info.setSyncInfo(null);
711       tree.createElement(resource.getFullPath(), info);
712     } else {
713       // if overwrite==true then slam the new info into the tree even if one existed before
714       if (overwrite || (!phantom && original.isSet(M_PHANTOM))) {
715         // copy over the sync info and flags from the old resource info
716         // since we are replacing a phantom with a real resource
717         // DO NOT set the sync info dirty flag because we want to
718         // preserve the old sync info so its not dirty
719         // XXX: must copy over the generic sync info from the old info to the new
720         // XXX: do we really need to clone the sync info here?
721         if (!keepSyncInfo)
722           info.setSyncInfo(original.getSyncInfo(true));
723         // mark the markers bit as dirty so we snapshot an empty marker set for
724         // the new resource
725         info.set(ICoreConstants.M_MARKERS_SNAP_DIRTY);
726         tree.setElementData(resource.getFullPath(), info);
727       } else {
728         String message = Policy.bind("resources.mustNotExist", resource.getFullPath().toString()); //$NON-NLS-1$
729         throw new ResourceException(IResourceStatus.RESOURCE_EXISTS, resource.getFullPath(), message, null);
730       }
731     }
732     return info;
733   }
734 
735   /*
736    * Creates the given resource in the tree and returns the new resource info object.  
737    * If phantom is true, the created element is marked as a phantom.
738    * If there is already be an element in the tree for the given resource
739    * in the given state (i.e., phantom), a CoreException is thrown.  
740    * If there is already a phantom in the tree and the phantom flag is false, 
741    * the element is overwritten with the new element. (but the synchronization
742    * information is preserved)
743    */
744   public ResourceInfo createResource(IResource resource, boolean phantom) throws CoreException {
745     return createResource(resource, null, phantom, false);
746   }
747 
748   public ResourceInfo createResource(IResource resource, boolean phantom, boolean overwrite) throws CoreException {
749     return createResource(resource, null, phantom, overwrite);
750   }
751 
752   public static WorkspaceDescription defaultWorkspaceDescription() {
753     return new WorkspaceDescription("Workspace"); //$NON-NLS-1$
754   }
755 
756   /* (non-Javadoc)
757    * @see IWorkspace#delete(IResource[], int, IProgressMonitor)
758    */
759   public IStatus delete(IResource[] resources, int updateFlags, IProgressMonitor monitor) throws CoreException {
760     monitor = Policy.monitorFor(monitor);
761     try {
762       int opWork = Math.max(resources.length, 1);
763       int totalWork = Policy.totalWork * opWork / Policy.opWork;
764       String message = Policy.bind("resources.deleting.0"); //$NON-NLS-1$
765       monitor.beginTask(message, totalWork);
766       message = Policy.bind("resources.deleteProblem"); //$NON-NLS-1$
767       MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
768       if (resources.length == 0)
769         return result;
770       resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
771       try {
772         prepareOperation(getRoot(), monitor);
773         beginOperation(true);
774         for (int i = 0; i < resources.length; i++) {
775           Policy.checkCanceled(monitor);
776           Resource resource = (Resource) resources[i];
777           if (resource == null) {
778             monitor.worked(1);
779             continue;
780           }
781           try {
782             resource.delete(updateFlags, Policy.subMonitorFor(monitor, 1));
783           } catch (CoreException e) {
784             // Don't really care about the exception unless the resource is still around.
785             ResourceInfo info = resource.getResourceInfo(false, false);
786             if (resource.exists(resource.getFlags(info), false)) {
787               message = Policy.bind("resources.couldnotDelete", resource.getFullPath().toString()); //$NON-NLS-1$
788               result.merge(new ResourceStatus(IResourceStatus.FAILED_DELETE_LOCAL, resource.getFullPath(), message));
789               result.merge(e.getStatus());
790             }
791           }
792         }
793         if (result.matches(IStatus.ERROR))
794           throw new ResourceException(result);
795         return result;
796       } catch (OperationCanceledException e) {
797         getWorkManager().operationCanceled();
798         throw e;
799       } finally {
800         endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
801       }
802     } finally {
803       monitor.done();
804     }
805   }
806 
807   /* (non-Javadoc)
808    * @see IWorkspace#delete(IResource[], boolean, IProgressMonitor)
809    */
810   public IStatus delete(IResource[] resources, boolean force, IProgressMonitor monitor) throws CoreException {
811     int updateFlags = force ? IResource.FORCE : IResource.NONE;
812     updateFlags |= IResource.KEEP_HISTORY;
813     return delete(resources, updateFlags, monitor);
814   }
815 
816   /* (non-Javadoc)
817    * @see IWorkspace#deleteMarkers(IMarker[])
818    */
819   public void deleteMarkers(IMarker[] markers) throws CoreException {
820     Assert.isNotNull(markers);
821     if (markers.length == 0)
822       return;
823     // clone to avoid outside changes
824     markers = (IMarker[]) markers.clone();
825     try {
826       prepareOperation(null, null);
827       beginOperation(true);
828       for (int i = 0; i < markers.length; ++i)
829         if (markers[i] != null && markers[i].getResource() != null)
830           markerManager.removeMarker(markers[i].getResource(), markers[i].getId());
831     } finally {
832       endOperation(null, false, null);
833     }
834   }
835 
836   /**
837    * Delete the given resource from the current tree of the receiver.
838    * This method simply removes the resource from the tree.  No cleanup or 
839    * other management is done.  Use IResource.delete for proper deletion.
840    * If the given resource is the root, all of its children (i.e., all projects) are
841    * deleted but the root is left.
842    */
843   void deleteResource(IResource resource) {
844     IPath path = resource.getFullPath();
845     if (path.equals(Path.ROOT)) {
846       IProject[] children = getRoot().getProjects();
847       for (int i = 0; i < children.length; i++)
848         tree.deleteElement(children[i].getFullPath());
849     } else
850       tree.deleteElement(path);
851   }
852 
853   /**
854    * For debugging purposes only.  Dumps plugin stats to console
855    */
856   public void dumpStats() {
857     EventStats.dumpStats();
858   }
859 
860   /**
861    * End an operation (group of resource changes).
862    * Notify interested parties that resource changes have taken place.  All
863    * registered resource change listeners are notified.  If autobuilding is
864    * enabled, a build is run.
865    */
866   public void endOperation(ISchedulingRule rule, boolean build, IProgressMonitor monitor) throws CoreException {
867     WorkManager workManager = getWorkManager();
868     //don't do any end operation work if we failed to check in
869     if (workManager.checkInFailed(rule))
870       return;
871     // This is done in a try finally to ensure that we always decrement the operation count
872     // and release the workspace lock.  This must be done at the end because snapshot
873     // and "hasChanges" comparison have to happen without interference from other threads.
874     boolean hasTreeChanges = false;
875     boolean depthOne = false;
876     try {
877       workManager.setBuild(build);
878       // if we are not exiting a top level operation then just decrement the count and return
879       depthOne = workManager.getPreparedOperationDepth() == 1;
880       if (!(notificationManager.shouldNotify() || depthOne)) {
881         notificationManager.requestNotify();
882         return;
883       }
884       // do the following in a try/finally to ensure that the operation tree is nulled at the end
885       // as we are completing a top level operation.
886       try {
887         notificationManager.beginNotify();
888         // check for a programming error on using beginOperation/endOperation
889         Assert.isTrue(workManager.getPreparedOperationDepth() > 0, "Mismatched begin/endOperation"); //$NON-NLS-1$
890 
891         // At this time we need to rebalance the nested operations. It is necessary because
892         // build() and snapshot() should not fail if they are called.
893         workManager.rebalanceNestedOperations();
894         
895         //find out if any operation has potentially modified the tree
896         hasTreeChanges = workManager.shouldBuild();
897         //double check if the tree has actually changed
898         if (hasTreeChanges)
899           hasTreeChanges = operationTree != null && ElementTree.hasChanges(tree, operationTree, ResourceComparator.getComparator(false), true);
900         broadcastChanges(IResourceChangeEvent.POST_CHANGE, true);
901         // Request a snapshot if we are sufficiently out of date.
902         saveManager.snapshotIfNeeded(hasTreeChanges);
903       } finally {
904         // make sure the tree is immutable if we are ending a top-level operation.
905         if (depthOne) {
906           tree.immutable();
907           operationTree = null;
908         } else
909           newWorkingTree();
910       }
911     } finally {
912       workManager.checkOut(rule);
913     }
914     if (depthOne)
915       buildManager.endTopLevel(hasTreeChanges);
916   }
917 
918   /**
919    * Flush the build order cache for the workspace.  Only needed if the
920    * description does not already have a build order.  That is, if this
921    * is really a cache.
922    */
923   protected void flushBuildOrder() {
924     if (description.getBuildOrder(false) == null)
925       buildOrder = null;
926   }
927 
928   /* (non-Javadoc)
929    * @see IWorkspace#forgetSavedTree(String)
930    */
931   public void forgetSavedTree(String pluginId) {
932     Assert.isNotNull(pluginId, "PluginId must not be null"); //$NON-NLS-1$
933     saveManager.forgetSavedTree(pluginId);
934   }
935 
936   public AliasManager getAliasManager() {
937     return aliasManager;
938   }
939 
940   /**
941    * Returns this workspace's build manager
942    */
943   public BuildManager getBuildManager() {
944     return buildManager;
945   }
946 
947   /**
948    * Returns the order in which open projects in this workspace will be built.
949    * <p>
950    * The project build order is based on information specified in the workspace
951    * description. The projects are built in the order specified by
952    * <code>IWorkspaceDescription.getBuildOrder</code>; closed or non-existent
953    * projects are ignored and not included in the result. If
954    * <code>IWorkspaceDescription.getBuildOrder</code> is non-null, the default
955    * build order is used; again, only open projects are included in the result.
956    * </p>
957    * <p>
958    * The returned value is cached in the <code>buildOrder</code> field.
959    * </p>
960    * 
961    * @return the list of currently open projects in the workspace in the order in
962    * which they would be built by <code>IWorkspace.build</code>.
963    * @see IWorkspace#build(int, IProgressMonitor)
964    * @see IWorkspaceDescription#getBuildOrder()
965    * @since 2.1
966    */
967   public IProject[] getBuildOrder() {
968     if (buildOrder != null) {
969       // return previously-computed and cached project build order
970       return buildOrder;
971     }
972     // see if a particular build order is specified
973     String[] order = description.getBuildOrder(false);
974     if (order != null) {
975       // convert from project names to project handles
976       // and eliminate non-existent and closed projects
977       List projectList = new ArrayList(order.length);
978       for (int i = 0; i < order.length; i++) {
979         IProject project = getRoot().getProject(order[i]);
980         if (project.isAccessible()) {
981           projectList.add(project);
982         }
983       }
984       buildOrder = new IProject[projectList.size()];
985       projectList.toArray(buildOrder);
986     } else {
987       // use default project build order
988       // computed for all accessible projects in workspace
989       buildOrder = computeFullProjectOrder().projects;
990     }
991     return buildOrder;
992   }
993   
994   public CharsetManager getCharsetManager() {
995     return charsetManager;
996   }
997   
998   public ContentDescriptionManager getContentDescriptionManager() {
999     return contentDescriptionManager;
1000  }  
1001  
1002
1003  /* (non-Javadoc)
1004   * @see IWorkspace#getDanglingReferences()
1005   */
1006  public Map getDanglingReferences() {
1007    IProject[] projects = getRoot().getProjects();
1008    Map result = new HashMap(projects.length);
1009    for (int i = 0; i < projects.length; i++) {
1010      Project project = (Project) projects[i];
1011      if (!project.isAccessible())
1012        continue;
1013      IProject[] refs = project.internalGetDescription().getReferencedProjects(false);
1014      List dangling = new ArrayList(refs.length);
1015      for (int j = 0; j < refs.length; j++)
1016        if (!refs[i].exists())
1017          dangling.add(refs[i]);
1018      if (!dangling.isEmpty())
1019        result.put(projects[i], dangling.toArray(new IProject[dangling.size()]));
1020    }
1021    return result;
1022  }
1023
1024  /* (non-Javadoc)
1025   * @see IWorkspace#getDescription()
1026   */
1027  public IWorkspaceDescription getDescription() {
1028    WorkspaceDescription workingCopy = defaultWorkspaceDescription();
1029    description.copyTo(workingCopy);
1030    return workingCopy;
1031  }
1032
1033  /** 
1034   * Returns the current element tree for this workspace
1035   */
1036  public ElementTree getElementTree() {
1037    return tree;
1038  }
1039
1040  public FileSystemResourceManager getFileSystemManager() {
1041    return fileSystemManager;
1042  }
1043
1044  /**
1045   * Returns the marker manager for this workspace
1046   */
1047  public MarkerManager getMarkerManager() {
1048    return markerManager;
1049  }
1050
1051  public LocalMetaArea getMetaArea() {
1052    return localMetaArea;
1053  }
1054
1055  protected IMoveDeleteHook getMoveDeleteHook() {
1056    if (moveDeleteHook == null)
1057      initializeMoveDeleteHook();
1058    return moveDeleteHook;
1059  }
1060
1061  /* (non-Javadoc)
1062   * @see IWorkspace#getNatureDescriptor(String)
1063   */
1064  public IProjectNatureDescriptor getNatureDescriptor(String natureId) {
1065    return natureManager.getNatureDescriptor(natureId);
1066  }
1067
1068  /* (non-Javadoc)
1069   * @see IWorkspace#getNatureDescriptors()
1070   */
1071  public IProjectNatureDescriptor[] getNatureDescriptors() {
1072    return natureManager.getNatureDescriptors();
1073  }
1074
1075  /**
1076   * Returns the nature manager for this workspace.
1077   */
1078  public NatureManager getNatureManager() {
1079    return natureManager;
1080  }
1081
1082  public NotificationManager getNotificationManager() {
1083    return notificationManager;
1084  }
1085
1086  /* (non-Javadoc)
1087   * @see IWorkspace#getPathVariableManager()
1088   */
1089  public IPathVariableManager getPathVariableManager() {
1090    return pathVariableManager;
1091  }
1092
1093  public PropertyManager getPropertyManager() {
1094    return propertyManager;
1095  }
1096  
1097  /**
1098   * Returns the refresh manager for this workspace
1099   */
1100  public RefreshManager getRefreshManager() {
1101    return refreshManager;
1102  }
1103
1104  /**
1105   * Returns the resource info for the identified resource.
1106   * null is returned if no such resource can be found.
1107   * If the phantom flag is true, phantom resources are considered.
1108   * If the mutable flag is true, the info is opened for change.
1109   *
1110   * This method DOES NOT throw an exception if the resource is not found.
1111   */
1112  public ResourceInfo getResourceInfo(IPath path, boolean phantom, boolean mutable) {
1113    try {
1114      if (path.segmentCount() == 0) {
1115        ResourceInfo info = (ResourceInfo) tree.getTreeData();
1116        Assert.isNotNull(info, "Tree root info must never be null"); //$NON-NLS-1$
1117        return info;
1118      }
1119      ResourceInfo result = null;
1120      if (!tree.includes(path))
1121        return null;
1122      if (mutable)
1123        result = (ResourceInfo) tree.openElementData(path);
1124      else
1125        result = (ResourceInfo) tree.getElementData(path);
1126      if (result != null && (!phantom && result.isSet(M_PHANTOM)))
1127        return null;
1128      return result;
1129    } catch (IllegalArgumentException e) {
1130      return null;
1131    }
1132  }
1133
1134  /* (non-Javadoc)
1135   * @see IWorkspace#getRoot()
1136   */
1137  public IWorkspaceRoot getRoot() {
1138    return defaultRoot;
1139  }
1140
1141  /* (non-Javadoc)
1142   * @see IWorkspace#getRuleFactory()
1143   */
1144  public IResourceRuleFactory getRuleFactory() {
1145    //note that the rule factory is created lazily because it
1146    //requires loading the teamHook extension
1147    if (ruleFactory == null)
1148      ruleFactory = new Rules(this);
1149    return ruleFactory;
1150  }
1151
1152  public SaveManager getSaveManager() {
1153    return saveManager;
1154  }
1155
1156  /* (non-Javadoc)
1157   * @see IWorkspace#getSynchronizer()
1158   */
1159  public ISynchronizer getSynchronizer() {
1160    return synchronizer;
1161  }
1162
1163  /**
1164   * Returns the installed team hook.  Never returns null.
1165   */
1166  protected TeamHook getTeamHook() {
1167    if (teamHook == null)
1168      initializeTeamHook();
1169    return teamHook;
1170  }
1171
1172  /**
1173   * We should not have direct references to this field. All references should go through
1174   * this method.
1175   */
1176  public WorkManager getWorkManager() throws CoreException {
1177    if (_workManager == null) {
1178      String message = Policy.bind("resources.shutdown"); //$NON-NLS-1$
1179      throw new ResourceException(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, null, message));
1180    }
1181    return _workManager;
1182  }
1183
1184  /**
1185   * A file modification validator hasn't been initialized. Check the extension point and 
1186   * try to create a new validator if a user has one defined as an extension.
1187   */
1188  protected void initializeValidator() {
1189    shouldValidate = false;
1190    IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_FILE_MODIFICATION_VALIDATOR);
1191    // no-one is plugged into the extension point so disable validation
1192    if (configs == null || configs.length == 0) {
1193      return;
1194    }
1195    // can only have one defined at a time. log a warning, disable validation, but continue with
1196    // the #setContents (e.g. don't throw an exception)
1197    if (configs.length > 1) {
1198      //XXX: shoud provide a meaningful status code
1199      IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.oneValidator"), null); //$NON-NLS-1$
1200      ResourcesPlugin.getPlugin().getLog().log(status);
1201      return;
1202    }
1203    // otherwise we have exactly one validator extension. Try to create a new instance 
1204    // from the user-specified class.
1205    try {
1206      IConfigurationElement config = configs[0];
1207      validator = (IFileModificationValidator) config.createExecutableExtension("class"); //$NON-NLS-1$
1208      shouldValidate = true;
1209    } catch (CoreException e) {
1210      //XXX: shoud provide a meaningful status code
1211      IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.initValidator"), e); //$NON-NLS-1$
1212      ResourcesPlugin.getPlugin().getLog().log(status);
1213    }
1214  }
1215
1216  /**
1217   * A move/delete hook hasn't been initialized. Check the extension point and 
1218   * try to create a new hook if a user has one defined as an extension. Otherwise
1219   * use the Core's implementation as the default.
1220   */
1221  protected void initializeMoveDeleteHook() {
1222    try {
1223      IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_MOVE_DELETE_HOOK);
1224      // no-one is plugged into the extension point so disable validation
1225      if (configs == null || configs.length == 0) {
1226        return;
1227      }
1228      // can only have one defined at a time. log a warning
1229      if (configs.length > 1) {
1230        //XXX: shoud provide a meaningful status code
1231        IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.oneHook"), null); //$NON-NLS-1$
1232        ResourcesPlugin.getPlugin().getLog().log(status);
1233        return;
1234      }
1235      // otherwise we have exactly one hook extension. Try to create a new instance 
1236      // from the user-specified class.
1237      try {
1238        IConfigurationElement config = configs[0];
1239        moveDeleteHook = (IMoveDeleteHook) config.createExecutableExtension("class"); //$NON-NLS-1$
1240      } catch (CoreException e) {
1241        //XXX: shoud provide a meaningful status code
1242        IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.initHook"), e); //$NON-NLS-1$
1243        ResourcesPlugin.getPlugin().getLog().log(status);
1244      }
1245    } finally {
1246      // for now just use Core's implementation
1247      if (moveDeleteHook == null)
1248        moveDeleteHook = new MoveDeleteHook();
1249    }
1250  }
1251
1252  /**
1253   * A team hook hasn't been initialized. Check the extension point and 
1254   * try to create a new hook if a user has one defined as an extension. 
1255   * Otherwise use the Core's implementation as the default.
1256   */
1257  protected void initializeTeamHook() {
1258    try {
1259      IConfigurationElement[] configs = Platform.getExtensionRegistry().getConfigurationElementsFor(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_TEAM_HOOK);
1260      // no-one is plugged into the extension point so disable validation
1261      if (configs == null || configs.length == 0) {
1262        return;
1263      }
1264      // can only have one defined at a time. log a warning
1265      if (configs.length > 1) {
1266        //XXX: shoud provide a meaningful status code
1267        IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.oneTeamHook"), null); //$NON-NLS-1$
1268        ResourcesPlugin.getPlugin().getLog().log(status);
1269        return;
1270      }
1271      // otherwise we have exactly one hook extension. Try to create a new instance 
1272      // from the user-specified class.
1273      try {
1274        IConfigurationElement config = configs[0];
1275        teamHook = (TeamHook) config.createExecutableExtension("class"); //$NON-NLS-1$
1276      } catch (CoreException e) {
1277        //XXX: shoud provide a meaningful status code
1278        IStatus status = new ResourceStatus(IStatus.ERROR, 1, null, Policy.bind("resources.initTeamHook"), e); //$NON-NLS-1$
1279        ResourcesPlugin.getPlugin().getLog().log(status);
1280      }
1281    } finally {
1282      // default to use Core's implementation
1283      //create anonymous subclass because TeamHook is abstract
1284      if (teamHook == null)
1285        teamHook = new TeamHook() {
1286          // empty
1287        };
1288    }
1289  }
1290
1291  public WorkspaceDescription internalGetDescription() {
1292    return description;
1293  }
1294
1295  /* (non-Javadoc)
1296   * @see IWorkspace#isAutoBuilding()
1297   */
1298  public boolean isAutoBuilding() {
1299    return description.isAutoBuilding();
1300  }
1301
1302  /**
1303   * Returns true if the object at the specified position has any
1304   * other copy in the given array.
1305   */
1306  private static boolean isDuplicate(Object[] array, int position) {
1307    if (array == null || position >= array.length)
1308      return false;
1309    for (int j = position - 1; j >= 0; j--)
1310      if (array[j].equals(array[position]))
1311        return true;
1312    return false;
1313  }
1314
1315  public boolean isOpen() {
1316    return openFlag;
1317  }
1318
1319  /**
1320   * Returns true if the given file system locations overlap. If "bothDirections" is true,
1321   * this means they are the same, or one is a proper prefix of the other.  If "bothDirections"
1322   * is false, this method only returns true if the locations are the same, or the first location
1323   * is a prefix of the second.  Returns false if the locations do not overlap
1324   * Does the right thing with respect to case insensitive platforms.
1325   */
1326  protected boolean isOverlapping(IPath location1, IPath location2, boolean bothDirections) {
1327    IPath one = location1;
1328    IPath two = location2;
1329    // If we are on a case-insensitive file system then convert to all lowercase.
1330    if (!CoreFileSystemLibrary.isCaseSensitive()) {
1331      one = new Path(location1.toOSString().toLowerCase());
1332      two = new Path(location2.toOSString().toLowerCase());
1333    }
1334    return one.isPrefixOf(two) || (bothDirections && two.isPrefixOf(one));
1335  }
1336
1337  /* (non-Javadoc)
1338   * @see IWorkspace#isTreeLocked()
1339   */
1340  public boolean isTreeLocked() {
1341    return treeLocked == Thread.currentThread();
1342  }
1343
1344  /**
1345   * Link the given tree into the receiver's tree at the specified resource.
1346   */
1347  protected void linkTrees(IPath path, ElementTree[] newTrees) {
1348    tree = tree.mergeDeltaChain(path, newTrees);
1349  }
1350
1351  /* (non-Javadoc)
1352   * @see IWorkspace#loadProjectDescription(IPath)
1353   * @since 2.0
1354   */
1355  public IProjectDescription loadProjectDescription(IPath path) throws CoreException {
1356    IProjectDescription result = null;
1357    IOException e = null;
1358    try {
1359      result = new ProjectDescriptionReader().read(path);
1360      if (result != null) {
1361        // check to see if we are using in the default area or not. use java.io.File for
1362        // testing equality because it knows better w.r.t. drives and case sensitivity
1363        IPath user = path.removeLastSegments(1);
1364        IPath platform = Platform.getLocation().append(result.getName());
1365        if (!user.toFile().equals(platform.toFile()))
1366          result.setLocation(user);
1367      }
1368    } catch (IOException ex) {
1369      e = ex;
1370    }
1371    if (result == null || e != null) {
1372      String message = Policy.bind("resources.errorReadProject", path.toOSString()); //$NON-NLS1 //$NON-NLS-1$
1373      IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_READ_METADATA, message, e);
1374      throw new ResourceException(status);
1375    }
1376    return result;
1377  }
1378
1379  /* (non-Javadoc)
1380   * @see IWorkspace#move(IResource[], IPath, int, IProgressMonitor)
1381   */
1382  public IStatus move(IResource[] resources, IPath destination, int updateFlags, IProgressMonitor monitor) throws CoreException {
1383    monitor = Policy.monitorFor(monitor);
1384    try {
1385      int opWork = Math.max(resources.length, 1);
1386      int totalWork = Policy.totalWork * opWork / Policy.opWork;
1387      String message = Policy.bind("resources.moving.0"); //$NON-NLS-1$
1388      monitor.beginTask(message, totalWork);
1389      Assert.isLegal(resources != null);
1390      if (resources.length == 0)
1391        return Status.OK_STATUS;
1392      resources = (IResource[]) resources.clone(); // to avoid concurrent changes to this array
1393      IPath parentPath = null;
1394      message = Policy.bind("resources.moveProblem"); //$NON-NLS-1$
1395      MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
1396      try {
1397        prepareOperation(getRoot(), monitor);
1398        beginOperation(true);
1399        for (int i = 0; i < resources.length; i++) {
1400          Policy.checkCanceled(monitor);
1401          Resource resource = (Resource) resources[i];
1402          if (resource == null || isDuplicate(resources, i)) {
1403            monitor.worked(1);
1404            continue;
1405          }
1406          // test siblings
1407          if (parentPath == null)
1408            parentPath = resource.getFullPath().removeLastSegments(1);
1409          if (parentPath.equals(resource.getFullPath().removeLastSegments(1))) {
1410            // test move requirements
1411            try {
1412              IStatus requirements = resource.checkMoveRequirements(destination.append(resource.getName()), resource.getType(), updateFlags);
1413              if (requirements.isOK()) {
1414                try {
1415                  resource.move(destination.append(resource.getName()), updateFlags, Policy.subMonitorFor(monitor, 1));
1416                } catch (CoreException e) {
1417                  status.merge(e.getStatus());
1418                }
1419              } else {
1420                monitor.worked(1);
1421                status.merge(requirements);
1422              }
1423            } catch (CoreException e) {
1424              monitor.worked(1);
1425              status.merge(e.getStatus());
1426            }
1427          } else {
1428            monitor.worked(1);
1429            message = Policy.bind("resources.notChild", resource.getFullPath().toString(), parentPath.toString()); //$NON-NLS-1$
1430            status.merge(new ResourceStatus(IResourceStatus.OPERATION_FAILED, resource.getFullPath(), message));
1431          }
1432        }
1433      } catch (OperationCanceledException e) {
1434        getWorkManager().operationCanceled();
1435        throw e;
1436      } finally {
1437        endOperation(getRoot(), true, Policy.subMonitorFor(monitor, totalWork - opWork));
1438      }
1439      if (status.matches(IStatus.ERROR))
1440        throw new ResourceException(status);
1441      return status.isOK() ? (IStatus) Status.OK_STATUS : (IStatus) status;
1442    } finally {
1443      monitor.done();
1444    }
1445  }
1446
1447  /* (non-Javadoc)
1448   * @see IWorkspace#move(IResource[], IPath, boolean, IProgressMonitor)
1449   */
1450  public IStatus move(IResource[] resources, IPath destination, boolean force, IProgressMonitor monitor) throws CoreException {
1451    int updateFlags = force ? IResource.FORCE : IResource.NONE;
1452    updateFlags |= IResource.KEEP_HISTORY;
1453    return move(resources, destination, updateFlags, monitor);
1454  }
1455
1456  /**
1457   * Moves this resource's subtree to the destination. This operation should only be
1458   * used by move methods. Destination must be a valid destination for this resource.
1459   * The keepSyncInfo boolean is used to indicated whether or not the sync info should
1460   * be moved from the source to the destination.
1461   */
1462
1463  /* package */
1464  void move(Resource source, IPath destination, int depth, int updateFlags, boolean keepSyncInfo) throws CoreException {
1465    // overlay the tree at the destination path, preserving any important info
1466    // in any already existing resource infos
1467    copyTree(source, destination, depth, updateFlags, keepSyncInfo);
1468    source.fixupAfterMoveSource();
1469  }
1470
1471  /**
1472   * Create and return a new tree element of the given type.
1473   */
1474  protected ResourceInfo newElement(int type) {
1475    ResourceInfo result = null;
1476    switch (type) {
1477      case IResource.FILE :
1478      case IResource.FOLDER :
1479        result = new ResourceInfo();
1480        break;
1481      case IResource.PROJECT :
1482        result = new ProjectInfo();
1483        break;
1484      case IResource.ROOT :
1485        result = new RootInfo();
1486        break;
1487    }
1488    result.setNodeId(nextNodeId());
1489    result.setModificationStamp(nextModificationStamp());
1490    result.setType(type);
1491    return result;
1492  }
1493
1494  /* (non-Javadoc)
1495   * @see IWorkspace#newProjectDescription(String)
1496   */
1497  public IProjectDescription newProjectDescription(String projectName) {
1498    IProjectDescription result = new ProjectDescription();
1499    result.setName(projectName);
1500    return result;
1501  }
1502
1503  public Resource newResource(IPath path, int type) {
1504    String message;
1505    switch (type) {
1506      case IResource.FOLDER :
1507        if (path.segmentCount() < ICoreConstants.MINIMUM_FOLDER_SEGMENT_LENGTH) {
1508          message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$
1509          Assert.isLegal(false, message);
1510        }
1511        return new Folder(path.makeAbsolute(), this);
1512      case IResource.FILE :
1513        if (path.segmentCount() < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) {
1514          message = "Path must include project and resource name: " + path.toString(); //$NON-NLS-1$
1515          Assert.isLegal(false, message);
1516        }
1517        return new File(path.makeAbsolute(), this);
1518      case IResource.PROJECT :
1519        return (Resource) getRoot().getProject(path.lastSegment());
1520      case IResource.ROOT :
1521        return (Resource) getRoot();
1522    }
1523    Assert.isLegal(false);
1524    // will never get here because of assertion.
1525    return null;
1526  }
1527
1528  /**
1529   * Opens a new mutable element tree layer, thus allowing 
1530   * modifications to the tree.
1531   */
1532  public ElementTree newWorkingTree() {
1533    tree = tree.newEmptyDelta();
1534    return tree;
1535  }
1536
1537  /**
1538   * Returns the next, previously unassigned, marker id.
1539   */
1540  protected long nextMarkerId() {
1541    return nextMarkerId++;
1542  }
1543
1544  public long nextModificationStamp() {
1545    return nextModificationStamp++;
1546  }
1547
1548  public long nextNodeId() {
1549    return nextNodeId++;
1550  }
1551
1552  /**
1553   * Opens this workspace using the data at its location in the local file system.
1554   * This workspace must not be open.
1555   * If the operation succeeds, the result will detail any serious
1556   * (but non-fatal) problems encountered while opening the workspace.
1557   * The status code will be <code>OK</code> if there were no problems.
1558   * An exception is thrown if there are fatal problems opening the workspace,
1559   * in which case the workspace is left closed.
1560   * <p>
1561   * This method is long-running; progress and cancellation are provided
1562   * by the given progress monitor.
1563   * </p>
1564   *
1565   * @param monitor a progress monitor, or <code>null</code> if progress
1566   *    reporting and cancellation are not desired
1567   * @return status with code <code>OK</code> if no problems;
1568   *     otherwise status describing any serious but non-fatal problems.
1569   *     
1570   * @exception CoreException if the workspace could not be opened.
1571   * Reasons include:
1572   * <ul>
1573   * <li> There is no valid workspace structure at the given location
1574   *      in the local file system.</li>
1575   * <li> The workspace structure on disk appears to be hopelessly corrupt.</li>
1576   * </ul>
1577   * @see ResourcesPlugin#getWorkspace()
1578   */
1579  public IStatus open(IProgressMonitor monitor) throws CoreException {
1580    // This method is not inside an operation because it is the one responsible for
1581    // creating the WorkManager object (who takes care of operations).
1582    String message = Policy.bind("resources.workspaceOpen"); //$NON-NLS-1$
1583    Assert.isTrue(!isOpen(), message);
1584    if (!getMetaArea().hasSavedWorkspace()) {
1585      message = Policy.bind("resources.readWorkspaceMeta"); //$NON-NLS-1$
1586      throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, Platform.getLocation(), message, null);
1587    }
1588    description = new WorkspacePreferences();
1589    description.setDefaults(Workspace.defaultWorkspaceDescription());
1590
1591    // if we have an old description file, read it (getting rid of it)
1592    WorkspaceDescription oldDescription = getMetaArea().readOldWorkspace();
1593    if (oldDescription != null) {
1594      description.copyFrom(oldDescription);
1595      ResourcesPlugin.getPlugin().savePluginPreferences();
1596    }
1597
1598    // create root location
1599    localMetaArea.locationFor(getRoot()).toFile().mkdirs();
1600
1601    IProgressMonitor nullMonitor = Policy.monitorFor(null);
1602    startup(nullMonitor);
1603    //restart the notification manager so it is initialized with the right tree
1604    notificationManager.startup(null);
1605    openFlag = true;
1606    if (crashed || refreshRequested()) {
1607      try {
1608        getRoot().refreshLocal(IResource.DEPTH_INFINITE, null);
1609      } catch (CoreException e) {
1610        //don't fail entire open if refresh failed, just report as minor warning
1611        return e.getStatus();
1612      }
1613    }
1614    return Status.OK_STATUS;
1615  }
1616
1617  /**
1618   * Called before checking the pre-conditions of an operation.  Optionally supply
1619   * a scheduling rule to determine when the operation is safe to run.  If a scheduling 
1620   * rule is supplied, this method will block until it is safe to run.
1621   * 
1622   * @param rule the scheduling rule that describes what this operation intends to modify.
1623   */
1624  public void prepareOperation(ISchedulingRule rule, IProgressMonitor monitor) throws CoreException {
1625    //make sure autobuild is not running
1626    if (rule != null)
1627      buildManager.interrupt();
1628    getWorkManager().checkIn(rule, monitor);
1629    if (!isOpen()) {
1630      String message = Policy.bind("resources.workspaceClosed"); //$NON-NLS-1$
1631      throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, null);
1632    }
1633  }
1634
1635  protected boolean refreshRequested() {
1636    String[] args = Platform.getCommandLineArgs();
1637    for (int i = 0; i < args.length; i++)
1638      if (args[i].equalsIgnoreCase(REFRESH_ON_STARTUP))
1639        return true;
1640    return false;
1641  }
1642
1643  /* (non-Javadoc)
1644   * @see IWorkspace#removeResourceChangeListener(IResourceChangeListener)
1645   */
1646  public void removeResourceChangeListener(IResourceChangeListener listener) {
1647    notificationManager.removeListener(listener);
1648  }
1649
1650  /* (non-Javadoc)
1651   * @see IWorkspace#removeSaveParticipant(Plugin)
1652   */
1653  public void removeSaveParticipant(Plugin plugin) {
1654    Assert.isNotNull(plugin, "Plugin must not be null"); //$NON-NLS-1$
1655    saveManager.removeParticipant(plugin);
1656  }
1657
1658  /* (non-Javadoc)
1659   * @see IWorkspace#run(IWorkspaceRunnable, ISchedulingRule, int, IProgressMonitor)
1660   */
1661  public void run(IWorkspaceRunnable action, ISchedulingRule rule, int options, IProgressMonitor monitor) throws CoreException {
1662    monitor = Policy.monitorFor(monitor);
1663    try {
1664      monitor.beginTask(null, Policy.totalWork);
1665      int depth = -1;
1666      boolean avoidNotification = (options & IWorkspace.AVOID_UPDATE) != 0;
1667      try {
1668        prepareOperation(rule, monitor);
1669        beginOperation(true);
1670        if (avoidNotification)
1671          avoidNotification = notificationManager.beginAvoidNotify();
1672        depth = getWorkManager().beginUnprotected();
1673        action.run(Policy.subMonitorFor(monitor, Policy.opWork, SubProgressMonitor.PREPEND_MAIN_LABEL_TO_SUBTASK));
1674      } catch (OperationCanceledException e) {
1675        getWorkManager().operationCanceled();
1676        throw e;
1677      } finally {
1678        if (avoidNotification)
1679          notificationManager.endAvoidNotify();
1680        if (depth >= 0)
1681          getWorkManager().endUnprotected(depth);
1682        endOperation(rule, false, Policy.subMonitorFor(monitor, Policy.buildWork));
1683      }
1684    } finally {
1685      monitor.done();
1686    }
1687  }
1688
1689  /* (non-Javadoc)
1690   * @see IWorkspace#run(IWorkspaceRunnable, IProgressMonitor)
1691   */
1692  public void run(IWorkspaceRunnable action, IProgressMonitor monitor) throws CoreException {
1693    run(action, defaultRoot, IWorkspace.AVOID_UPDATE, monitor);
1694  }
1695
1696  /* (non-Javadoc)
1697   * @see IWorkspace#save(boolean, IProgressMonitor)
1698   */
1699  public IStatus save(boolean full, IProgressMonitor monitor) throws CoreException {
1700    String message;
1701    if (full) {
1702      //according to spec it is illegal to start a full save inside another operation
1703      if (getWorkManager().isLockAlreadyAcquired()) {
1704        message = Policy.bind("resources.saveOp"); //$NON-NLS-1$
1705        throw new ResourceException(IResourceStatus.OPERATION_FAILED, null, message, new IllegalStateException());
1706      }
1707      return saveManager.save(ISaveContext.FULL_SAVE, null, monitor);
1708    }
1709    // A snapshot was requested.  Start an operation (if not already started) and 
1710    // signal that a snapshot should be done at the end.
1711    try {
1712      prepareOperation(getRoot(), monitor);
1713      beginOperation(false);
1714      saveManager.requestSnapshot();
1715      message = Policy.bind("resources.snapRequest"); //$NON-NLS-1$
1716      return new ResourceStatus(IStatus.OK, message);
1717    } finally {
1718      endOperation(getRoot(), false, null);
1719    }
1720  }
1721
1722  public void setCrashed(boolean value) {
1723    crashed = value;
1724  }
1725
1726  /* (non-Javadoc)
1727   * @see IWorkspace#setDescription(IWorkspaceDescription)
1728   */
1729  public void setDescription(IWorkspaceDescription value) throws CoreException {
1730    // if both the old and new description's build orders are null, leave the
1731    // workspace's build order slot because it is caching the computed order.
1732    // Otherwise, set the slot to null to force recomputation or building from the description.
1733    WorkspaceDescription newDescription = (WorkspaceDescription) value;
1734    String[] newOrder = newDescription.getBuildOrder(false);
1735    if (description.getBuildOrder(false) != null || newOrder != null)
1736      buildOrder = null;
1737    description.copyFrom(newDescription);
1738    Policy.setupAutoBuildProgress(description.isAutoBuilding());
1739    ResourcesPlugin.getPlugin().savePluginPreferences();
1740  }
1741
1742  public void setTreeLocked(boolean locked) {
1743    treeLocked = locked ? Thread.currentThread() : null;
1744  }
1745
1746  /**
1747   * @deprecated
1748   */
1749  public void setWorkspaceLock(WorkspaceLock lock) {
1750    // do nothing
1751  }
1752
1753  protected void shutdown(IProgressMonitor monitor) throws CoreException {
1754    monitor = Policy.monitorFor(monitor);
1755    try {
1756      IManager[] managers = {buildManager, propertyManager, pathVariableManager, charsetManager, fileSystemManager, markerManager, saveManager, _workManager, aliasManager, refreshManager, contentDescriptionManager};
1757      monitor.beginTask(null, managers.length);
1758      String message = Policy.bind("resources.shutdownProblems"); //$NON-NLS-1$
1759      MultiStatus status = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, null);
1760      // best effort to shutdown every object and free resources
1761      for (int i = 0; i < managers.length; i++) {
1762        IManager manager = managers[i];
1763        if (manager == null)
1764          monitor.worked(1);
1765        else {
1766          try {
1767            manager.shutdown(Policy.subMonitorFor(monitor, 1));
1768          } catch (Exception e) {
1769            message = Policy.bind("resources.shutdownProblems"); //$NON-NLS-1$
1770            status.add(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, message, e));
1771          }
1772        }
1773      }
1774      buildManager = null;
1775      notificationManager = null;
1776      propertyManager = null;
1777      pathVariableManager = null;
1778      fileSystemManager = null;
1779      markerManager = null;
1780      synchronizer = null;
1781      saveManager = null;
1782      _workManager = null;
1783      aliasManager = null;
1784      refreshManager = null;
1785      charsetManager = null;
1786      contentDescriptionManager = null;
1787      if (!status.isOK())
1788        throw new CoreException(status);
1789    } finally {
1790      monitor.done();
1791    }
1792  }
1793
1794  /* (non-Javadoc)
1795   * @see IWorkspace#sortNatureSet(String[])
1796   */
1797  public String[] sortNatureSet(String[] natureIds) {
1798    return natureManager.sortNatureSet(natureIds);
1799  }
1800
1801  protected void startup(IProgressMonitor monitor) throws CoreException {
1802    // ensure the tree is locked during the startup notification
1803    _workManager = new WorkManager(this);
1804    _workManager.startup(null);
1805    fileSystemManager = new FileSystemResourceManager(this);
1806    fileSystemManager.startup(monitor);
1807    propertyManager = new PropertyManager(this);
1808    propertyManager.startup(monitor);
1809    pathVariableManager = new PathVariableManager();
1810    pathVariableManager.startup(null);
1811    natureManager = new NatureManager();
1812    natureManager.startup(null);
1813    buildManager = new BuildManager(this, getWorkManager().getLock());
1814    buildManager.startup(null);
1815    notificationManager = new NotificationManager(this);
1816    notificationManager.startup(null);
1817    markerManager = new MarkerManager(this);
1818    markerManager.startup(null);
1819    synchronizer = new Synchronizer(this);
1820    saveManager = new SaveManager(this);
1821    saveManager.startup(null);
1822    //must start after save manager, because (read) access to tree is needed
1823    aliasManager = new AliasManager(this);
1824    aliasManager.startup(null);
1825    refreshManager = new RefreshManager(this);
1826    refreshManager.startup(null);
1827    charsetManager = new CharsetManager(this);
1828    charsetManager.startup(null);
1829    contentDescriptionManager = new ContentDescriptionManager();
1830    contentDescriptionManager.startup(null);
1831    treeLocked = null; // unlock the tree.
1832  }
1833
1834  /** 
1835   * Returns a string representation of this working state's
1836   * structure suitable for debug purposes.
1837   */
1838  public String toDebugString() {
1839    final StringBuffer buffer = new StringBuffer("\nDump of " + toString() + ":\n"); //$NON-NLS-1$ //$NON-NLS-2$
1840    buffer.append("  parent: " + tree.getParent()); //$NON-NLS-1$
1841    IElementContentVisitor visitor = new IElementContentVisitor() {
1842      public boolean visitElement(ElementTree aTree, IPathRequestor requestor, Object elementContents) {
1843        buffer.append("\n  " + requestor.requestPath() + ": " + elementContents); //$NON-NLS-1$ //$NON-NLS-2$
1844        return true;
1845      }
1846    };
1847    new ElementTreeIterator(tree, Path.ROOT).iterate(visitor);
1848    return buffer.toString();
1849  }
1850
1851  public void updateModificationStamp(ResourceInfo info) {
1852    info.setModificationStamp(nextModificationStamp());
1853  }
1854
1855  /* (non-javadoc)
1856   * @see IWorkspace#validateEdit(IFile[], Object)
1857   */
1858  public IStatus validateEdit(final IFile[] files, final Object context) {
1859    // if validation is turned off then just return
1860    if (!shouldValidate) {
1861      String message = Policy.bind("resources.readOnly2"); //$NON-NLS-1$
1862      MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.READ_ONLY_LOCAL, message, null);
1863      for (int i = 0; i < files.length; i++) {
1864        if (files[i].isReadOnly()) {
1865          IPath filePath = files[i].getFullPath();
1866          message = Policy.bind("resources.readOnly", filePath.toString()); //$NON-NLS-1$
1867          result.add(new ResourceStatus(IResourceStatus.READ_ONLY_LOCAL, filePath, message));
1868        }
1869      }
1870      return result.getChildren().length == 0 ? Status.OK_STATUS : (IStatus) result;
1871    }
1872    // first time through the validator hasn't been initialized so try and create it
1873    if (validator == null)
1874      initializeValidator();
1875    // we were unable to initialize the validator. Validation has been turned off and 
1876    // a warning has already been logged so just return.
1877    if (validator == null)
1878      return Status.OK_STATUS;
1879    // otherwise call the API and throw an exception if appropriate
1880    final IStatus[] status = new IStatus[1];
1881    ISafeRunnable body = new ISafeRunnable() {
1882      public void run() throws Exception {
1883        status[0] = validator.validateEdit(files, context);
1884      }
1885
1886      public void handleException(Throwable exception) {
1887        status[0] = new ResourceStatus(IStatus.ERROR, null, Policy.bind("resources.errorValidator"), exception); //$NON-NLS-1$
1888      }
1889    };
1890    Platform.run(body);
1891    return status[0];
1892  }
1893
1894  /* (non-Javadoc)
1895   * @see IWorkspace#validateLinkLocation(IResource, IPath)
1896   */
1897  public IStatus validateLinkLocation(IResource resource, IPath unresolvedLocation) {
1898    String message;
1899    //check if resource linking is disabled
1900    if (ResourcesPlugin.getPlugin().getPluginPreferences().getBoolean(ResourcesPlugin.PREF_DISABLE_LINKING)) {
1901      message = Policy.bind("links.workspaceVeto", resource.getName()); //$NON-NLS-1$
1902      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1903    }
1904    //check that the resource has a project as its parent
1905    IContainer parent = resource.getParent();
1906    if (parent == null || parent.getType() != IResource.PROJECT) {
1907      message = Policy.bind("links.parentNotProject", resource.getName()); //$NON-NLS-1$
1908      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1909    }
1910    if (!parent.isAccessible()) {
1911      message = Policy.bind("links.parentNotAccessible", resource.getFullPath().toString()); //$NON-NLS-1$
1912      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1913    }
1914    IPath location = getPathVariableManager().resolvePath(unresolvedLocation);
1915    //check nature veto
1916    String[] natureIds = ((Project) parent).internalGetDescription().getNatureIds();
1917
1918    IStatus result = getNatureManager().validateLinkCreation(natureIds);
1919    if (!result.isOK())
1920      return result;
1921    //check team provider veto
1922    if (resource.getType() == IResource.FILE)
1923      result = getTeamHook().validateCreateLink((IFile) resource, IResource.NONE, location);
1924    else
1925      result = getTeamHook().validateCreateLink((IFolder) resource, IResource.NONE, location);
1926    if (!result.isOK())
1927      return result;
1928    if (location.isEmpty()) {
1929      message = Policy.bind("links.noPath"); //$NON-NLS-1$
1930      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1931    }
1932    //check the standard path name restrictions
1933    int segmentCount = location.segmentCount();
1934    for (int i = 0; i < segmentCount; i++) {
1935      result = validateName(location.segment(i), resource.getType());
1936      if (!result.isOK())
1937        return result;
1938    }
1939    //if the location doesn't have a device, see if the OS will assign one
1940    if (location.isAbsolute() && location.getDevice() == null)
1941      location = new Path(location.toFile().getAbsolutePath());
1942    // test if the given location overlaps the platform metadata location
1943    IPath testLocation = getMetaArea().getLocation();
1944    if (isOverlapping(location, testLocation, true)) {
1945      message = Policy.bind("links.invalidLocation", location.toOSString()); //$NON-NLS-1$
1946      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1947    }
1948    //test if the given path overlaps the location of the given project
1949    testLocation = resource.getProject().getLocation();
1950    if (testLocation != null && isOverlapping(location, testLocation, false)) {
1951      message = Policy.bind("links.locationOverlapsProject", location.toOSString()); //$NON-NLS-1$
1952      return new ResourceStatus(IResourceStatus.INVALID_VALUE, resource.getFullPath(), message);
1953    }
1954    //warnings (all errors must be checked before all warnings)
1955    //check that the location is absolute
1956    if (!location.isAbsolute()) {
1957      //we know there is at least one segment, because of previous isEmpty check
1958      message = Policy.bind("pathvar.undefined", location.toOSString(), location.segment(0)); //$NON-NLS-1$
1959      return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED_WARNING, resource.getFullPath(), message);
1960    }
1961    // Iterate over each known project and ensure that the location does not
1962    // conflict with any project locations or linked resource locations
1963    IProject[] projects = getRoot().getProjects();
1964    for (int i = 0; i < projects.length; i++) {
1965      IProject project = projects[i];
1966      // since we are iterating over the project in the workspace, we
1967      // know that they have been created before and must have a description
1968      IProjectDescription desc = ((Project) project).internalGetDescription();
1969      testLocation = desc.getLocation();
1970      if (testLocation != null && isOverlapping(location, testLocation, true)) {
1971        message = Policy.bind("links.overlappingResource", location.toOSString()); //$NON-NLS-1$
1972        return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
1973      }
1974      //iterate over linked resources and check for overlap
1975      if (!project.isOpen())
1976        continue;
1977      IResource[] children = null;
1978      try {
1979        children = project.members();
1980      } catch (CoreException e) {
1981        //ignore projects that cannot be accessed
1982      }
1983      if (children == null)
1984        continue;
1985      for (int j = 0; j < children.length; j++) {
1986        if (children[j].isLinked()) {
1987          testLocation = children[j].getLocation();
1988          if (testLocation != null && isOverlapping(location, testLocation, true)) {
1989            message = Policy.bind("links.overlappingResource", location.toOSString()); //$NON-NLS-1$
1990            return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, resource.getFullPath(), message);
1991          }
1992        }
1993      }
1994    }
1995    return Status.OK_STATUS;
1996  }
1997
1998  /* (non-Javadoc)
1999   * @see IWorkspace#validateName(String, int)
2000   */
2001  public IStatus validateName(String segment, int type) {
2002    String message;
2003
2004    /* segment must not be null */
2005    if (segment == null) {
2006      message = Policy.bind("resources.nameNull"); //$NON-NLS-1$
2007      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2008    }
2009
2010    // cannot be an empty string
2011    if (segment.length() == 0) {
2012      message = Policy.bind("resources.nameEmpty"); //$NON-NLS-1$
2013      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2014    }
2015
2016    /* segment must not begin or end with a whitespace */
2017    if (Character.isWhitespace(segment.charAt(0)) || Character.isWhitespace(segment.charAt(segment.length() - 1))) {
2018      message = Policy.bind("resources.invalidWhitespace", segment); //$NON-NLS-1$
2019      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2020    }
2021
2022    /* segment must not end with a dot */
2023    if (segment.endsWith(".")) { //$NON-NLS-1$
2024      message = Policy.bind("resources.invalidDot", segment); //$NON-NLS-1$
2025      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2026    }
2027
2028    /* test invalid characters */
2029    char[] chars = OS.INVALID_RESOURCE_CHARACTERS;
2030    for (int i = 0; i < chars.length; i++)
2031      if (segment.indexOf(chars[i]) != -1) {
2032        message = Policy.bind("resources.invalidCharInName", String.valueOf(chars[i]), segment); //$NON-NLS-1$
2033        return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2034      }
2035
2036    /* test invalid OS names */
2037    if (!OS.isNameValid(segment)) {
2038      message = Policy.bind("resources.invalidName", segment); //$NON-NLS-1$
2039      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2040    }
2041    return Status.OK_STATUS;
2042  }
2043
2044  /* (non-Javadoc)
2045   * @see IWorkspace#validateNatureSet(String[])
2046   */
2047  public IStatus validateNatureSet(String[] natureIds) {
2048    return natureManager.validateNatureSet(natureIds);
2049  }
2050
2051  /* (non-Javadoc)
2052   * @see IWorkspace#validatePath(String, int)
2053   */
2054  public IStatus validatePath(String path, int type) {
2055    /* path must not be null */
2056    if (path == null) {
2057      String message = Policy.bind("resources.pathNull"); //$NON-NLS-1$
2058      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2059    }
2060    return validatePath(new Path(path), type, false);
2061  }
2062
2063  /**
2064   * Validates that the given workspace path is valid for the given type.  If 
2065   * <code>lastSegmentOnly</code> is true, it is assumed that all segments except
2066   * the last one have previously been validated.  This is an optimization for validating
2067   * a leaf resource when it is known that the parent exists (and thus its parent path
2068   * must already be valid).
2069   */
2070  public IStatus validatePath(IPath path, int type, boolean lastSegmentOnly) {
2071    String message;
2072
2073    /* path must not be null */
2074    if (path == null) {
2075      message = Policy.bind("resources.pathNull"); //$NON-NLS-1$
2076      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2077    }
2078
2079    /* path must not have a device separator */
2080    if (path.getDevice() != null) {
2081      message = Policy.bind("resources.invalidCharInPath", String.valueOf(IPath.DEVICE_SEPARATOR), path.toString()); //$NON-NLS-1$
2082      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2083    }
2084
2085    /* path must not be the root path */
2086    if (path.isRoot()) {
2087      message = Policy.bind("resources.invalidRoot"); //$NON-NLS-1$
2088      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2089    }
2090
2091    /* path must be absolute */
2092    if (!path.isAbsolute()) {
2093      message = Policy.bind("resources.mustBeAbsolute", path.toString()); //$NON-NLS-1$
2094      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2095    }
2096
2097    /* validate segments */
2098    int numberOfSegments = path.segmentCount();
2099    if ((type & IResource.PROJECT) != 0) {
2100      if (numberOfSegments == ICoreConstants.PROJECT_SEGMENT_LENGTH) {
2101        return validateName(path.segment(0), IResource.PROJECT);
2102      } else if (type == IResource.PROJECT) {
2103        message = Policy.bind("resources.projectPath", path.toString()); //$NON-NLS-1$
2104        return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2105      }
2106    }
2107    if ((type & (IResource.FILE | IResource.FOLDER)) != 0) {
2108      if (numberOfSegments < ICoreConstants.MINIMUM_FILE_SEGMENT_LENGTH) {
2109        message = Policy.bind("resources.resourcePath", path.toString()); //$NON-NLS-1$
2110        return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2111      }
2112      int fileFolderType = type &= ~IResource.PROJECT;
2113      int segmentCount = path.segmentCount();
2114      if (lastSegmentOnly)
2115        return validateName(path.segment(segmentCount - 1), fileFolderType);
2116      IStatus status = validateName(path.segment(0), IResource.PROJECT);
2117      if (!status.isOK())
2118        return status;
2119      // ignore first segment (the project)
2120      for (int i = 1; i < segmentCount; i++) {
2121        status = validateName(path.segment(i), fileFolderType);
2122        if (!status.isOK())
2123          return status;
2124      }
2125      return Status.OK_STATUS;
2126    }
2127    message = Policy.bind("resources.invalidPath", path.toString()); //$NON-NLS-1$
2128    return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2129  }
2130
2131  /* (non-Javadoc)
2132   * @see IWorkspace#validateProjectLocation(IProject, IPath)
2133   */
2134  public IStatus validateProjectLocation(IProject context, IPath unresolvedLocation) {
2135    String message;
2136    // the default default is ok for all projects
2137    if (unresolvedLocation == null) {
2138      return Status.OK_STATUS;
2139    }
2140    //check the standard path name restrictions
2141    IPath location = getPathVariableManager().resolvePath(unresolvedLocation);
2142    int segmentCount = location.segmentCount();
2143    for (int i = 0; i < segmentCount; i++) {
2144      IStatus result = validateName(location.segment(i), IResource.PROJECT);
2145      if (!result.isOK())
2146        return result;
2147    }
2148    //check that the location is absolute
2149    if (!location.isAbsolute()) {
2150      if (location.segmentCount() > 0)
2151        message = Policy.bind("pathvar.undefined", location.toOSString(), location.segment(0)); //$NON-NLS-1$
2152      else
2153        message = Policy.bind("links.noPath"); //$NON-NLS-1$
2154      return new ResourceStatus(IResourceStatus.VARIABLE_NOT_DEFINED, null, message);
2155    }
2156    //if the location doesn't have a device, see if the OS will assign one
2157    if (location.getDevice() == null)
2158      location = new Path(location.toFile().getAbsolutePath());
2159    // test if the given location overlaps the default default location
2160    IPath defaultDefaultLocation = Platform.getLocation();
2161    if (isOverlapping(location, defaultDefaultLocation, true)) {
2162      message = Policy.bind("resources.overlapLocal", location.toString(), defaultDefaultLocation.toString()); //$NON-NLS-1$
2163      return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2164    }
2165    // Iterate over each known project and ensure that the location does not
2166    // conflict with any of their already defined locations.
2167    IProject[] projects = getRoot().getProjects();
2168    for (int j = 0; j < projects.length; j++) {
2169      IProject project = projects[j];
2170      // since we are iterating over the project in the workspace, we
2171      // know that they have been created before and must have a description
2172      IProjectDescription desc = ((Project) project).internalGetDescription();
2173      IPath definedLocalLocation = desc.getLocation();
2174      // if the project uses the default location then continue
2175      if (definedLocalLocation == null)
2176        continue;
2177      //tolerate locations being the same if this is the project being tested
2178      if (project.equals(context) && definedLocalLocation.equals(location))
2179        continue;
2180      if (isOverlapping(location, definedLocalLocation, true)) {
2181        message = Policy.bind("resources.overlapLocal", location.toString(), definedLocalLocation.toString()); //$NON-NLS-1$
2182        return new ResourceStatus(IResourceStatus.INVALID_VALUE, null, message);
2183      }
2184    }
2185    //if this project exists and has linked resources, the project location cannot overlap
2186    //the locations of any linked resources in that project
2187    if (context.exists() && context.isOpen()) {
2188      IResource[] children = null;
2189      try {
2190        children = context.members();
2191      } catch (CoreException e) {
2192        //ignore projects that cannot be accessed
2193      }
2194      if (children != null) {
2195        for (int i = 0; i < children.length; i++) {
2196          if (children[i].isLinked()) {
2197            IPath testLocation = children[i].getLocation();
2198            if (testLocation != null && isOverlapping(testLocation, location, false)) {
2199              message = Policy.bind("links.locationOverlapsLink", location.toOSString()); //$NON-NLS-1$
2200              return new ResourceStatus(IResourceStatus.OVERLAPPING_LOCATION, context.getFullPath(), message);
2201            }
2202          }
2203        }
2204      }
2205    }
2206    return Status.OK_STATUS;
2207  }
2208
2209  /**
2210   * Internal method. To be called only from the following methods:
2211   * <ul>
2212   * <li><code>IFile#appendContents</code></li>
2213   * <li><code>IFile#setContents(InputStream, boolean, boolean, IProgressMonitor)</code></li>
2214   * <li><code>IFile#setContents(IFileState, boolean, boolean, IProgressMonitor)</code></li>
2215   * </ul>
2216   * 
2217   * @see IFileModificationValidator#validateSave(IFile)
2218   */
2219  protected void validateSave(final IFile file) throws CoreException {
2220    // if validation is turned off then just return
2221    if (!shouldValidate)
2222      return;
2223    // first time through the validator hasn't been initialized so try and create it
2224    if (validator == null)
2225      initializeValidator();
2226    // we were unable to initialize the validator. Validation has been turned off and 
2227    // a warning has already been logged so just return.
2228    if (validator == null)
2229      return;
2230    // otherwise call the API and throw an exception if appropriate
2231    final IStatus[] status = new IStatus[1];
2232    ISafeRunnable body = new ISafeRunnable() {
2233      public void run() throws Exception {
2234        status[0] = validator.validateSave(file);
2235      }
2236
2237      public void handleException(Throwable exception) {
2238        status[0] = new ResourceStatus(IStatus.ERROR, null, Policy.bind("resources.errorValidator"), exception); //$NON-NLS-1$
2239      }
2240    };
2241    Platform.run(body);
2242    if (!status[0].isOK())
2243      throw new ResourceException(status[0]);
2244  }
2245}