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}