001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 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.actions;
011:
012: import java.lang.reflect.InvocationTargetException;
013: import java.util.ArrayList;
014: import java.util.Iterator;
015: import java.util.List;
016:
017: import org.eclipse.core.resources.IResource;
018: import org.eclipse.core.resources.WorkspaceJob;
019: import org.eclipse.core.runtime.CoreException;
020: import org.eclipse.core.runtime.IProgressMonitor;
021: import org.eclipse.core.runtime.IStatus;
022: import org.eclipse.core.runtime.MultiStatus;
023: import org.eclipse.core.runtime.OperationCanceledException;
024: import org.eclipse.core.runtime.Status;
025: import org.eclipse.core.runtime.SubProgressMonitor;
026: import org.eclipse.core.runtime.jobs.ISchedulingRule;
027: import org.eclipse.core.runtime.jobs.Job;
028: import org.eclipse.jface.dialogs.ErrorDialog;
029: import org.eclipse.jface.dialogs.MessageDialog;
030: import org.eclipse.jface.operation.IRunnableWithProgress;
031: import org.eclipse.jface.viewers.IStructuredSelection;
032: import org.eclipse.osgi.util.NLS;
033: import org.eclipse.swt.widgets.Shell;
034: import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
035: import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
036: import org.eclipse.ui.internal.ide.StatusUtil;
037: import org.eclipse.ui.internal.progress.ProgressMonitorJobsDialog;
038:
039: /**
040: * The abstract superclass for actions which invoke commands implemented in
041: * org.eclipse.core.* on a set of selected resources.
042: *
043: * It iterates over all selected resources; errors are collected and displayed
044: * to the user via a problems dialog at the end of the operation. User requests
045: * to cancel the operation are passed along to the core.
046: * <p>
047: * Subclasses must implement the following methods:
048: * <ul>
049: * <li><code>invokeOperation</code> - to perform the operation on one of the
050: * selected resources</li>
051: * <li><code>getOperationMessage</code> - to furnish a title for the progress
052: * dialog</li>
053: * </ul>
054: * </p>
055: * <p>
056: * Subclasses may override the following methods:
057: * <ul>
058: * <li><code>shouldPerformResourcePruning</code> - reimplement to turn off</li>
059: * <li><code>updateSelection</code> - extend to refine enablement criteria</li>
060: * <li><code>getProblemsTitle</code> - reimplement to furnish a title for the
061: * problems dialog</li>
062: * <li><code>getProblemsMessage</code> - reimplement to furnish a message for
063: * the problems dialog</li>
064: * <li><code>run</code> - extend to </li>
065: * </ul>
066: * </p>
067: */
068: public abstract class WorkspaceAction extends SelectionListenerAction {
069: /**
070: * The shell in which to show the progress and problems dialog.
071: */
072: private final Shell shell;
073:
074: /**
075: * Creates a new action with the given text.
076: *
077: * @param shell
078: * the shell (for the modal progress dialog and error messages)
079: * @param text
080: * the string used as the text for the action, or
081: * <code>null</code> if there is no text
082: */
083: protected WorkspaceAction(Shell shell, String text) {
084: super (text);
085: if (shell == null) {
086: throw new IllegalArgumentException();
087: }
088: this .shell = shell;
089: }
090:
091: /**
092: * Opens an error dialog to display the given message.
093: * <p>
094: * Note that this method must be called from UI thread.
095: * </p>
096: *
097: * @param message
098: * the message
099: */
100: void displayError(String message) {
101: if (message == null) {
102: message = IDEWorkbenchMessages.WorkbenchAction_internalError;
103: }
104: MessageDialog.openError(shell, getProblemsTitle(), message);
105: }
106:
107: /**
108: * Runs <code>invokeOperation</code> on each of the selected resources,
109: * reporting progress and fielding cancel requests from the given progress
110: * monitor.
111: * <p>
112: * Note that if an action is running in the background, the same action
113: * instance can be executed multiple times concurrently. This method must
114: * not access or modify any mutable state on action class.
115: *
116: * @param monitor
117: * a progress monitor
118: * @return The result of the execution
119: */
120: final IStatus execute(List resources, IProgressMonitor monitor) {
121: MultiStatus errors = null;
122: // 1FTIMQN: ITPCORE:WIN - clients required to do too much iteration work
123: if (shouldPerformResourcePruning()) {
124: resources = pruneResources(resources);
125: }
126: // 1FV0B3Y: ITPUI:ALL - sub progress monitors granularity issues
127: monitor.beginTask("", resources.size() * 1000); //$NON-NLS-1$
128: // Fix for bug 31768 - Don't provide a task name in beginTask
129: // as it will be appended to each subTask message. Need to
130: // call setTaskName as its the only was to assure the task name is
131: // set in the monitor (see bug 31824)
132: monitor.setTaskName(getOperationMessage());
133: Iterator resourcesEnum = resources.iterator();
134: try {
135: while (resourcesEnum.hasNext()) {
136: IResource resource = (IResource) resourcesEnum.next();
137: try {
138: // 1FV0B3Y: ITPUI:ALL - sub progress monitors granularity
139: // issues
140: invokeOperation(resource, new SubProgressMonitor(
141: monitor, 1000));
142: } catch (CoreException e) {
143: errors = recordError(errors, e);
144: }
145: if (monitor.isCanceled()) {
146: throw new OperationCanceledException();
147: }
148: }
149: return errors == null ? Status.OK_STATUS : errors;
150: } finally {
151: monitor.done();
152: }
153: }
154:
155: /**
156: * Returns the string to display for this action's operation.
157: * <p>
158: * Note that this hook method is invoked in a non-UI thread.
159: * </p>
160: * <p>
161: * Subclasses must implement this method.
162: * </p>
163: *
164: * @return the message
165: *
166: * @since 3.1
167: */
168: protected abstract String getOperationMessage();
169:
170: /**
171: * Returns the string to display for this action's problems dialog.
172: * <p>
173: * The <code>WorkspaceAction</code> implementation of this method returns
174: * a vague message (localized counterpart of something like "The following
175: * problems occurred."). Subclasses may reimplement to provide something
176: * more suited to the particular action.
177: * </p>
178: *
179: * @return the problems message
180: *
181: * @since 3.1
182: */
183: protected String getProblemsMessage() {
184: return IDEWorkbenchMessages.WorkbenchAction_problemsMessage;
185: }
186:
187: /**
188: * Returns the title for this action's problems dialog.
189: * <p>
190: * The <code>WorkspaceAction</code> implementation of this method returns
191: * a generic title (localized counterpart of "Problems"). Subclasses may
192: * reimplement to provide something more suited to the particular action.
193: * </p>
194: *
195: * @return the problems dialog title
196: *
197: * @since 3.1
198: */
199: protected String getProblemsTitle() {
200: return IDEWorkbenchMessages.WorkspaceAction_problemsTitle;
201: }
202:
203: /**
204: * Returns the shell for this action. This shell is used for the modal
205: * progress and error dialogs.
206: *
207: * @return the shell
208: */
209: Shell getShell() {
210: return shell;
211: }
212:
213: /**
214: * Performs this action's operation on each of the selected resources,
215: * reporting progress to, and fielding cancel requests from, the given
216: * progress monitor.
217: * <p>
218: * Note that this method is invoked in a non-UI thread.
219: * </p>
220: * <p>
221: * Subclasses must implement this method.
222: * <p>
223: * @deprecated Since 3.3, subclasses should instead implement the method
224: * {@link #createOperation(IStatus[])} and provide an empty implementation
225: * for this method.
226: * </p>
227: *
228: * @param resource
229: * one of the selected resources
230: * @param monitor
231: * a progress monitor
232: * @exception CoreException
233: * if the operation fails
234: *
235: * @since 3.1
236: */
237: protected abstract void invokeOperation(IResource resource,
238: IProgressMonitor monitor) throws CoreException;
239:
240: /**
241: * Returns whether the given resource is a descendent of any of the
242: * resources in the given list.
243: *
244: * @param resources
245: * the list of resources (element type: <code>IResource</code>)
246: * @param child
247: * the resource to check
248: * @return <code>true</code> if <code>child</code> is a descendent of
249: * any of the elements of <code>resources</code>
250: */
251: boolean isDescendent(List resources, IResource child) {
252: IResource parent = child.getParent();
253: return parent != null
254: && (resources.contains(parent) || isDescendent(
255: resources, parent));
256: }
257:
258: /**
259: * Performs pruning on the given list of resources, as described in
260: * <code>shouldPerformResourcePruning</code>.
261: *
262: * @param resourceCollection
263: * the list of resources (element type: <code>IResource</code>)
264: * @return the list of resources (element type: <code>IResource</code>)
265: * after pruning.
266: * @see #shouldPerformResourcePruning
267: */
268: List pruneResources(List resourceCollection) {
269: List prunedList = new ArrayList(resourceCollection);
270: Iterator elementsEnum = prunedList.iterator();
271: while (elementsEnum.hasNext()) {
272: IResource currentResource = (IResource) elementsEnum.next();
273: if (isDescendent(prunedList, currentResource)) {
274: elementsEnum.remove(); // Removes currentResource
275: }
276: }
277: return prunedList;
278: }
279:
280: /**
281: * Records the core exception to be displayed to the user once the action is
282: * finished.
283: *
284: * @param error
285: * a <code>CoreException</code>
286: */
287: MultiStatus recordError(MultiStatus errors, CoreException error) {
288: if (errors == null) {
289: errors = new MultiStatus(IDEWorkbenchPlugin.IDE_WORKBENCH,
290: IStatus.ERROR, getProblemsMessage(), null);
291: }
292: errors.merge(error.getStatus());
293: return errors;
294: }
295:
296: /**
297: * The <code>CoreWrapperAction</code> implementation of this
298: * <code>IAction</code> method uses a <code>ProgressMonitorDialog</code>
299: * to run the operation. The operation calls <code>execute</code> (which,
300: * in turn, calls <code>invokeOperation</code>). Afterwards, any
301: * <code>CoreException</code>s encountered while running the operation
302: * are reported to the user via a problems dialog.
303: * <p>
304: * Subclasses may extend this method.
305: * </p>
306: */
307: public void run() {
308: IStatus[] errorStatus = new IStatus[1];
309: try {
310: new ProgressMonitorJobsDialog(shell).run(true, true,
311: createOperation(errorStatus));
312: } catch (InterruptedException e) {
313: return;
314: } catch (InvocationTargetException e) {
315: // we catch ExecutionException in the created operation, but unexpected runtime
316: // exceptions or errors may still occur
317: String msg = NLS.bind(
318: IDEWorkbenchMessages.WorkspaceAction_logTitle,
319: getClass().getName(), e.getTargetException());
320: IDEWorkbenchPlugin.log(msg, StatusUtil.newStatus(
321: IStatus.ERROR, msg, e.getTargetException()));
322: displayError(e.getTargetException().getMessage());
323: }
324: // If errors occurred, open an Error dialog & build a multi status error
325: // for it
326: if (errorStatus[0] != null && !errorStatus[0].isOK()) {
327: ErrorDialog.openError(shell, getProblemsTitle(), null, // no
328: // special
329: // message
330: errorStatus[0]);
331: }
332: }
333:
334: /**
335: * Returns whether this action should attempt to optimize the resources
336: * being operated on. This kind of pruning makes sense when the operation
337: * has depth infinity semantics (when the operation is applied explicitly to
338: * a resource then it is also applied implicitly to all the resource's
339: * descendents).
340: * <p>
341: * The <code>WorkspaceAction</code> implementation of this method returns
342: * <code>true</code>. Subclasses should reimplement to return
343: * <code>false</code> if pruning is not required.
344: * </p>
345: *
346: * @return <code>true</code> if pruning should be performed, and
347: * <code>false</code> if pruning is not desired
348: *
349: * @since 3.1
350: */
351: protected boolean shouldPerformResourcePruning() {
352: return true;
353: }
354:
355: /**
356: * The <code>WorkspaceAction</code> implementation of this
357: * <code>SelectionListenerAction</code> method ensures that this action is
358: * disabled if any of the selected resources are inaccessible. Subclasses
359: * may extend to react to selection changes; however, if the super method
360: * returns <code>false</code>, the overriding method should also return
361: * <code>false</code>.
362: */
363: protected boolean updateSelection(IStructuredSelection selection) {
364: if (!super .updateSelection(selection) || selection.isEmpty()) {
365: return false;
366: }
367: for (Iterator i = getSelectedResources().iterator(); i
368: .hasNext();) {
369: IResource r = (IResource) i.next();
370: if (!r.isAccessible()) {
371: return false;
372: }
373: }
374: return true;
375: }
376:
377: /**
378: * Returns the elements that the action is to be performed on. By default
379: * return the selected resources.
380: * <p>
381: * Subclasses may override this method.
382: *
383: * @return list of resource elements (element type: <code>IResource</code>)
384: */
385: protected List getActionResources() {
386: return getSelectedResources();
387: }
388:
389: /**
390: * Run the action in the background rather than with the progress dialog.
391: *
392: * @param rule
393: * The rule to apply to the background job or <code>null</code>
394: * if there isn't one.
395: */
396: public void runInBackground(ISchedulingRule rule) {
397: runInBackground(rule, (Object[]) null);
398: }
399:
400: /**
401: * Run the action in the background rather than with the progress dialog.
402: *
403: * @param rule
404: * The rule to apply to the background job or <code>null</code>
405: * if there isn't one.
406: * @param jobFamily
407: * a single family that the job should belong to or
408: * <code>null</code> if none.
409: *
410: * @since 3.1
411: */
412: public void runInBackground(ISchedulingRule rule, Object jobFamily) {
413: if (jobFamily == null) {
414: runInBackground(rule, (Object[]) null);
415: } else {
416: runInBackground(rule, new Object[] { jobFamily });
417: }
418: }
419:
420: /**
421: * Run the action in the background rather than with the progress dialog.
422: *
423: * @param rule
424: * The rule to apply to the background job or <code>null</code>
425: * if there isn't one.
426: * @param jobFamilies
427: * the families the job should belong to or <code>null</code>
428: * if none.
429: *
430: * @since 3.1
431: */
432: public void runInBackground(ISchedulingRule rule,
433: final Object[] jobFamilies) {
434: // obtain a copy of the selected resources before the job is forked
435: final List resources = new ArrayList(getActionResources());
436: Job job = new WorkspaceJob(removeMnemonics(getText())) {
437:
438: /*
439: * (non-Javadoc)
440: *
441: * @see org.eclipse.core.runtime.jobs.Job#belongsTo(java.lang.Object)
442: */
443: public boolean belongsTo(Object family) {
444: if (jobFamilies == null || family == null) {
445: return false;
446: }
447: for (int i = 0; i < jobFamilies.length; i++) {
448: if (family.equals(jobFamilies[i])) {
449: return true;
450: }
451: }
452: return false;
453: }
454:
455: /*
456: * (non-Javadoc)
457: *
458: * @see org.eclipse.core.resources.WorkspaceJob#runInWorkspace(org.eclipse.core.runtime.IProgressMonitor)
459: */
460: public IStatus runInWorkspace(IProgressMonitor monitor) {
461: return WorkspaceAction.this .execute(resources, monitor);
462: }
463: };
464: if (rule != null) {
465: job.setRule(rule);
466: }
467: job.setUser(true);
468: job.schedule();
469: }
470:
471: /**
472: * Returns the operation to perform when this action runs. The returned
473: * operation must be an {@link IRunnableWithProgress} that will perform the
474: * action's work. The default implementation returns an operation that will
475: * iterate over the selected resources and call
476: * {@link #invokeOperation(IResource, IProgressMonitor)} for each resource.
477: * Subclasses must either implement
478: * {@link #invokeOperation(IResource, IProgressMonitor)} or override this
479: * method to provide a different operation. Subclasses typically override
480: * this method when an undoable operation is to be provided.
481: *
482: * @param errorStatus
483: * an array of error status objects to which the result of
484: * running the operation should be added.
485: *
486: * @return the operation to perform when this action runs.
487: * @since 3.3
488: */
489: protected IRunnableWithProgress createOperation(
490: final IStatus[] errorStatus) {
491: return new WorkspaceModifyOperation() {
492: public void execute(IProgressMonitor monitor) {
493: errorStatus[0] = WorkspaceAction.this.execute(
494: getActionResources(), monitor);
495: }
496: };
497: }
498:
499: }
|