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.HashMap;
013: import java.util.Iterator;
014: import java.util.Map;
015:
016: import org.eclipse.core.resources.IMarker;
017: import org.eclipse.core.resources.IResource;
018: import org.eclipse.core.runtime.CoreException;
019: import org.eclipse.core.runtime.IProgressMonitor;
020: import org.eclipse.core.runtime.IStatus;
021: import org.eclipse.core.runtime.Status;
022: import org.eclipse.core.runtime.jobs.ISchedulingRule;
023: import org.eclipse.core.runtime.jobs.MultiRule;
024: import org.eclipse.ui.internal.ide.undo.MarkerDescription;
025: import org.eclipse.ui.internal.ide.undo.UndoMessages;
026:
027: /**
028: * An AbstractMarkersOperation represents an undoable operation that affects
029: * markers on a resource. It provides implementations for marker creation,
030: * deletion, and updating. Clients may call the public API from a background
031: * thread.
032: *
033: * This class is not intended to be subclassed by clients.
034: *
035: * @since 3.3
036: *
037: */
038: abstract class AbstractMarkersOperation extends
039: AbstractWorkspaceOperation {
040:
041: MarkerDescription[] markerDescriptions;
042:
043: IMarker[] markers;
044:
045: Map[] attributes;
046:
047: /**
048: * Create an AbstractMarkersOperation by specifying a combination of markers
049: * and attributes or marker descriptions.
050: *
051: * @param markers
052: * the markers used in the operation or <code>null</code> if no
053: * markers yet exist
054: * @param markerDescriptions
055: * the marker descriptions that should be used to create markers,
056: * or <code>null</code> if the markers already exist
057: * @param attributes
058: * The map of attributes that should be assigned to any existing
059: * markers when the markers are updated. Ignored if the markers
060: * parameter is <code>null</code>.
061: * @param name
062: * the name used to describe the operation
063: */
064: AbstractMarkersOperation(IMarker[] markers,
065: MarkerDescription[] markerDescriptions, Map attributes,
066: String name) {
067: super (name);
068: this .markers = markers;
069: this .attributes = null;
070: // If there is more than one marker, create an array with a copy
071: // of the attributes map for each marker. Keeping a unique map
072: // per marker allows us to support the scenario where attributes
073: // are merged when updated. In this case, each marker's attributes
074: // may differ since their original attributes may have differed.
075: if (attributes != null && markers != null) {
076: if (markers.length > 1) {
077: this .attributes = new Map[markers.length];
078: for (int i = 0; i < markers.length; i++) {
079: Map copiedAttributes = new HashMap();
080: copiedAttributes.putAll(attributes);
081: this .attributes[i] = copiedAttributes;
082: }
083: } else {
084: this .attributes = new Map[] { attributes };
085: }
086: }
087: setMarkerDescriptions(markerDescriptions);
088: }
089:
090: /**
091: * Delete any currently known markers and save their information in marker
092: * descriptions so that they can be restored.
093: *
094: * @param work
095: * the number of work ticks to be used by the delete
096: * @param monitor
097: * the progress monitor to use for the delete
098: * @throws CoreException
099: * propagates any CoreExceptions thrown from the resources API
100: *
101: */
102: protected void deleteMarkers(int work, IProgressMonitor monitor)
103: throws CoreException {
104: if (markers == null || markers.length == 0) {
105: monitor.worked(work);
106: return;
107: }
108: int markerWork = work / markers.length;
109: markerDescriptions = new MarkerDescription[markers.length];
110: for (int i = 0; i < markers.length; i++) {
111: markerDescriptions[i] = new MarkerDescription(markers[i]);
112: markers[i].delete();
113: monitor.worked(markerWork);
114: }
115: markers = new IMarker[0];
116: }
117:
118: /**
119: * Create markers from any currently known marker descriptions.
120: *
121: * @param work
122: * the number of work ticks to be used by the create
123: * @param monitor
124: * the progress monitor to use for the create
125: * @throws CoreException
126: * propagates any CoreExceptions thrown from the resources API
127: */
128: protected void createMarkers(int work, IProgressMonitor monitor)
129: throws CoreException {
130: if (markerDescriptions == null
131: || markerDescriptions.length == 0) {
132: monitor.worked(work);
133: return;
134: }
135: int markerWork = work / markerDescriptions.length;
136: markers = new IMarker[markerDescriptions.length];
137:
138: // Recreate the markers from the descriptions
139: for (int i = 0; i < markerDescriptions.length; i++) {
140: markers[i] = markerDescriptions[i].createMarker();
141: monitor.worked(markerWork);
142: }
143: }
144:
145: /**
146: * Update the currently known markers with the corresponding array of marker
147: * descriptions.
148: *
149: * @param work
150: * the number of work ticks to be used by the update
151: * @param monitor
152: * the progress monitor to use for the update
153: * @param mergeAttributes
154: * a boolean specifying whether the attributes are merged or
155: * considered to be a replacement of the previous attributes.
156: * @throws CoreException
157: * propagates any CoreExceptions thrown from the resources API
158: *
159: */
160: protected void updateMarkers(int work, IProgressMonitor monitor,
161: boolean mergeAttributes) throws CoreException {
162: if (attributes == null || markers == null
163: || attributes.length != markers.length
164: || markers.length == 0) {
165: monitor.worked(work);
166: return;
167: }
168: int markerWork = work / markers.length;
169: for (int i = 0; i < markers.length; i++) {
170: if (mergeAttributes) {
171: Map oldAttributes = markers[i].getAttributes();
172: int increment = markerWork / attributes[i].size();
173: Map replacedAttributes = new HashMap();
174:
175: for (Iterator iter = attributes[i].keySet().iterator(); iter
176: .hasNext();) {
177: String key = (String) iter.next();
178: Object val = attributes[i].get(key);
179: markers[i].setAttribute(key, val);
180: replacedAttributes.put(key, oldAttributes.get(key));
181: monitor.worked(increment);
182: }
183: attributes[i] = replacedAttributes;
184: } else {
185: // replace all of the attributes
186: Map oldAttributes = markers[i].getAttributes();
187: markers[i].setAttributes(attributes[i]);
188: attributes[i] = oldAttributes;
189: }
190: }
191: }
192:
193: /**
194: * Set the marker descriptions that describe markers that can be created.
195: *
196: * @param descriptions
197: * the descriptions of markers that can be created.
198: */
199: protected void setMarkerDescriptions(
200: MarkerDescription[] descriptions) {
201: markerDescriptions = descriptions;
202: addUndoContexts();
203: updateTargetResources();
204: }
205:
206: /*
207: * Update the target resources by traversing the currently known markers or
208: * marker descriptions and getting their resources.
209: */
210:
211: private void updateTargetResources() {
212: IResource[] resources = null;
213: if (markers == null) {
214: if (markerDescriptions != null) {
215: resources = new IResource[markerDescriptions.length];
216: for (int i = 0; i < markerDescriptions.length; i++) {
217: resources[i] = markerDescriptions[i].getResource();
218: }
219: }
220: } else {
221: resources = new IResource[markers.length];
222: for (int i = 0; i < markers.length; i++) {
223: resources[i] = markers[i].getResource();
224: }
225: }
226: setTargetResources(resources);
227: }
228:
229: /*
230: * Add undo contexts according to marker types. Any unknown marker types
231: * will cause the workspace undo context to be added.
232: *
233: * This is an optimization that allows us to add specific undo contexts for
234: * tasks and bookmarks, without also adding the workspace undo context. Note
235: * that clients with different marker types may still assign their own
236: * specific undo context using AbstractOperation.addContext(IUndoContext) in
237: * addition to the workspace context assigned by this method.
238: */
239:
240: private void addUndoContexts() {
241: String[] types = null;
242: if (markers == null) {
243: if (markerDescriptions != null) {
244: types = new String[markerDescriptions.length];
245: for (int i = 0; i < markerDescriptions.length; i++) {
246: types[i] = markerDescriptions[i].getType();
247: }
248: }
249: } else {
250: types = new String[markers.length];
251: for (int i = 0; i < markers.length; i++) {
252: try {
253: types[i] = markers[i].getType();
254: } catch (CoreException e) {
255: }
256:
257: }
258: }
259: if (types != null) {
260: for (int i = 0; i < types.length; i++) {
261: // Marker type could be null if marker did not exist.
262: // This shouldn't happen, but can.
263: // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=158129
264: if (types[i] != null) {
265: if (types[i].equals(IMarker.BOOKMARK)) {
266: addContext(WorkspaceUndoUtil
267: .getBookmarksUndoContext());
268: } else if (types[i].equals(IMarker.TASK)) {
269: addContext(WorkspaceUndoUtil
270: .getTasksUndoContext());
271: } else if (types[i] != null) {
272: // type is not known, use the workspace undo context
273: addContext(WorkspaceUndoUtil
274: .getWorkspaceUndoContext());
275: }
276: }
277: }
278: }
279: }
280:
281: /**
282: * Return the array of markers that has been updated or created.
283: *
284: * @return the array of markers that have been updated or created, or
285: * <code>null</code> if no markers have been created or updated.
286: */
287: public IMarker[] getMarkers() {
288: return markers;
289: }
290:
291: /**
292: * Return whether the markers known by this operation currently exist.
293: *
294: * @return <code>true</code> if there are existing markers and
295: * <code>false</code> if there are no known markers or any one of
296: * them does not exist
297: */
298: protected boolean markersExist() {
299: if (markers == null || markers.length == 0) {
300: return false;
301: }
302: for (int i = 0; i < markers.length; i++) {
303: if (!markers[i].exists()) {
304: return false;
305: }
306: }
307: return true;
308:
309: }
310:
311: /**
312: * Return a status indicating the projected outcome of undoing the marker
313: * operation. The receiver is not responsible for remembering the result of
314: * this computation.
315: *
316: * @return the status indicating whether the operation can be undone
317: */
318: protected abstract IStatus getBasicUndoStatus();
319:
320: /**
321: * Return a status indicating the projected outcome of redoing the marker
322: * operation. The receiver is not responsible for remembering the result of
323: * this computation.
324: *
325: * @return the status indicating whether the operation can be undone
326: */
327: protected abstract IStatus getBasicRedoStatus();
328:
329: /*
330: * (non-Javadoc)
331: *
332: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeExecutionStatus(org.eclipse.core.runtime.IProgressMonitor)
333: */
334: public IStatus computeExecutionStatus(IProgressMonitor monitor) {
335: IStatus status = getBasicRedoStatus();
336: if (status.isOK()) {
337: return super .computeExecutionStatus(monitor);
338: }
339: if (status.getSeverity() == IStatus.ERROR) {
340: markInvalid();
341: }
342: return status;
343: }
344:
345: /*
346: * (non-Javadoc)
347: *
348: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeUndoableStatus(org.eclipse.core.runtime.IProgressMonitor)
349: */
350: public IStatus computeUndoableStatus(IProgressMonitor monitor) {
351: IStatus status = getBasicUndoStatus();
352: if (status.isOK()) {
353: return super .computeUndoableStatus(monitor);
354: }
355: if (status.getSeverity() == IStatus.ERROR) {
356: markInvalid();
357: }
358: return status;
359: }
360:
361: /*
362: * (non-Javadoc)
363: *
364: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#computeRedoableStatus(org.eclipse.core.runtime.IProgressMonitor)
365: */
366: public IStatus computeRedoableStatus(IProgressMonitor monitor) {
367: IStatus status = getBasicRedoStatus();
368: if (status.isOK()) {
369: return super .computeRedoableStatus(monitor);
370: }
371: if (status.getSeverity() == IStatus.ERROR) {
372: markInvalid();
373: }
374: return status;
375: }
376:
377: /**
378: * Compute the status for deleting any known markers. A status severity of
379: * <code>OK</code> indicates that the delete is likely to be successful. A
380: * status severity of <code>ERROR</code> indicates that the operation is
381: * no longer valid. Other status severities are open to interpretation by
382: * the caller.
383: *
384: * @return the status indicating the projected outcome of deleting the
385: * markers.
386: *
387: */
388: protected IStatus getMarkerDeletionStatus() {
389: if (markersExist()) {
390: return Status.OK_STATUS;
391: }
392: return getErrorStatus(UndoMessages.MarkerOperation_MarkerDoesNotExist);
393: }
394:
395: /**
396: * Compute the status for creating any known markers. A status severity of
397: * <code>OK</code> indicates that the create is likely to be successful. A
398: * status severity of <code>ERROR</code> indicates that the operation is
399: * no longer valid. Other status severities are open to interpretation by
400: * the caller.
401: *
402: * @return the status indicating the projected outcome of creating the
403: * markers.
404: *
405: */
406: protected IStatus getMarkerCreationStatus() {
407: if (!resourcesExist()) {
408: return getErrorStatus(UndoMessages.MarkerOperation_ResourceDoesNotExist);
409: } else if (markerDescriptions == null) {
410: return getErrorStatus(UndoMessages.MarkerOperation_NotEnoughInfo);
411: }
412: return Status.OK_STATUS;
413: }
414:
415: /**
416: * Compute the status for updating any known markers. A status severity of
417: * <code>OK</code> indicates that the update is likely to be successful. A
418: * status severity of <code>ERROR</code> indicates that the operation is
419: * no longer valid. Other status severities are open to interpretation by
420: * the caller.
421: *
422: * @return the status indicating the projected outcome of updating the
423: * markers.
424: *
425: */
426: protected IStatus getMarkerUpdateStatus() {
427: if (!markersExist()) {
428: return getErrorStatus(UndoMessages.MarkerOperation_MarkerDoesNotExist);
429: } else if (attributes == null) {
430: return getErrorStatus(UndoMessages.MarkerOperation_NotEnoughInfo);
431: }
432: return Status.OK_STATUS;
433: }
434:
435: /*
436: * (non-Javadoc)
437: *
438: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#getExecuteSchedulingRule()
439: */
440: protected ISchedulingRule getExecuteSchedulingRule() {
441: ISchedulingRule[] ruleArray = new ISchedulingRule[resources.length];
442: for (int i = 0; i < resources.length; i++) {
443: ruleArray[i] = getWorkspaceRuleFactory().markerRule(
444: resources[i]);
445: }
446: return MultiRule.combine(ruleArray);
447: }
448:
449: /*
450: * (non-Javadoc)
451: *
452: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#getUndoSchedulingRule()
453: */
454: protected ISchedulingRule getUndoSchedulingRule() {
455: return getExecuteSchedulingRule();
456: }
457:
458: /*
459: * (non-Javadoc)
460: *
461: * @see org.eclipse.ui.ide.undo.AbstractWorkspaceOperation#appendDescriptiveText(java.lang.StringBuffer)
462: */
463: protected void appendDescriptiveText(StringBuffer text) {
464: super .appendDescriptiveText(text);
465: text.append(" markers: "); //$NON-NLS-1$
466: text.append(markers);
467: text.append('\'');
468: text.append(" markerDescriptions: "); //$NON-NLS-1$
469: text.append(markerDescriptions);
470: text.append('\'');
471: text.append(" attributes: "); //$NON-NLS-1$
472: text.append(attributes);
473: text.append('\'');
474: }
475: }
|