001: /*******************************************************************************
002: * Copyright (c) 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.internal.ide.undo;
011:
012: import org.eclipse.core.commands.ExecutionException;
013: import org.eclipse.core.commands.operations.IAdvancedUndoableOperation;
014: import org.eclipse.core.commands.operations.IAdvancedUndoableOperation2;
015: import org.eclipse.core.commands.operations.IOperationHistory;
016: import org.eclipse.core.commands.operations.IOperationHistoryListener;
017: import org.eclipse.core.commands.operations.IUndoableOperation;
018: import org.eclipse.core.commands.operations.OperationHistoryEvent;
019: import org.eclipse.core.resources.IResourceChangeEvent;
020: import org.eclipse.core.resources.IResourceChangeListener;
021: import org.eclipse.core.resources.ResourcesPlugin;
022: import org.eclipse.core.runtime.IStatus;
023: import org.eclipse.core.runtime.Status;
024: import org.eclipse.ui.PlatformUI;
025: import org.eclipse.ui.ide.undo.WorkspaceUndoUtil;
026: import org.eclipse.ui.internal.ide.Policy;
027:
028: /**
029: * WorkspaceUndoMonitor monitors the workspace for resource changes and
030: * periodically checks the undo history to make sure it is valid.
031: *
032: * This class is not intended to be instantiated or used by clients.
033: *
034: * @since 3.3
035: *
036: */
037: public class WorkspaceUndoMonitor {
038:
039: /**
040: * Singleton instance.
041: */
042: private static WorkspaceUndoMonitor instance;
043:
044: /**
045: * Number of workspace changes that will cause validation of undo history
046: */
047: private static int CHANGE_THRESHHOLD = 10;
048:
049: /**
050: * Prefix to use on debug info
051: */
052: private static String DEBUG_PREFIX = "Workspace Undo Monitor: "; //$NON-NLS-1$
053:
054: /**
055: * Get the singleton instance of this class.
056: *
057: * @return the singleton instance of this class.
058: */
059: public static WorkspaceUndoMonitor getInstance() {
060: if (instance == null) {
061: instance = new WorkspaceUndoMonitor();
062: }
063: return instance;
064: }
065:
066: /**
067: * Number of workspace changes that have occurred since the last undoable
068: * operation was executed, undone, or redone.
069: */
070: private int numChanges = 0;
071:
072: /**
073: * The IUndoableOperation in progress, or <code>null</code> if there is
074: * none in progress.
075: */
076: private IUndoableOperation operationInProgress = null;
077:
078: /**
079: * Resource listener used to determine how often to validate the workspace
080: * undo history.
081: */
082: private IResourceChangeListener resourceListener;
083:
084: /**
085: * Operation history listener used to determine whether there is an undoable
086: * operation in progress.
087: */
088: private IOperationHistoryListener historyListener;
089:
090: /**
091: * Construct an instance. Should only be called by {@link #getInstance()}
092: */
093: private WorkspaceUndoMonitor() {
094: if (Policy.DEBUG_UNDOMONITOR) {
095: System.out.println(DEBUG_PREFIX + "Installing listeners"); //$NON-NLS-1$
096: }
097: resourceListener = getResourceChangeListener();
098: ResourcesPlugin.getWorkspace().addResourceChangeListener(
099: resourceListener);
100:
101: historyListener = getOperationHistoryListener();
102: getOperationHistory().addOperationHistoryListener(
103: historyListener);
104:
105: }
106:
107: /**
108: * Get a change listener for listening to resource changes.
109: *
110: * @return the resource change listeners
111: */
112: private IResourceChangeListener getResourceChangeListener() {
113: return new IResourceChangeListener() {
114: /*
115: * (non-Javadoc)
116: *
117: * @see org.eclipse.core.resources.IResourceChangeListener#resourceChanged(org.eclipse.core.resources.IResourceChangeEvent)
118: */
119: public void resourceChanged(IResourceChangeEvent event) {
120: // If there is an operation in progress, this event is to be
121: // ignored.
122: if (operationInProgress != null) {
123: return;
124: }
125: if (event.getType() == IResourceChangeEvent.POST_CHANGE
126: || event.getType() == IResourceChangeEvent.POST_BUILD) {
127: // For now, we consider any change a change worth tracking.
128: // We can be more specific later if warranted.
129: incrementChangeCount();
130: if (numChanges >= CHANGE_THRESHHOLD) {
131: checkOperationHistory();
132: }
133: }
134: }
135: };
136: }
137:
138: /**
139: * Get a change listener for listening to operation history changes.
140: *
141: * @return the resource change listeners
142: */
143: private IOperationHistoryListener getOperationHistoryListener() {
144: return new IOperationHistoryListener() {
145:
146: /*
147: * (non-Javadoc)
148: *
149: * @see org.eclipse.core.commands.operations.IOperationHistoryListener#historyNotification(org.eclipse.core.commands.operations.OperationHistoryEvent)
150: */
151: public void historyNotification(OperationHistoryEvent event) {
152: // We only care about events that have the workspace undo
153: // context.
154: if (!event.getOperation().hasContext(
155: WorkspaceUndoUtil.getWorkspaceUndoContext())) {
156: return;
157: }
158: switch (event.getEventType()) {
159: case OperationHistoryEvent.ABOUT_TO_EXECUTE:
160: case OperationHistoryEvent.ABOUT_TO_UNDO:
161: case OperationHistoryEvent.ABOUT_TO_REDO:
162: operationInProgress = event.getOperation();
163: break;
164: case OperationHistoryEvent.DONE:
165: case OperationHistoryEvent.UNDONE:
166: case OperationHistoryEvent.REDONE:
167: resetChangeCount();
168: operationInProgress = null;
169: break;
170: case OperationHistoryEvent.OPERATION_NOT_OK:
171: operationInProgress = null;
172: break;
173: }
174: }
175:
176: };
177: }
178:
179: /**
180: * Shutdown the workspace undo monitor. Unhooks the listeners.
181: */
182: public void shutdown() {
183: if (Policy.DEBUG_UNDOMONITOR) {
184: System.out.println(DEBUG_PREFIX + "Shutting Down"); //$NON-NLS-1$
185: }
186:
187: if (resourceListener != null) {
188: ResourcesPlugin.getWorkspace()
189: .removeResourceChangeListener(resourceListener);
190: }
191: if (historyListener != null) {
192: getOperationHistory().removeOperationHistoryListener(
193: historyListener);
194: }
195: }
196:
197: /**
198: * Get the operation history.
199: */
200: private IOperationHistory getOperationHistory() {
201: return PlatformUI.getWorkbench().getOperationSupport()
202: .getOperationHistory();
203: }
204:
205: /**
206: * Check the pending undoable operation to see if it is still valid.
207: */
208: private void checkOperationHistory() {
209: if (Policy.DEBUG_UNDOMONITOR) {
210: System.out.println(DEBUG_PREFIX
211: + "Checking Operation History..."); //$NON-NLS-1$
212: }
213: IUndoableOperation currentOp = getOperationHistory()
214: .getUndoOperation(
215: WorkspaceUndoUtil.getWorkspaceUndoContext());
216: // If there is no pending op, nothing to do.
217: if (currentOp == null) {
218: resetChangeCount();
219: return;
220: }
221: // First try the simple check
222: if (!currentOp.canUndo()) {
223: flushWorkspaceHistory(currentOp);
224: return;
225: }
226: // Now try a more advanced check. If the undoable status is definitely
227: // an error, flush the history. Anything less than an error status
228: // should be left alone so that the user can be prompted as to what
229: // should be done when an undo is actually attempted.
230: if (currentOp instanceof IAdvancedUndoableOperation
231: && currentOp instanceof IAdvancedUndoableOperation2) {
232: ((IAdvancedUndoableOperation2) currentOp)
233: .setQuietCompute(true);
234: IStatus status;
235: try {
236: status = ((IAdvancedUndoableOperation) currentOp)
237: .computeUndoableStatus(null);
238: } catch (ExecutionException e) {
239: // Things are not really OK, but we do not want to
240: // interrupt the user with notification of this problem.
241: // For now, we pretend that everything is OK, knowing that
242: // computation will occur again just before the user attempts to
243: // undo this operation.
244: status = Status.OK_STATUS;
245: }
246: ((IAdvancedUndoableOperation2) currentOp)
247: .setQuietCompute(false);
248: if (status.getSeverity() == IStatus.ERROR) {
249: flushWorkspaceHistory(currentOp);
250: }
251: }
252: resetChangeCount();
253: }
254:
255: /**
256: * Flush the undo and redo history for the workspace undo context.
257: */
258: private void flushWorkspaceHistory(IUndoableOperation op) {
259: if (Policy.DEBUG_UNDOMONITOR) {
260: System.out.println(DEBUG_PREFIX
261: + "Flushing undo history due to " + op); //$NON-NLS-1$
262: }
263: getOperationHistory().dispose(
264: WorkspaceUndoUtil.getWorkspaceUndoContext(), true,
265: true, false);
266: }
267:
268: /**
269: * Reset the workspace change count
270: */
271: private void resetChangeCount() {
272: numChanges = 0;
273: if (Policy.DEBUG_UNDOMONITOR) {
274: System.out.println(DEBUG_PREFIX
275: + "Resetting change count to 0"); //$NON-NLS-1$
276: }
277: }
278:
279: /**
280: * Increment the workspace change count
281: */
282: private void incrementChangeCount() {
283: numChanges++;
284: if (Policy.DEBUG_UNDOMONITOR) {
285: System.out
286: .println(DEBUG_PREFIX
287: + "Incrementing workspace change count. Count = " + numChanges); //$NON-NLS-1$
288: }
289: }
290: }
|