001: /*******************************************************************************
002: * Copyright (c) 2006, 2007 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.ide.undo;
011:
012: import java.util.ArrayList;
013: import java.util.List;
014:
015: import org.eclipse.core.commands.operations.IUndoContext;
016: import org.eclipse.core.commands.operations.ObjectUndoContext;
017: import org.eclipse.core.resources.IContainer;
018: import org.eclipse.core.resources.IFile;
019: import org.eclipse.core.resources.IProject;
020: import org.eclipse.core.resources.IResource;
021: import org.eclipse.core.resources.IResourceStatus;
022: import org.eclipse.core.resources.IWorkspace;
023: import org.eclipse.core.resources.IWorkspaceRoot;
024: import org.eclipse.core.resources.ResourcesPlugin;
025: import org.eclipse.core.runtime.CoreException;
026: import org.eclipse.core.runtime.IAdaptable;
027: import org.eclipse.core.runtime.IPath;
028: import org.eclipse.core.runtime.IProgressMonitor;
029: import org.eclipse.core.runtime.IStatus;
030: import org.eclipse.core.runtime.MultiStatus;
031: import org.eclipse.core.runtime.NullProgressMonitor;
032: import org.eclipse.core.runtime.OperationCanceledException;
033: import org.eclipse.core.runtime.Status;
034: import org.eclipse.core.runtime.SubProgressMonitor;
035: import org.eclipse.jface.dialogs.IDialogConstants;
036: import org.eclipse.jface.dialogs.MessageDialog;
037: import org.eclipse.osgi.util.NLS;
038: import org.eclipse.swt.widgets.Shell;
039: import org.eclipse.ui.PlatformUI;
040: import org.eclipse.ui.internal.WorkbenchPlugin;
041: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
042: import org.eclipse.ui.internal.ide.undo.ContainerDescription;
043: import org.eclipse.ui.internal.ide.undo.FileDescription;
044: import org.eclipse.ui.internal.ide.undo.UndoMessages;
045:
046: /**
047: * WorkspaceUndoUtil defines common utility methods and constants used by
048: * clients who create undoable workspace operations.
049: *
050: * @since 3.3
051: *
052: */
053: public class WorkspaceUndoUtil {
054:
055: private static ObjectUndoContext tasksUndoContext;
056:
057: private static ObjectUndoContext bookmarksUndoContext;
058:
059: /**
060: * Return the undo context that should be used for workspace-wide operations
061: *
062: * @return the undo context suitable for workspace-level operations.
063: */
064: public static IUndoContext getWorkspaceUndoContext() {
065: return WorkbenchPlugin.getDefault().getOperationSupport()
066: .getUndoContext();
067: }
068:
069: /**
070: * Return the undo context that should be used for operations involving
071: * tasks.
072: *
073: * @return the tasks undo context
074: */
075: public static IUndoContext getTasksUndoContext() {
076: if (tasksUndoContext == null) {
077: tasksUndoContext = new ObjectUndoContext(new Object(),
078: "Tasks Context"); //$NON-NLS-1$
079: tasksUndoContext.addMatch(getWorkspaceUndoContext());
080: }
081: return tasksUndoContext;
082: }
083:
084: /**
085: * Return the undo context that should be used for operations involving
086: * bookmarks.
087: *
088: * @return the bookmarks undo context
089: */
090: public static IUndoContext getBookmarksUndoContext() {
091: if (bookmarksUndoContext == null) {
092: bookmarksUndoContext = new ObjectUndoContext(new Object(),
093: "Bookmarks Context"); //$NON-NLS-1$
094: bookmarksUndoContext.addMatch(getWorkspaceUndoContext());
095: }
096: return bookmarksUndoContext;
097: }
098:
099: /**
100: * Make an <code>IAdaptable</code> that adapts to the specified shell,
101: * suitable for passing for passing to any
102: * {@link org.eclipse.core.commands.operations.IUndoableOperation} or
103: * {@link org.eclipse.core.commands.operations.IOperationHistory} method
104: * that requires an {@link org.eclipse.core.runtime.IAdaptable}
105: * <code>uiInfo</code> parameter.
106: *
107: * @param shell
108: * the shell that should be returned by the IAdaptable when asked
109: * to adapt a shell. If this parameter is <code>null</code>,
110: * the returned shell will also be <code>null</code>.
111: *
112: * @return an IAdaptable that will return the specified shell.
113: */
114: public static IAdaptable getUIInfoAdapter(final Shell shell) {
115: return new IAdaptable() {
116: public Object getAdapter(Class clazz) {
117: if (clazz == Shell.class) {
118: return shell;
119: }
120: return null;
121: }
122: };
123: }
124:
125: private WorkspaceUndoUtil() {
126: // should not construct
127: }
128:
129: /**
130: * Delete all of the specified resources, returning resource descriptions
131: * that can be used to restore them.
132: *
133: * @param resourcesToDelete
134: * an array of resources to be deleted
135: * @param monitor
136: * the progress monitor to use to show the operation's progress
137: * @param uiInfo
138: * the IAdaptable (or <code>null</code>) provided by the
139: * caller in order to supply UI information for prompting the
140: * user if necessary. When this parameter is not
141: * <code>null</code>, it contains an adapter for the
142: * org.eclipse.swt.widgets.Shell.class
143: *
144: * @param deleteContent
145: * a boolean indicating whether project content should be deleted
146: * when a project resource is to be deleted
147: * @return an array of ResourceDescriptions that can be used to restore the
148: * deleted resources.
149: * @throws CoreException
150: * propagates any CoreExceptions thrown from the resources API
151: */
152: static ResourceDescription[] delete(IResource[] resourcesToDelete,
153: IProgressMonitor monitor, IAdaptable uiInfo,
154: boolean deleteContent) throws CoreException {
155: final List exceptions = new ArrayList();
156: boolean forceOutOfSyncDelete = false;
157: ResourceDescription[] returnedResourceDescriptions = new ResourceDescription[resourcesToDelete.length];
158: monitor.beginTask("", resourcesToDelete.length); //$NON-NLS-1$
159: monitor
160: .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
161: try {
162: for (int i = 0; i < resourcesToDelete.length; ++i) {
163: if (monitor.isCanceled()) {
164: throw new OperationCanceledException();
165: }
166: IResource resource = resourcesToDelete[i];
167: try {
168: returnedResourceDescriptions[i] = delete(resource,
169: new SubProgressMonitor(monitor, 1), uiInfo,
170: forceOutOfSyncDelete, deleteContent);
171: } catch (CoreException e) {
172: if (resource.getType() == IResource.FILE) {
173: IStatus[] children = e.getStatus()
174: .getChildren();
175: if (children.length == 1
176: && children[0].getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
177: int result = queryDeleteOutOfSync(resource,
178: uiInfo);
179:
180: if (result == IDialogConstants.YES_ID) {
181: // retry the delete with a force out of sync
182: delete(resource,
183: new SubProgressMonitor(monitor,
184: 1), uiInfo, true,
185: deleteContent);
186: } else if (result == IDialogConstants.YES_TO_ALL_ID) {
187: // all future attempts should force out of
188: // sync
189: forceOutOfSyncDelete = true;
190: delete(resource,
191: new SubProgressMonitor(monitor,
192: 1), uiInfo,
193: forceOutOfSyncDelete,
194: deleteContent);
195: } else if (result == IDialogConstants.CANCEL_ID) {
196: throw new OperationCanceledException();
197: } else {
198: exceptions.add(e);
199: }
200: } else {
201: exceptions.add(e);
202: }
203: } else {
204: exceptions.add(e);
205: }
206: }
207: }
208: IStatus result = createResult(exceptions);
209: if (!result.isOK()) {
210: throw new CoreException(result);
211: }
212: } finally {
213: monitor.done();
214: }
215: return returnedResourceDescriptions;
216: }
217:
218: /**
219: * Copies the resources to the given destination. This method can be called
220: * recursively to merge folders during folder copy.
221: *
222: * @param resources
223: * the resources to be copied
224: * @param destination
225: * the destination path for the resources, relative to the
226: * workspace
227: * @param resourcesAtDestination
228: * A list used to record the new copies.
229: * @param monitor
230: * the progress monitor used to show progress
231: * @param uiInfo
232: * the IAdaptable (or <code>null</code>) provided by the
233: * caller in order to supply UI information for prompting the
234: * user if necessary. When this parameter is not
235: * <code>null</code>, it contains an adapter for the
236: * org.eclipse.swt.widgets.Shell.class
237: * @param pathIncludesName
238: * a boolean that indicates whether the specified path includes
239: * the resource's name at the destination. If this value is
240: * <code>true</code>, the destination will contain the desired
241: * name of the resource (usually only desired when only one
242: * resource is being copied). If this value is <code>false</code>,
243: * each resource's name will be appended to the destination.
244: * @return an array of ResourceDescriptions describing any resources that
245: * were overwritten by the copy operation
246: * @throws CoreException
247: * propagates any CoreExceptions thrown from the resources API
248: */
249: static ResourceDescription[] copy(IResource[] resources,
250: IPath destination, List resourcesAtDestination,
251: IProgressMonitor monitor, IAdaptable uiInfo,
252: boolean pathIncludesName) throws CoreException {
253:
254: monitor.beginTask("", resources.length); //$NON-NLS-1$
255: monitor
256: .setTaskName(UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress);
257: List overwrittenResources = new ArrayList();
258: for (int i = 0; i < resources.length; i++) {
259: IResource source = resources[i];
260: IPath destinationPath;
261: if (pathIncludesName) {
262: destinationPath = destination;
263: } else {
264: destinationPath = destination.append(source.getName());
265: }
266: IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
267: IResource existing = workspaceRoot
268: .findMember(destinationPath);
269: if (source.getType() == IResource.FOLDER
270: && existing != null) {
271: // The resource is a folder and it exists in the destination.
272: // Copy its children to the existing destination.
273: if (source.isLinked() == existing.isLinked()) {
274: IResource[] children = ((IContainer) source)
275: .members();
276: ResourceDescription[] overwritten = copy(children,
277: destinationPath, resourcesAtDestination,
278: new SubProgressMonitor(monitor, 1), uiInfo,
279: false);
280: // We don't record the copy since this recursive call will
281: // do so. Just record the overwrites.
282: for (int j = 0; j < overwritten.length; j++) {
283: overwrittenResources.add(overwritten[j]);
284: }
285: } else {
286: // delete the destination folder, copying a linked folder
287: // over an unlinked one or vice versa. Fixes bug 28772.
288: ResourceDescription[] deleted = delete(
289: new IResource[] { existing },
290: new SubProgressMonitor(monitor, 0), uiInfo,
291: false);
292: source.copy(destinationPath, IResource.SHALLOW,
293: new SubProgressMonitor(monitor, 1));
294: // Record the copy
295: resourcesAtDestination.add(getWorkspace().getRoot()
296: .findMember(destinationPath));
297: for (int j = 0; j < deleted.length; j++) {
298: overwrittenResources.add(deleted[j]);
299: }
300: }
301: } else {
302: if (existing != null) {
303: if (source.isLinked() == existing.isLinked()) {
304: overwrittenResources
305: .add(copyOverExistingResource(source,
306: existing,
307: new SubProgressMonitor(monitor,
308: 1), uiInfo, false));
309: // Record the "copy"
310: resourcesAtDestination.add(existing);
311: } else {
312: // Copying a linked resource over unlinked or vice
313: // versa. Can't use setContents here. Fixes bug 28772.
314: ResourceDescription[] deleted = delete(
315: new IResource[] { existing },
316: new SubProgressMonitor(monitor, 0),
317: uiInfo, false);
318: source.copy(destinationPath, IResource.SHALLOW,
319: new SubProgressMonitor(monitor, 1));
320: // Record the copy
321: resourcesAtDestination.add(getWorkspace()
322: .getRoot().findMember(destinationPath));
323: for (int j = 0; j < deleted.length; j++) {
324: overwrittenResources.add(deleted[j]);
325: }
326: }
327: } else {
328: // no resources are being overwritten
329: // ensure the destination path exists
330: IPath parentPath = destination;
331: if (pathIncludesName) {
332: parentPath = destination.removeLastSegments(1);
333: }
334: IContainer generatedParent = generateContainers(parentPath);
335: source.copy(destinationPath, IResource.SHALLOW,
336: new SubProgressMonitor(monitor, 1));
337: // Record the copy. If we had to generate a parent
338: // folder, that should be recorded as part of the copy
339: if (generatedParent == null) {
340: resourcesAtDestination.add(getWorkspace()
341: .getRoot().findMember(destinationPath));
342: } else {
343: resourcesAtDestination.add(generatedParent);
344: }
345: }
346:
347: if (monitor.isCanceled()) {
348: throw new OperationCanceledException();
349: }
350: }
351: }
352: monitor.done();
353: return (ResourceDescription[]) overwrittenResources
354: .toArray(new ResourceDescription[overwrittenResources
355: .size()]);
356:
357: }
358:
359: /**
360: * Moves the resources to the given destination. This method can be called
361: * recursively to merge folders during folder move.
362: *
363: * @param resources
364: * the resources to be moved
365: * @param destination
366: * the destination path for the resources, relative to the
367: * workspace
368: * @param resourcesAtDestination
369: * A list used to record each moved resource.
370: * @param reverseDestinations
371: * A list used to record each moved resource's original location
372: * @param monitor
373: * the progress monitor used to show progress
374: * @param uiInfo
375: * the IAdaptable (or <code>null</code>) provided by the
376: * caller in order to supply UI information for prompting the
377: * user if necessary. When this parameter is not
378: * <code>null</code>, it contains an adapter for the
379: * org.eclipse.swt.widgets.Shell.class
380: * @param pathIncludesName
381: * a boolean that indicates whether the specified path includes
382: * the resource's name at the destination. If this value is
383: * <code>true</code>, the destination will contain the desired
384: * name of the resource (usually only desired when only one
385: * resource is being moved). If this value is <code>false</code>,
386: * each resource's name will be appended to the destination.
387: * @return an array of ResourceDescriptions describing any resources that
388: * were overwritten by the move operation
389: * @throws CoreException
390: * propagates any CoreExceptions thrown from the resources API
391: */
392: static ResourceDescription[] move(IResource[] resources,
393: IPath destination, List resourcesAtDestination,
394: List reverseDestinations, IProgressMonitor monitor,
395: IAdaptable uiInfo, boolean pathIncludesName)
396: throws CoreException {
397:
398: monitor.beginTask("", resources.length); //$NON-NLS-1$
399: monitor
400: .setTaskName(UndoMessages.AbstractResourcesOperation_MovingResources);
401: List overwrittenResources = new ArrayList();
402: for (int i = 0; i < resources.length; i++) {
403: IResource source = resources[i];
404: IPath destinationPath;
405: if (pathIncludesName) {
406: destinationPath = destination;
407: } else {
408: destinationPath = destination.append(source.getName());
409: }
410: IWorkspaceRoot workspaceRoot = getWorkspaceRoot();
411: IResource existing = workspaceRoot
412: .findMember(destinationPath);
413: if (source.getType() == IResource.FOLDER
414: && existing != null) {
415: // The resource is a folder and it exists in the destination.
416: // Move its children to the existing destination.
417: if (source.isLinked() == existing.isLinked()) {
418: IResource[] children = ((IContainer) source)
419: .members();
420: ResourceDescription[] overwritten = move(children,
421: destinationPath, resourcesAtDestination,
422: reverseDestinations,
423: new SubProgressMonitor(monitor, 1), uiInfo,
424: false);
425: // We don't record the moved resources since the recursive
426: // call has done so. Just record the overwrites.
427: for (int j = 0; j < overwritten.length; j++) {
428: overwrittenResources.add(overwritten[j]);
429: }
430: // Delete the source. No need to record it since it
431: // will get moved back.
432: delete(source, monitor, uiInfo, false, false);
433: } else {
434: // delete the destination folder, moving a linked folder
435: // over an unlinked one or vice versa. Fixes bug 28772.
436: ResourceDescription[] deleted = delete(
437: new IResource[] { existing },
438: new SubProgressMonitor(monitor, 0), uiInfo,
439: false);
440: // Record the original path
441: reverseDestinations.add(source.getFullPath());
442: source.move(destinationPath, IResource.SHALLOW
443: | IResource.KEEP_HISTORY,
444: new SubProgressMonitor(monitor, 1));
445: // Record the resource at its destination
446: resourcesAtDestination.add(getWorkspace().getRoot()
447: .findMember(destinationPath));
448: for (int j = 0; j < deleted.length; j++) {
449: overwrittenResources.add(deleted[j]);
450: }
451: }
452: } else {
453: if (existing != null) {
454: if (source.isLinked() == existing.isLinked()) {
455: // Record the original path
456: reverseDestinations.add(source.getFullPath());
457: overwrittenResources
458: .add(copyOverExistingResource(source,
459: existing,
460: new SubProgressMonitor(monitor,
461: 1), uiInfo, true));
462: resourcesAtDestination.add(existing);
463: } else {
464: // Moving a linked resource over unlinked or vice
465: // versa. Can't use setContents here. Fixes bug 28772.
466: ResourceDescription[] deleted = delete(
467: new IResource[] { existing },
468: new SubProgressMonitor(monitor, 0),
469: uiInfo, false);
470: reverseDestinations.add(source.getFullPath());
471: source.move(destinationPath, IResource.SHALLOW
472: | IResource.KEEP_HISTORY,
473: new SubProgressMonitor(monitor, 1));
474: // Record the resource at its destination
475: resourcesAtDestination.add(getWorkspace()
476: .getRoot().findMember(destinationPath));
477: for (int j = 0; j < deleted.length; j++) {
478: overwrittenResources.add(deleted[j]);
479: }
480: }
481: } else {
482: // No resources are being overwritten.
483: // First record the source path
484: reverseDestinations.add(source.getFullPath());
485: // ensure the destination path exists
486: IPath parentPath = destination;
487: if (pathIncludesName) {
488: parentPath = destination.removeLastSegments(1);
489: }
490:
491: IContainer generatedParent = generateContainers(parentPath);
492: source.move(destinationPath, IResource.SHALLOW
493: | IResource.KEEP_HISTORY,
494: new SubProgressMonitor(monitor, 1));
495: // Record the move. If we had to generate a parent
496: // folder, that should be recorded as part of the copy
497: if (generatedParent == null) {
498: resourcesAtDestination.add(getWorkspace()
499: .getRoot().findMember(destinationPath));
500: } else {
501: resourcesAtDestination.add(generatedParent);
502: }
503: }
504:
505: if (monitor.isCanceled()) {
506: throw new OperationCanceledException();
507: }
508: }
509: }
510: monitor.done();
511: return (ResourceDescription[]) overwrittenResources
512: .toArray(new ResourceDescription[overwrittenResources
513: .size()]);
514:
515: }
516:
517: /**
518: * Recreate the resources from the specified resource descriptions.
519: *
520: * @param resourcesToRecreate
521: * the ResourceDescriptions describing resources to be recreated
522: * @param monitor
523: * the progress monitor used to show progress
524: * @param uiInfo
525: * the IAdaptable (or <code>null</code>) provided by the
526: * caller in order to supply UI information for prompting the
527: * user if necessary. When this parameter is not
528: * <code>null</code>, it contains an adapter for the
529: * org.eclipse.swt.widgets.Shell.class
530: * @return an array of resources that were created
531: * @throws CoreException
532: * propagates any CoreExceptions thrown from the resources API
533: */
534: static IResource[] recreate(
535: ResourceDescription[] resourcesToRecreate,
536: IProgressMonitor monitor, IAdaptable uiInfo)
537: throws CoreException {
538: final List exceptions = new ArrayList();
539: IResource[] resourcesToReturn = new IResource[resourcesToRecreate.length];
540: monitor.beginTask("", resourcesToRecreate.length); //$NON-NLS-1$
541: monitor
542: .setTaskName(UndoMessages.AbstractResourcesOperation_CreateResourcesProgress);
543: try {
544: for (int i = 0; i < resourcesToRecreate.length; ++i) {
545: if (monitor.isCanceled()) {
546: throw new OperationCanceledException();
547: }
548: try {
549: resourcesToReturn[i] = resourcesToRecreate[i]
550: .createResource(new SubProgressMonitor(
551: monitor, 1));
552: } catch (CoreException e) {
553: exceptions.add(e);
554: }
555: }
556: IStatus result = WorkspaceUndoUtil.createResult(exceptions);
557: if (!result.isOK()) {
558: throw new CoreException(result);
559: }
560: } finally {
561: monitor.done();
562: }
563: return resourcesToReturn;
564: }
565:
566: /**
567: * Delete the specified resources, returning a resource description that can
568: * be used to restore it.
569: *
570: * @param resourceToDelete
571: * the resource to be deleted
572: * @param monitor
573: * the progress monitor to use to show the operation's progress
574: * @param uiInfo
575: * the IAdaptable (or <code>null</code>) provided by the
576: * caller in order to supply UI information for prompting the
577: * user if necessary. When this parameter is not
578: * <code>null</code>, it contains an adapter for the
579: * org.eclipse.swt.widgets.Shell.class
580: * @param forceOutOfSyncDelete
581: * a boolean indicating whether a resource should be deleted even
582: * if it is out of sync with the file system
583: * @param deleteContent
584: * a boolean indicating whether project content should be deleted
585: * when a project resource is to be deleted
586: * @return a ResourceDescription that can be used to restore the deleted
587: * resource.
588: * @throws CoreException
589: * propagates any CoreExceptions thrown from the resources API
590: */
591: static ResourceDescription delete(IResource resourceToDelete,
592: IProgressMonitor monitor, IAdaptable uiInfo,
593: boolean forceOutOfSyncDelete, boolean deleteContent)
594: throws CoreException {
595: ResourceDescription resourceDescription = ResourceDescription
596: .fromResource(resourceToDelete);
597: if (resourceToDelete.getType() == IResource.PROJECT) {
598: // it is a project
599: monitor
600: .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
601: IProject project = (IProject) resourceToDelete;
602: project
603: .delete(deleteContent, forceOutOfSyncDelete,
604: monitor);
605: } else {
606: // if it's not a project, just delete it
607: monitor.beginTask("", 2); //$NON-NLS-1$
608: monitor
609: .setTaskName(UndoMessages.AbstractResourcesOperation_DeleteResourcesProgress);
610: int updateFlags;
611: if (forceOutOfSyncDelete) {
612: updateFlags = IResource.KEEP_HISTORY | IResource.FORCE;
613: } else {
614: updateFlags = IResource.KEEP_HISTORY;
615: }
616: resourceToDelete.delete(updateFlags,
617: new SubProgressMonitor(monitor, 1));
618: resourceDescription.recordStateFromHistory(
619: resourceToDelete,
620: new SubProgressMonitor(monitor, 1));
621: monitor.done();
622: }
623:
624: return resourceDescription;
625: }
626:
627: /*
628: * Copy the content of the specified resource to the existing resource,
629: * returning a ResourceDescription that can be used to restore the original
630: * content. Do nothing if the resources are not files.
631: */
632: private static ResourceDescription copyOverExistingResource(
633: IResource source, IResource existing,
634: IProgressMonitor monitor, IAdaptable uiInfo,
635: boolean deleteSourceFile) throws CoreException {
636: if (!(source instanceof IFile && existing instanceof IFile)) {
637: return null;
638: }
639: IFile file = (IFile) source;
640: IFile existingFile = (IFile) existing;
641: monitor
642: .beginTask(
643: UndoMessages.AbstractResourcesOperation_CopyingResourcesProgress,
644: 3);
645: if (file != null && existingFile != null) {
646: if (validateEdit(file, existingFile, getShell(uiInfo))) {
647: // Remember the state of the existing file so it can be
648: // restored.
649: FileDescription fileDescription = new FileDescription(
650: existingFile);
651: // Reset the contents to that of the file being moved
652: existingFile.setContents(file.getContents(),
653: IResource.KEEP_HISTORY, new SubProgressMonitor(
654: monitor, 1));
655: fileDescription.recordStateFromHistory(existingFile,
656: new SubProgressMonitor(monitor, 1));
657: // Now delete the source file if requested
658: // We don't need to remember anything about it, because
659: // any undo involving this operation will move the original
660: // content back to it.
661: if (deleteSourceFile) {
662: file.delete(IResource.KEEP_HISTORY,
663: new SubProgressMonitor(monitor, 1));
664: }
665: monitor.done();
666: return fileDescription;
667: }
668: }
669: monitor.done();
670: return null;
671: }
672:
673: /*
674: * Check for existence of the specified path and generate any containers
675: * that do not yet exist. Return any generated containers, or null if no
676: * container had to be generated.
677: */
678: private static IContainer generateContainers(IPath path)
679: throws CoreException {
680: IResource container;
681: if (path.segmentCount() == 0) {
682: // nothing to generate
683: return null;
684: }
685: container = getWorkspaceRoot().findMember(path);
686: // Nothing to generate because container exists
687: if (container != null) {
688: return null;
689: }
690:
691: // Now make a non-existent handle representing the desired container
692: if (path.segmentCount() == 1) {
693: container = ResourcesPlugin.getWorkspace().getRoot()
694: .getProject(path.segment(0));
695: } else {
696: container = ResourcesPlugin.getWorkspace().getRoot()
697: .getFolder(path);
698: }
699: ContainerDescription containerDescription = ContainerDescription
700: .fromContainer((IContainer) container);
701: container = containerDescription.createResourceHandle();
702: containerDescription.createExistentResourceFromHandle(
703: container, new NullProgressMonitor());
704: return (IContainer) container;
705: }
706:
707: /*
708: * Ask the user whether the given resource should be deleted despite being
709: * out of sync with the file system.
710: *
711: * Return one of the IDialogConstants constants indicating which of the Yes,
712: * Yes to All, No, Cancel options has been selected by the user.
713: */
714: private static int queryDeleteOutOfSync(IResource resource,
715: IAdaptable uiInfo) {
716: Shell shell = getShell(uiInfo);
717: final MessageDialog dialog = new MessageDialog(
718: shell,
719: UndoMessages.AbstractResourcesOperation_deletionMessageTitle,
720: null,
721: NLS
722: .bind(
723: UndoMessages.AbstractResourcesOperation_outOfSyncQuestion,
724: resource.getName()),
725: MessageDialog.QUESTION, new String[] {
726: IDialogConstants.YES_LABEL,
727: IDialogConstants.YES_TO_ALL_LABEL,
728: IDialogConstants.NO_LABEL,
729: IDialogConstants.CANCEL_LABEL }, 0);
730: shell.getDisplay().syncExec(new Runnable() {
731: public void run() {
732: dialog.open();
733: }
734: });
735: int result = dialog.getReturnCode();
736: if (result == 0) {
737: return IDialogConstants.YES_ID;
738: }
739: if (result == 1) {
740: return IDialogConstants.YES_TO_ALL_ID;
741: }
742: if (result == 2) {
743: return IDialogConstants.NO_ID;
744: }
745: return IDialogConstants.CANCEL_ID;
746: }
747:
748: /*
749: * Creates and return a result status appropriate for the given list of
750: * exceptions.
751: */
752: private static IStatus createResult(List exceptions) {
753: if (exceptions.isEmpty()) {
754: return Status.OK_STATUS;
755: }
756: final int exceptionCount = exceptions.size();
757: if (exceptionCount == 1) {
758: return ((CoreException) exceptions.get(0)).getStatus();
759: }
760: CoreException[] children = (CoreException[]) exceptions
761: .toArray(new CoreException[exceptionCount]);
762: boolean outOfSync = false;
763: for (int i = 0; i < children.length; i++) {
764: if (children[i].getStatus().getCode() == IResourceStatus.OUT_OF_SYNC_LOCAL) {
765: outOfSync = true;
766: break;
767: }
768: }
769: String title = outOfSync ? UndoMessages.AbstractResourcesOperation_outOfSyncError
770: : UndoMessages.AbstractResourcesOperation_deletionExceptionMessage;
771: final MultiStatus multi = new MultiStatus(
772: IDEWorkbenchPlugin.IDE_WORKBENCH, 0, title, null);
773: for (int i = 0; i < exceptionCount; i++) {
774: CoreException exception = children[i];
775: IStatus status = exception.getStatus();
776: multi.add(new Status(status.getSeverity(), status
777: .getPlugin(), status.getCode(),
778: status.getMessage(), exception));
779: }
780: return multi;
781: }
782:
783: /*
784: * Return the workspace.
785: */
786: private static IWorkspace getWorkspace() {
787: return ResourcesPlugin.getWorkspace();
788: }
789:
790: /*
791: * Return the workspace root.
792: */
793: private static IWorkspaceRoot getWorkspaceRoot() {
794: return getWorkspace().getRoot();
795: }
796:
797: /*
798: * Validate the destination file if it is read-only and additionally the
799: * source file if both are read-only. Returns true if both files could be
800: * made writeable.
801: */
802: private static boolean validateEdit(IFile source,
803: IFile destination, Shell shell) {
804: if (destination.isReadOnly()) {
805: IWorkspace workspace = WorkspaceUndoUtil.getWorkspace();
806: IStatus status;
807: if (source.isReadOnly()) {
808: status = workspace.validateEdit(new IFile[] { source,
809: destination }, shell);
810: } else {
811: status = workspace.validateEdit(
812: new IFile[] { destination }, shell);
813: }
814: return status.isOK();
815: }
816: return true;
817: }
818:
819: /**
820: * Return the shell described by the specified adaptable, or the active
821: * shell if no shell has been specified in the adaptable.
822: *
823: * @param uiInfo
824: * the IAdaptable (or <code>null</code>) provided by the
825: * caller in order to supply UI information for prompting the
826: * user if necessary. When this parameter is not
827: * <code>null</code>, it contains an adapter for the
828: * org.eclipse.swt.widgets.Shell.class
829: *
830: * @return the Shell that can be used to show information
831: */
832: public static Shell getShell(IAdaptable uiInfo) {
833: if (uiInfo != null) {
834: Shell shell = (Shell) uiInfo.getAdapter(Shell.class);
835: if (shell != null) {
836: return shell;
837: }
838: }
839: return PlatformUI.getWorkbench().getActiveWorkbenchWindow()
840: .getShell();
841: }
842: }
|