comparison org.eclipse.core.commands/src/org/eclipse/core/commands/operations/DefaultOperationHistory.d @ 12:bc29606a740c

Added dwt-addons in original directory structure of eclipse.org
author Frank Benoit <benoit@tionex.de>
date Sat, 14 Mar 2009 18:23:29 +0100
parents
children
comparison
equal deleted inserted replaced
11:43904fec5dca 12:bc29606a740c
1 /*******************************************************************************
2 * Copyright (c) 2005, 2007 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module org.eclipse.core.commands.operations.DefaultOperationHistory;
14
15 import org.eclipse.core.commands.ExecutionException;
16 import org.eclipse.core.commands.util.Tracing;
17 import org.eclipse.core.runtime.Assert;
18 import org.eclipse.core.runtime.IAdaptable;
19 import org.eclipse.core.runtime.IProgressMonitor;
20 import org.eclipse.core.runtime.ISafeRunnable;
21 import org.eclipse.core.runtime.IStatus;
22 import org.eclipse.core.runtime.ListenerList;
23 import org.eclipse.core.runtime.OperationCanceledException;
24 import org.eclipse.core.runtime.SafeRunner;
25 import org.eclipse.core.runtime.Status;
26
27 import org.eclipse.core.commands.operations.IOperationHistory;
28 import org.eclipse.core.commands.operations.IUndoContext;
29 import org.eclipse.core.commands.operations.IUndoableOperation;
30 import org.eclipse.core.commands.operations.ICompositeOperation;
31 import org.eclipse.core.commands.operations.IOperationApprover;
32 import org.eclipse.core.commands.operations.IOperationApprover2;
33 import org.eclipse.core.commands.operations.IOperationHistoryListener;
34 import org.eclipse.core.commands.operations.OperationHistoryEvent;
35 import org.eclipse.core.commands.operations.IAdvancedUndoableOperation;
36
37 import java.lang.all;
38 import java.util.Map;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Iterator;
44
45 /**
46 * <p>
47 * A base implementation of IOperationHistory that implements a linear undo and
48 * redo model . The most recently added operation is available for undo, and the
49 * most recently undone operation is available for redo.
50 * </p>
51 * <p>
52 * If the operation eligible for undo is not in a state where it can be undone,
53 * then no undo is available. No other operations are considered. Likewise, if
54 * the operation available for redo cannot be redone, then no redo is available.
55 * </p>
56 * <p>
57 * Implementations for the direct undo and redo of a specified operation are
58 * available. If a strict linear undo is to be enforced, than an
59 * IOperationApprover should be installed that prevents undo and redo of any
60 * operation that is not the most recently undone or redone operation in all of
61 * its undo contexts.
62 * </p>
63 * <p>
64 * The data structures used by the DefaultOperationHistory are synchronized, and
65 * entry points that modify the undo and redo history concurrently are also
66 * synchronized. This means that the DefaultOperationHistory is relatively
67 * "thread-friendly" in its implementation. Outbound notifications or operation
68 * approval requests will occur on the thread that initiated the request.
69 * Clients may use DefaultOperationHistory API from any thread; however,
70 * listeners or operation approvers that receive notifications from the
71 * DefaultOperationHistory must be prepared to receive these notifications from
72 * a background thread. Any UI access occurring inside these notifications must
73 * be properly synchronized using the techniques specified by the client's
74 * widget library.
75 * </p>
76 *
77 * <p>
78 * This implementation is not intended to be subclassed.
79 * </p>
80 *
81 * @see org.eclipse.core.commands.operations.IOperationHistory
82 * @see org.eclipse.core.commands.operations.IOperationApprover
83 *
84 * @since 3.1
85 */
86 public final class DefaultOperationHistory : IOperationHistory {
87 /**
88 * This flag can be set to <code>true</code> if the history should print
89 * information to <code>System.out</code> whenever notifications about
90 * changes to the history occur. This flag should be used for debug purposes
91 * only.
92 */
93 public static bool DEBUG_OPERATION_HISTORY_NOTIFICATION = false;
94
95 /**
96 * This flag can be set to <code>true</code> if the history should print
97 * information to <code>System.out</code> whenever an unexpected condition
98 * arises. This flag should be used for debug purposes only.
99 */
100 public static bool DEBUG_OPERATION_HISTORY_UNEXPECTED = false;
101
102 /**
103 * This flag can be set to <code>true</code> if the history should print
104 * information to <code>System.out</code> whenever an undo context is
105 * disposed. This flag should be used for debug purposes only.
106 */
107 public static bool DEBUG_OPERATION_HISTORY_DISPOSE = false;
108
109 /**
110 * This flag can be set to <code>true</code> if the history should print
111 * information to <code>System.out</code> during the open/close sequence.
112 * This flag should be used for debug purposes only.
113 */
114 public static bool DEBUG_OPERATION_HISTORY_OPENOPERATION = false;
115
116 /**
117 * This flag can be set to <code>true</code> if the history should print
118 * information to <code>System.out</code> whenever an operation is not
119 * approved. This flag should be used for debug purposes only.
120 */
121 public static bool DEBUG_OPERATION_HISTORY_APPROVAL = false;
122
123 static const int DEFAULT_LIMIT = 20;
124
125 /**
126 * the list of {@link IOperationApprover}s
127 */
128 ListenerList approvers;
129
130 /**
131 * a map of undo limits per context
132 */
133 private Map limits;
134
135 /**
136 * the list of {@link IOperationHistoryListener}s
137 */
138 ListenerList listeners;
139
140 /**
141 * the list of operations available for redo, LIFO
142 */
143 private List redoList;
144
145 /**
146 * the list of operations available for undo, LIFO
147 */
148 private List undoList;
149
150 /**
151 * a lock that is used to synchronize access between the undo and redo
152 * history
153 */
154 final Object undoRedoHistoryLock;
155
156 /**
157 * An operation that is "absorbing" all other operations while it is open.
158 * When this is not null, other operations added or executed are added to
159 * this composite.
160 *
161 */
162 private ICompositeOperation openComposite = null;
163
164 /**
165 * a lock that is used to synchronize access to the open composite.
166 */
167 const Object openCompositeLock;
168
169 /**
170 * Create an instance of DefaultOperationHistory.
171 */
172 public this() {
173 openCompositeLock = new Object();
174 undoRedoHistoryLock = new Object();
175 approvers = new ListenerList(ListenerList.IDENTITY);
176 limits = Collections.synchronizedMap(new HashMap());
177 listeners = new ListenerList(ListenerList.IDENTITY);
178 redoList = Collections.synchronizedList(new ArrayList());
179 undoList = Collections.synchronizedList(new ArrayList());
180 }
181
182 /*
183 * (non-Javadoc)
184 *
185 * @see org.eclipse.core.commands.operations.IOperationHistory#add(org.eclipse.core.commands.operations.IUndoableOperation)
186 */
187 public void add(IUndoableOperation operation) {
188 Assert.isNotNull( cast(Object)operation);
189
190 /*
191 * If we are in the middle of executing an open batching operation, and
192 * this is not that operation, then we need only add the context of the
193 * new operation to the batch. The operation itself is disposed since we
194 * will never undo or redo it. We consider it to be triggered by the
195 * batching operation and assume that its undo will be triggered by the
196 * batching operation undo.
197 */
198 synchronized (openCompositeLock) {
199 if (openComposite !is null && openComposite !is operation) {
200 openComposite.add(operation);
201 return;
202 }
203 }
204
205 if (checkUndoLimit(operation)) {
206 synchronized (undoRedoHistoryLock) {
207 undoList.add(cast(Object)operation);
208 }
209 notifyAdd(operation);
210
211 // flush redo stack for related contexts
212 IUndoContext[] contexts = operation.getContexts();
213 for (int i = 0; i < contexts.length; i++) {
214 flushRedo(contexts[i]);
215 }
216 } else {
217 // Dispose the operation since we will not have a reference to it.
218 operation.dispose();
219 }
220 }
221
222 /**
223 * <p>
224 * Add the specified approver to the list of operation approvers consulted
225 * by the operation history before an undo or redo is allowed to proceed.
226 * This method has no effect if the instance being added is already in the
227 * list.
228 * </p>
229 * <p>
230 * Operation approvers must be prepared to receive these the operation
231 * approval messages from a background thread. Any UI access occurring
232 * inside the implementation must be properly synchronized using the
233 * techniques specified by the client's widget library.
234 * </p>
235 *
236 * @param approver
237 * the IOperationApprover to be added as an approver.
238 *
239 */
240
241 public void addOperationApprover(IOperationApprover approver) {
242 approvers.add(cast(Object)approver);
243 }
244
245 /**
246 * <p>
247 * Add the specified listener to the list of operation history listeners
248 * that are notified about changes in the history or operations that are
249 * executed, undone, or redone. This method has no effect if the instance
250 * being added is already in the list.
251 * </p>
252 * <p>
253 * Operation history listeners must be prepared to receive notifications
254 * from a background thread. Any UI access occurring inside the
255 * implementation must be properly synchronized using the techniques
256 * specified by the client's widget library.
257 * </p>
258 *
259 * @param listener
260 * the IOperationHistoryListener to be added as a listener.
261 *
262 * @see org.eclipse.core.commands.operations.IOperationHistoryListener
263 * @see org.eclipse.core.commands.operations.OperationHistoryEvent
264 */
265 public void addOperationHistoryListener(IOperationHistoryListener listener) {
266 listeners.add(cast(Object)listener);
267 }
268
269 /*
270 * (non-Javadoc)
271 *
272 * @see org.eclipse.core.commands.operations.IOperationHistory#canRedo(org.eclipse.core.commands.operations.IUndoContext)
273 */
274 public bool canRedo(IUndoContext context) {
275 // null context is allowed and passed through
276 IUndoableOperation operation = getRedoOperation(context);
277 return (operation !is null && operation.canRedo());
278 }
279
280 /*
281 * (non-Javadoc)
282 *
283 * @see org.eclipse.core.commands.operations.IOperationHistory#canUndo(org.eclipse.core.commands.operations.IUndoContext)
284 */
285 public bool canUndo(IUndoContext context) {
286 // null context is allowed and passed through
287 IUndoableOperation operation = getUndoOperation(context);
288 return (operation !is null && operation.canUndo());
289 }
290
291 /**
292 * Check the redo limit before adding an operation. In theory the redo limit
293 * should never be reached, because the redo items are transferred from the
294 * undo history, which has the same limit. The redo history is cleared
295 * whenever a new operation is added. We check for completeness since
296 * implementations may change over time.
297 *
298 * Return a bool indicating whether the redo should proceed.
299 */
300 private bool checkRedoLimit(IUndoableOperation operation) {
301 IUndoContext[] contexts = operation.getContexts();
302 for (int i = 0; i < contexts.length; i++) {
303 int limit = getLimit(contexts[i]);
304 if (limit > 0) {
305 forceRedoLimit(contexts[i], limit - 1);
306 } else {
307 // this context has a 0 limit
308 operation.removeContext(contexts[i]);
309 }
310 }
311 return operation.getContexts().length > 0;
312 }
313
314 /**
315 * Check the undo limit before adding an operation. Return a bool
316 * indicating whether the undo should proceed.
317 */
318 private bool checkUndoLimit(IUndoableOperation operation) {
319 IUndoContext[] contexts = operation.getContexts();
320 for (int i = 0; i < contexts.length; i++) {
321 int limit = getLimit(contexts[i]);
322 if (limit > 0) {
323 forceUndoLimit(contexts[i], limit - 1);
324 } else {
325 // this context has a 0 limit
326 operation.removeContext(contexts[i]);
327 }
328 }
329 return operation.getContexts().length > 0;
330 }
331
332 /*
333 * (non-Javadoc)
334 *
335 * @see org.eclipse.core.commands.operations.IOperationHistory#dispose(org.eclipse.core.commands.operations.IUndoContext,
336 * bool, bool, bool)
337 */
338 public void dispose(IUndoContext context, bool flushUndo_,
339 bool flushRedo_, bool flushContext_) {
340 // dispose of any limit that was set for the context if it is not to be
341 // used again.
342 if (flushContext_) {
343 if (DEBUG_OPERATION_HISTORY_DISPOSE) {
344 Tracing.printTrace("OPERATIONHISTORY", "Flushing context " //$NON-NLS-1$//$NON-NLS-2$
345 ~ (cast(Object)context).toString );
346 }
347 flushUndo(context);
348 flushRedo(context);
349 limits.remove(cast(Object)context);
350 return;
351 }
352 if (flushUndo_) {
353 flushUndo(context);
354 }
355 if (flushRedo_) {
356 flushRedo(context);
357 }
358
359 }
360
361 /**
362 * Perform the redo. All validity checks have already occurred.
363 *
364 * @param monitor
365 * @param operation
366 */
367 private IStatus doRedo(IProgressMonitor monitor, IAdaptable info,
368 IUndoableOperation operation) {
369
370 IStatus status = getRedoApproval(operation, info);
371 if (status.isOK()) {
372 notifyAboutToRedo(operation);
373 try {
374 status = operation.redo(monitor, info);
375 } catch (OperationCanceledException e) {
376 status = Status.CANCEL_STATUS;
377 } catch (ExecutionException e) {
378 notifyNotOK(operation);
379 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
380 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
381 "ExecutionException while redoing " ~ (cast(Object)operation).toString ); //$NON-NLS-1$
382 }
383 throw e;
384 } catch (Exception e) {
385 notifyNotOK(operation);
386 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
387 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
388 "Exception while redoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$
389 }
390 throw new ExecutionException(
391 "While redoing the operation, an exception occurred", e); //$NON-NLS-1$
392 }
393 }
394
395 // if successful, the operation is removed from the redo history and
396 // placed back in the undo history.
397 if (status.isOK()) {
398 bool addedToUndo = true;
399 synchronized (undoRedoHistoryLock) {
400 redoList.remove(cast(Object)operation);
401 if (checkUndoLimit(operation)) {
402 undoList.add(cast(Object)operation);
403 } else {
404 addedToUndo = false;
405 }
406 }
407 // dispose the operation since we could not add it to the
408 // stack and will no longer have a reference to it.
409 if (!addedToUndo) {
410 operation.dispose();
411 }
412
413 // notify listeners must happen after history is updated
414 notifyRedone(operation);
415 } else {
416 notifyNotOK(operation, status);
417 }
418
419 return status;
420 }
421
422 /**
423 * Perform the undo. All validity checks have already occurred.
424 *
425 * @param monitor
426 * @param operation
427 */
428 private IStatus doUndo(IProgressMonitor monitor, IAdaptable info,
429 IUndoableOperation operation) {
430 IStatus status = getUndoApproval(operation, info);
431 if (status.isOK()) {
432 notifyAboutToUndo(operation);
433 try {
434 status = operation.undo(monitor, info);
435 } catch (OperationCanceledException e) {
436 status = Status.CANCEL_STATUS;
437 } catch (ExecutionException e) {
438 notifyNotOK(operation);
439 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
440 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
441 "ExecutionException while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$
442 }
443 throw e;
444 } catch (Exception e) {
445 notifyNotOK(operation);
446 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
447 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
448 "Exception while undoing " ~ (cast(Object)operation).toString); //$NON-NLS-1$
449 }
450 throw new ExecutionException(
451 "While undoing the operation, an exception occurred", e); //$NON-NLS-1$
452 }
453 }
454 // if successful, the operation is removed from the undo history and
455 // placed in the redo history.
456 if (status.isOK()) {
457 bool addedToRedo = true;
458 synchronized (undoRedoHistoryLock) {
459 undoList.remove(cast(Object)operation);
460 if (checkRedoLimit(operation)) {
461 redoList.add(cast(Object)operation);
462 } else {
463 addedToRedo = false;
464 }
465 }
466 // dispose the operation since we could not add it to the
467 // stack and will no longer have a reference to it.
468 if (!addedToRedo) {
469 operation.dispose();
470 }
471 // notification occurs after the undo and redo histories are
472 // adjusted
473 notifyUndone(operation);
474 } else {
475 notifyNotOK(operation, status);
476 }
477 return status;
478 }
479
480 /*
481 * (non-Javadoc)
482 *
483 * @see org.eclipse.core.commands.operations.IOperationHistory#execute(org.eclipse.core.commands.operations.IUndoableOperation,
484 * org.eclipse.core.runtime.IProgressMonitor,
485 * org.eclipse.core.runtime.IAdaptable)
486 */
487 public IStatus execute(IUndoableOperation operation,
488 IProgressMonitor monitor, IAdaptable info) {
489 Assert.isNotNull(cast(Object)operation);
490
491 // error if operation is invalid
492 if (!operation.canExecute()) {
493 return IOperationHistory.OPERATION_INVALID_STATUS;
494 }
495
496 // check with the operation approvers
497 IStatus status = getExecuteApproval(operation, info);
498 if (!status.isOK()) {
499 // not approved. No notifications are sent, just return the status.
500 return status;
501 }
502
503 /*
504 * If we are in the middle of an open composite, then we will add this
505 * operation to the open operation rather than add the operation to the
506 * history. We will still execute it.
507 */
508 bool merging = false;
509 synchronized (openCompositeLock) {
510 if (openComposite !is null) {
511 // the composite shouldn't be executed explicitly while it is
512 // still
513 // open
514 if (openComposite is operation) {
515 return IOperationHistory.OPERATION_INVALID_STATUS;
516 }
517 openComposite.add(operation);
518 merging = true;
519 }
520 }
521
522 /*
523 * Execute the operation
524 */
525 if (!merging) {
526 notifyAboutToExecute(operation);
527 }
528 try {
529 status = operation.execute(monitor, info);
530 } catch (OperationCanceledException e) {
531 status = Status.CANCEL_STATUS;
532 } catch (ExecutionException e) {
533 notifyNotOK(operation);
534 throw e;
535 } catch (Exception e) {
536 notifyNotOK(operation);
537 throw new ExecutionException(
538 "While executing the operation, an exception occurred", e); //$NON-NLS-1$
539 }
540
541 // if successful, the notify listeners are notified and the operation is
542 // added to the history
543 if (!merging) {
544 if (status.isOK()) {
545 notifyDone(operation);
546 add(operation);
547 } else {
548 notifyNotOK(operation, status);
549 // dispose the operation since we did not add it to the stack
550 // and will no longer have a reference to it.
551 operation.dispose();
552 }
553 }
554 // all other severities are not interpreted. Simply return the status.
555 return status;
556 }
557
558 /*
559 * Filter the specified list to include only the specified undo context.
560 */
561 private IUndoableOperation[] filter(List list, IUndoContext context) {
562 /*
563 * This method is used whenever there is a need to filter the undo or
564 * redo history on a particular context. Currently there are no caches
565 * kept to optimize repeated requests for the same filter. If benchmarks
566 * show this to be a common pattern that causes performances problems,
567 * we could implement a filtered cache here that is nullified whenever
568 * the global history changes.
569 */
570
571 List filtered = new ArrayList();
572 Iterator iterator = list.iterator();
573 synchronized (undoRedoHistoryLock) {
574 while (iterator.hasNext()) {
575 IUndoableOperation operation = cast(IUndoableOperation) iterator
576 .next();
577 if (operation.hasContext(context)) {
578 filtered.add(cast(Object)operation);
579 }
580 }
581 }
582 return arraycast!(IUndoableOperation)( filtered
583 .toArray());
584 }
585
586 /*
587 * Flush the redo stack of all operations that have the given context.
588 */
589 private void flushRedo(IUndoContext context) {
590 if (DEBUG_OPERATION_HISTORY_DISPOSE) {
591 Tracing.printTrace("OPERATIONHISTORY", "Flushing redo history for " //$NON-NLS-1$ //$NON-NLS-2$
592 ~ (cast(Object)context).toString );
593 }
594
595 IUndoableOperation[] filtered = filter(redoList, context);
596 for (int i = 0; i < filtered.length; i++) {
597 IUndoableOperation operation = cast(IUndoableOperation) filtered[i];
598 if (context is GLOBAL_UNDO_CONTEXT
599 || operation.getContexts().length is 1) {
600 // remove the operation if it only has the context or we are
601 // flushing all
602 redoList.remove(cast(Object)operation);
603 internalRemove(operation);
604 } else {
605 // remove the reference to the context.
606 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786
607 // It is not enough to simply remove the context. There could
608 // be one or more contexts that match the one we are trying to
609 // dispose.
610 IUndoContext[] contexts = operation.getContexts();
611 for (int j = 0; j < contexts.length; j++) {
612 if (contexts[j].matches(context)) {
613 operation.removeContext(contexts[j]);
614 }
615 }
616 if (operation.getContexts().length is 0) {
617 redoList.remove(cast(Object)operation);
618 internalRemove(operation);
619 }
620 }
621 }
622 }
623
624 /*
625 * Flush the undo stack of all operations that have the given context.
626 */
627 private void flushUndo(IUndoContext context) {
628 if (DEBUG_OPERATION_HISTORY_DISPOSE) {
629 Tracing.printTrace("OPERATIONHISTORY", "Flushing undo history for " //$NON-NLS-1$ //$NON-NLS-2$
630 ~ (cast(Object)context).toString );
631 }
632
633 // Get all operations that have the context (or one that matches)
634 IUndoableOperation[] filtered = filter(undoList, context);
635 for (int i = 0; i < filtered.length; i++) {
636 IUndoableOperation operation = cast(IUndoableOperation) filtered[i];
637 if (context is GLOBAL_UNDO_CONTEXT
638 || operation.getContexts().length is 1) {
639 // remove the operation if it only has the context or we are
640 // flushing all
641 undoList.remove(cast(Object)operation);
642 internalRemove(operation);
643 } else {
644 // remove the reference to the context.
645 // See https://bugs.eclipse.org/bugs/show_bug.cgi?id=161786
646 // It is not enough to simply remove the context. There could
647 // be one or more contexts that match the one we are trying to
648 // dispose.
649 IUndoContext[] contexts = operation.getContexts();
650 for (int j = 0; j < contexts.length; j++) {
651 if (contexts[j].matches(context)) {
652 operation.removeContext(contexts[j]);
653 }
654 }
655 if (operation.getContexts().length is 0) {
656 undoList.remove(cast(Object)operation);
657 internalRemove(operation);
658 }
659 }
660 }
661 /*
662 * There may be an open composite. If it has this context, then the
663 * context must be removed. If it has only this context or we are
664 * flushing all operations, then null it out and notify that we are
665 * ending it. We don't remove it since it was never added.
666 */
667 ICompositeOperation endedComposite = null;
668 synchronized (openCompositeLock) {
669 if (openComposite !is null) {
670 if (openComposite.hasContext(context)) {
671 if (context is GLOBAL_UNDO_CONTEXT
672 || openComposite.getContexts().length is 1) {
673 endedComposite = openComposite;
674 openComposite = null;
675 } else {
676 openComposite.removeContext(context);
677 }
678 }
679 }
680 }
681 // notify outside of the synchronized block.
682 if (endedComposite !is null) {
683 notifyNotOK(endedComposite);
684 }
685 }
686
687 /*
688 * Force the redo history for the given context to contain max or less
689 * items.
690 */
691 private void forceRedoLimit(IUndoContext context, int max) {
692 IUndoableOperation[] filtered = filter(redoList, context);
693 int size = filtered.length;
694 if (size > 0) {
695 int index = 0;
696 while (size > max) {
697 IUndoableOperation removed = cast(IUndoableOperation)filtered[index];
698 if (context is GLOBAL_UNDO_CONTEXT
699 || removed.getContexts().length is 1) {
700 /*
701 * remove the operation if we are enforcing a global limit
702 * or if the operation only has the specified context
703 */
704 redoList.remove(cast(Object)removed);
705 internalRemove(removed);
706 } else {
707 /*
708 * if the operation has multiple contexts and we've reached
709 * the limit for only one of them, then just remove the
710 * context, not the operation.
711 */
712 removed.removeContext(context);
713 }
714 size--;
715 index++;
716 }
717 }
718 }
719
720 /*
721 * Force the undo history for the given context to contain max or less
722 * items.
723 */
724 private void forceUndoLimit(IUndoContext context, int max) {
725 IUndoableOperation[] filtered = filter(undoList, context);
726 int size = filtered.length;
727 if (size > 0) {
728 int index = 0;
729 while (size > max) {
730 IUndoableOperation removed = cast(IUndoableOperation)filtered[index];
731 if (context is GLOBAL_UNDO_CONTEXT
732 || removed.getContexts().length is 1) {
733 /*
734 * remove the operation if we are enforcing a global limit
735 * or if the operation only has the specified context
736 */
737 undoList.remove(cast(Object)removed);
738 internalRemove(removed);
739 } else {
740 /*
741 * if the operation has multiple contexts and we've reached
742 * the limit for only one of them, then just remove the
743 * context, not the operation.
744 */
745 removed.removeContext(context);
746 }
747 size--;
748 index++;
749 }
750 }
751 }
752
753 /*
754 * (non-Javadoc)
755 *
756 * @see org.eclipse.core.commands.operations.IOperationHistory#getLimit()
757 */
758 public int getLimit(IUndoContext context) {
759 if (!limits.containsKey(cast(Object)context)) {
760 return DEFAULT_LIMIT;
761 }
762 return (cast(Integer) (limits.get(cast(Object)context))).intValue();
763 }
764
765 /*
766 * Consult the IOperationApprovers to see if the proposed redo should be
767 * allowed.
768 */
769 private IStatus getRedoApproval(IUndoableOperation operation,
770 IAdaptable info) {
771
772 Object[] approverArray = approvers.getListeners();
773
774 for (int i = 0; i < approverArray.length; i++) {
775 IOperationApprover approver = cast(IOperationApprover) approverArray[i];
776 IStatus approval = approver.proceedRedoing(operation, this, info);
777 if (!approval.isOK()) {
778 if (DEBUG_OPERATION_HISTORY_APPROVAL) {
779 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
780 "Redo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$
781 ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$
782 ~ " approved by " ~ (cast(Object)approval).toString); //$NON-NLS-1$
783 }
784 return approval;
785 }
786 }
787 return Status.OK_STATUS;
788 }
789
790 /*
791 * (non-Javadoc)
792 *
793 * @see org.eclipse.core.commands.operations.IOperationHistory#getRedoHistory(org.eclipse.core.commands.operations.IUndoContext)
794 */
795 public IUndoableOperation[] getRedoHistory(IUndoContext context) {
796 Assert.isNotNull(cast(Object)context);
797 return filter(redoList, context);
798 }
799
800 /*
801 * (non-Javadoc)
802 *
803 * @see org.eclipse.core.commands.operations.IOperationHistory#getOperation(org.eclipse.core.commands.operations.IUndoContext)
804 */
805 public IUndoableOperation getRedoOperation(IUndoContext context) {
806 Assert.isNotNull(cast(Object)context);
807 synchronized (undoRedoHistoryLock) {
808 for (int i = redoList.size() - 1; i >= 0; i--) {
809 IUndoableOperation operation = cast(IUndoableOperation) redoList
810 .get(i);
811 if (operation.hasContext(context)) {
812 return operation;
813 }
814 }
815 }
816 return null;
817 }
818
819 /*
820 * Consult the IOperationApprovers to see if the proposed undo should be
821 * allowed.
822 */
823 private IStatus getUndoApproval(IUndoableOperation operation,
824 IAdaptable info) {
825
826 final Object[] approverArray = approvers.getListeners();
827
828 for (int i = 0; i < approverArray.length; i++) {
829 IOperationApprover approver = cast(IOperationApprover) approverArray[i];
830 IStatus approval = approver.proceedUndoing(operation, this, info);
831 if (!approval.isOK()) {
832 if (DEBUG_OPERATION_HISTORY_APPROVAL) {
833 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
834 "Undo not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$
835 ~ "for operation " ~ (cast(Object)operation ).toString//$NON-NLS-1$
836 ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$
837 }
838 return approval;
839 }
840 }
841 return Status.OK_STATUS;
842 }
843
844 /*
845 * (non-Javadoc)
846 *
847 * @see org.eclipse.core.commands.operations.IOperationHistory#getUndoHistory(org.eclipse.core.commands.operations.IUndoContext)
848 */
849 public IUndoableOperation[] getUndoHistory(IUndoContext context) {
850 Assert.isNotNull(cast(Object)context);
851 return filter(undoList, context);
852 }
853
854 /*
855 * (non-Javadoc)
856 *
857 * @see org.eclipse.core.commands.operations.IOperationHistory#getUndoOperation(org.eclipse.core.commands.operations.IUndoContext)
858 */
859 public IUndoableOperation getUndoOperation(IUndoContext context) {
860 Assert.isNotNull(cast(Object)context);
861 synchronized (undoRedoHistoryLock) {
862 for (int i = undoList.size() - 1; i >= 0; i--) {
863 IUndoableOperation operation = cast(IUndoableOperation) undoList
864 .get(i);
865 if (operation.hasContext(context)) {
866 return operation;
867 }
868 }
869 }
870 return null;
871 }
872
873 /*
874 * Consult the IOperationApprovers to see if the proposed execution should
875 * be allowed.
876 *
877 * @since 3.2
878 */
879 private IStatus getExecuteApproval(IUndoableOperation operation,
880 IAdaptable info) {
881
882 final Object[] approverArray = approvers.getListeners();
883
884 for (int i = 0; i < approverArray.length; i++) {
885 if ( auto apro = cast(IOperationApprover2)approverArray[i] ) {
886 IOperationApprover2 approver = apro;
887 IStatus approval = approver.proceedExecuting(operation, this,
888 info);
889 if (!approval.isOK()) {
890 if (DEBUG_OPERATION_HISTORY_APPROVAL) {
891 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
892 "Execute not approved by " ~ (cast(Object)approver).toString //$NON-NLS-1$
893 ~ "for operation " ~ (cast(Object)operation).toString //$NON-NLS-1$
894 ~ " with status " ~ (cast(Object)approval).toString); //$NON-NLS-1$
895 }
896 return approval;
897 }
898 }
899 }
900 return Status.OK_STATUS;
901 }
902
903 /*
904 * Remove the operation by disposing it and notifying listeners.
905 */
906 private void internalRemove(IUndoableOperation operation) {
907 operation.dispose();
908 notifyRemoved(operation);
909 }
910
911 /*
912 * Notify listeners of an operation event.
913 */
914 private void notifyListeners(OperationHistoryEvent event) {
915 if ( auto e = cast(IAdvancedUndoableOperation)event.getOperation() ) {
916 IAdvancedUndoableOperation advancedOp = e;
917 SafeRunner.run(new class(advancedOp) ISafeRunnable {
918 IAdvancedUndoableOperation advancedOp_;
919 this(IAdvancedUndoableOperation a){ advancedOp_=a;}
920 public void handleException(Exception exception) {
921 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
922 Tracing.printTrace(
923 "OPERATIONHISTORY", //$NON-NLS-1$
924 "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$
925 }
926 }
927
928 public void run() {
929 advancedOp_.aboutToNotify(event);
930 }
931 });
932 }
933 Object[] listenerArray = listeners.getListeners();
934 for (int i = 0; i < listenerArray.length; i++) {
935 IOperationHistoryListener listener = cast(IOperationHistoryListener) listenerArray[i];
936 SafeRunner.run(new class(listener) ISafeRunnable {
937 IOperationHistoryListener listener_;
938 this(IOperationHistoryListener a){ listener_=a; }
939 public void handleException(Exception exception) {
940 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
941 Tracing.printTrace(
942 "OPERATIONHISTORY", //$NON-NLS-1$
943 "Exception during notification callback " ~ exception.toString); //$NON-NLS-1$
944 }
945 }
946
947 public void run() {
948 listener_.historyNotification(event);
949 }
950 });
951 }
952 }
953
954 private void notifyAboutToExecute(IUndoableOperation operation) {
955 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
956 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_EXECUTE " //$NON-NLS-1$ //$NON-NLS-2$
957 ~ (cast(Object)operation).toString);
958 }
959
960 notifyListeners(new OperationHistoryEvent(
961 OperationHistoryEvent.ABOUT_TO_EXECUTE, this, operation));
962 }
963
964 /*
965 * Notify listeners that an operation is about to redo.
966 */
967 private void notifyAboutToRedo(IUndoableOperation operation) {
968 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
969 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_REDO " //$NON-NLS-1$ //$NON-NLS-2$
970 ~ (cast(Object)operation).toString);
971 }
972
973 notifyListeners(new OperationHistoryEvent(
974 OperationHistoryEvent.ABOUT_TO_REDO, this, operation));
975 }
976
977 /*
978 * Notify listeners that an operation is about to undo.
979 */
980 private void notifyAboutToUndo(IUndoableOperation operation) {
981 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
982 Tracing.printTrace("OPERATIONHISTORY", "ABOUT_TO_UNDO " //$NON-NLS-1$ //$NON-NLS-2$
983 ~ (cast(Object)operation).toString);
984 }
985
986 notifyListeners(new OperationHistoryEvent(
987 OperationHistoryEvent.ABOUT_TO_UNDO, this, operation));
988 }
989
990 /*
991 * Notify listeners that an operation has been added.
992 */
993 private void notifyAdd(IUndoableOperation operation) {
994 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
995 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_ADDED " //$NON-NLS-1$ //$NON-NLS-2$
996 ~ (cast(Object)operation).toString);
997 }
998
999 notifyListeners(new OperationHistoryEvent(
1000 OperationHistoryEvent.OPERATION_ADDED, this, operation));
1001 }
1002
1003 /*
1004 * Notify listeners that an operation is done executing.
1005 */
1006 private void notifyDone(IUndoableOperation operation) {
1007 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1008 Tracing.printTrace("OPERATIONHISTORY", "DONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$
1009 }
1010
1011 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.DONE,
1012 this, operation));
1013 }
1014
1015 /*
1016 * Notify listeners that an operation did not succeed after an attempt to
1017 * execute, undo, or redo was made.
1018 */
1019 private void notifyNotOK(IUndoableOperation operation) {
1020 notifyNotOK(operation, null);
1021 }
1022
1023 /*
1024 * Notify listeners that an operation did not succeed after an attempt to
1025 * execute, undo, or redo was made. Include the status associated with the
1026 * attempt.
1027 *
1028 * @since 3.2
1029 */
1030 private void notifyNotOK(IUndoableOperation operation, IStatus status) {
1031 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1032 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_NOT_OK " //$NON-NLS-1$ //$NON-NLS-2$
1033 ~ (cast(Object)operation).toString);
1034 }
1035
1036 notifyListeners(new OperationHistoryEvent(
1037 OperationHistoryEvent.OPERATION_NOT_OK, this, operation, status));
1038 }
1039
1040 /*
1041 * Notify listeners that an operation was redone.
1042 */
1043 private void notifyRedone(IUndoableOperation operation) {
1044 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1045 Tracing.printTrace("OPERATIONHISTORY", "REDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$
1046 }
1047
1048 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.REDONE,
1049 this, operation));
1050 }
1051
1052 /*
1053 * Notify listeners that an operation has been removed from the history.
1054 */
1055 private void notifyRemoved(IUndoableOperation operation) {
1056 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1057 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_REMOVED " //$NON-NLS-1$ //$NON-NLS-2$
1058 ~ (cast(Object)operation).toString);
1059 }
1060
1061 notifyListeners(new OperationHistoryEvent(
1062 OperationHistoryEvent.OPERATION_REMOVED, this, operation));
1063 }
1064
1065 /*
1066 * Notify listeners that an operation has been undone.
1067 */
1068 private void notifyUndone(IUndoableOperation operation) {
1069 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1070 Tracing.printTrace("OPERATIONHISTORY", "UNDONE " ~ (cast(Object)operation).toString); //$NON-NLS-1$ //$NON-NLS-2$
1071 }
1072
1073 notifyListeners(new OperationHistoryEvent(OperationHistoryEvent.UNDONE,
1074 this, operation));
1075 }
1076
1077 /*
1078 * Notify listeners that an operation has been undone.
1079 */
1080 private void notifyChanged(IUndoableOperation operation) {
1081 if (DEBUG_OPERATION_HISTORY_NOTIFICATION) {
1082 Tracing.printTrace("OPERATIONHISTORY", "OPERATION_CHANGED " //$NON-NLS-1$//$NON-NLS-2$
1083 ~ (cast(Object)operation).toString);
1084 }
1085
1086 notifyListeners(new OperationHistoryEvent(
1087 OperationHistoryEvent.OPERATION_CHANGED, this, operation));
1088 }
1089
1090 /*
1091 * (non-Javadoc)
1092 *
1093 * @see org.eclipse.core.commands.operations.IOperationHistory#redo(org.eclipse.core.commands.operations.IUndoContext,
1094 * org.eclipse.core.runtime.IProgressMonitor,
1095 * org.eclipse.core.runtime.IAdaptable)
1096 */
1097 public IStatus redo(IUndoContext context, IProgressMonitor monitor,
1098 IAdaptable info) {
1099 Assert.isNotNull(cast(Object)context);
1100 IUndoableOperation operation = getRedoOperation(context);
1101
1102 // info if there is no operation
1103 if (operation is null) {
1104 return IOperationHistory.NOTHING_TO_REDO_STATUS;
1105 }
1106
1107 // error if operation is invalid
1108 if (!operation.canRedo()) {
1109 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1110 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1111 "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$
1112 }
1113
1114 return IOperationHistory.OPERATION_INVALID_STATUS;
1115 }
1116
1117 return doRedo(monitor, info, operation);
1118 }
1119
1120 /*
1121 * (non-Javadoc)
1122 *
1123 * @see org.eclipse.core.commands.operations.IOperationHistory#redoOperation(org.eclipse.core.commands.operations.IUndoableOperation,
1124 * org.eclipse.core.runtime.IProgressMonitor,
1125 * org.eclipse.core.runtime.IAdaptable)
1126 */
1127
1128 public IStatus redoOperation(IUndoableOperation operation,
1129 IProgressMonitor monitor, IAdaptable info) {
1130 Assert.isNotNull(cast(Object)operation);
1131 IStatus status;
1132 if (operation.canRedo()) {
1133 status = doRedo(monitor, info, operation);
1134 } else {
1135 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1136 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1137 "Redo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$
1138 }
1139 status = IOperationHistory.OPERATION_INVALID_STATUS;
1140 }
1141 return status;
1142 }
1143
1144 /*
1145 * (non-Javadoc)
1146 *
1147 * @see org.eclipse.core.commands.operations.IOperationHistory#removeOperationApprover(org.eclipse.core.commands.operations.IOperationApprover)
1148 */
1149 public void removeOperationApprover(IOperationApprover approver) {
1150 approvers.remove(cast(Object)approver);
1151 }
1152
1153 /*
1154 * (non-Javadoc)
1155 *
1156 * @see org.eclipse.core.commands.operations.IOperationHistory#removeOperationHistoryListener(org.eclipse.core.commands.operations.IOperationHistoryListener)
1157 */
1158 public void removeOperationHistoryListener(
1159 IOperationHistoryListener listener) {
1160 listeners.remove(cast(Object)listener);
1161 }
1162
1163 /*
1164 * (non-Javadoc)
1165 *
1166 * @see org.eclipse.core.commands.operations.IOperationHistory#replaceOperation(org.eclipse.core.commands.operations.IUndoableOperation,
1167 * org.eclipse.core.commands.operations.IUndoableOperation [])
1168 */
1169 public void replaceOperation(IUndoableOperation operation,
1170 IUndoableOperation[] replacements) {
1171 // check the undo history first.
1172 bool inUndo = false;
1173 synchronized (undoRedoHistoryLock) {
1174 int index = undoList.indexOf(cast(Object)operation);
1175 if (index > -1) {
1176 inUndo = true;
1177 undoList.remove(cast(Object)operation);
1178 // notify listeners after the lock on undoList is released
1179 ArrayList allContexts = new ArrayList(replacements.length);
1180 for (int i = 0; i < replacements.length; i++) {
1181 IUndoContext[] opContexts = replacements[i].getContexts();
1182 for (int j = 0; j < opContexts.length; j++) {
1183 allContexts.add(cast(Object)opContexts[j]);
1184 }
1185 undoList.add(index, cast(Object)replacements[i]);
1186 // notify listeners after the lock on the history is
1187 // released
1188 }
1189 // recheck all the limits. We do this at the end so the index
1190 // doesn't change during replacement
1191 for (int i = 0; i < allContexts.size(); i++) {
1192 IUndoContext context = cast(IUndoContext) allContexts.get(i);
1193 forceUndoLimit(context, getLimit(context));
1194 }
1195 }
1196 }
1197 if (inUndo) {
1198 // notify listeners of operations added and removed
1199 internalRemove(operation);
1200 for (int i = 0; i < replacements.length; i++) {
1201 notifyAdd(replacements[i]);
1202 }
1203 return;
1204 }
1205
1206 // operation was not in the undo history. Check the redo history.
1207
1208 synchronized (undoRedoHistoryLock) {
1209 int index = redoList.indexOf(cast(Object)operation);
1210 if (index is -1) {
1211 return;
1212 }
1213 ArrayList allContexts = new ArrayList(replacements.length);
1214 redoList.remove(cast(Object)operation);
1215 // notify listeners after we release the lock on redoList
1216 for (int i = 0; i < replacements.length; i++) {
1217 IUndoContext[] opContexts = replacements[i].getContexts();
1218 for (int j = 0; j < opContexts.length; j++) {
1219 allContexts.add(cast(Object)opContexts[j]);
1220 }
1221 redoList.add(index, cast(Object)replacements[i]);
1222 // notify listeners after we release the lock on redoList
1223 }
1224 // recheck all the limits. We do this at the end so the index
1225 // doesn't change during replacement
1226 for (int i = 0; i < allContexts.size(); i++) {
1227 IUndoContext context = cast(IUndoContext) allContexts.get(i);
1228 forceRedoLimit(context, getLimit(context));
1229 }
1230 }
1231 // send listener notifications after we release the lock on the history
1232 internalRemove(operation);
1233 for (int i = 0; i < replacements.length; i++) {
1234 notifyAdd(replacements[i]);
1235 }
1236 }
1237
1238 /*
1239 * (non-Javadoc)
1240 *
1241 * @see org.eclipse.core.commands.operations.IOperationHistory#setLimit(org.eclipse.core.commands.operations.IUndoContext,
1242 * int)
1243 */
1244 public void setLimit(IUndoContext context, int limit) {
1245 Assert.isTrue(limit >= 0);
1246 /*
1247 * The limit checking methods interpret a null context as a global limit
1248 * to be enforced. We do not wish to support a global limit in this
1249 * implementation, so we throw an exception for a null context. The rest
1250 * of the implementation can handle a null context, so subclasses can
1251 * override this if a global limit is desired.
1252 */
1253 Assert.isNotNull(cast(Object)context);
1254 limits.put(cast(Object)context, new Integer(limit));
1255 forceUndoLimit(context, limit);
1256 forceRedoLimit(context, limit);
1257
1258 }
1259
1260 /*
1261 * (non-Javadoc)
1262 *
1263 * @see org.eclipse.core.commands.operations.IOperationHistory#undo(org.eclipse.core.commands.operations.IUndoContext,
1264 * org.eclipse.core.runtime.IProgressMonitor,
1265 * org.eclipse.core.runtime.IAdaptable)
1266 */
1267 public IStatus undo(IUndoContext context, IProgressMonitor monitor,
1268 IAdaptable info) {
1269 Assert.isNotNull(cast(Object)context);
1270 IUndoableOperation operation = getUndoOperation(context);
1271
1272 // info if there is no operation
1273 if (operation is null) {
1274 return IOperationHistory.NOTHING_TO_UNDO_STATUS;
1275 }
1276
1277 // error if operation is invalid
1278 if (!operation.canUndo()) {
1279 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1280 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1281 "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$
1282 }
1283 return IOperationHistory.OPERATION_INVALID_STATUS;
1284 }
1285
1286 return doUndo(monitor, info, operation);
1287 }
1288
1289 /*
1290 * (non-Javadoc)
1291 *
1292 * @see org.eclipse.core.commands.operations.IOperationHistory#undoOperation(org.eclipse.core.commands.operations.IUndoableOperation,
1293 * org.eclipse.core.runtime.IProgressMonitor,
1294 * org.eclipse.core.runtime.IAdaptable)
1295 */
1296 public IStatus undoOperation(IUndoableOperation operation,
1297 IProgressMonitor monitor, IAdaptable info) {
1298 Assert.isNotNull(cast(Object)operation);
1299 IStatus status;
1300 if (operation.canUndo()) {
1301 status = doUndo(monitor, info, operation);
1302 } else {
1303 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1304 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1305 "Undo operation not valid - " ~ (cast(Object)operation).toString); //$NON-NLS-1$
1306 }
1307 status = IOperationHistory.OPERATION_INVALID_STATUS;
1308 }
1309 return status;
1310 }
1311
1312 /*
1313 * (non-Javadoc)
1314 *
1315 * @see org.eclipse.core.commands.operations.IOperationHistory#openOperation(org.eclipse.core.commands.operations.ICompositeOperation)
1316 */
1317 public void openOperation(ICompositeOperation operation, int mode) {
1318 synchronized (openCompositeLock) {
1319 if (openComposite !is null && openComposite !is operation) {
1320 // unexpected nesting of operations.
1321 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1322 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1323 "Open operation called while another operation is open. old: " //$NON-NLS-1$
1324 ~ (cast(Object)openComposite).toString ~ "; new: " ~ (cast(Object)operation).toString); //$NON-NLS-1$
1325 }
1326
1327 throw new IllegalStateException(
1328 "Cannot open an operation while one is already open"); //$NON-NLS-1$
1329 }
1330 openComposite = operation;
1331 }
1332 if (DEBUG_OPERATION_HISTORY_OPENOPERATION) {
1333 Tracing.printTrace("OPERATIONHISTORY", "Opening operation " //$NON-NLS-1$ //$NON-NLS-2$
1334 ~ (cast(Object)openComposite).toString);
1335 }
1336
1337 if (mode is EXECUTE) {
1338 notifyAboutToExecute(openComposite);
1339 }
1340 }
1341
1342 /*
1343 * (non-Javadoc)
1344 *
1345 * @see org.eclipse.core.commands.operations.IOperationHistory#closeOperation(bool,
1346 * bool)
1347 */
1348 public void closeOperation(bool operationOK, bool addToHistory,
1349 int mode) {
1350 ICompositeOperation endedComposite = null;
1351
1352 synchronized (openCompositeLock) {
1353 if (DEBUG_OPERATION_HISTORY_UNEXPECTED) {
1354 if (openComposite is null) {
1355 Tracing.printTrace("OPERATIONHISTORY", //$NON-NLS-1$
1356 "Attempted to close operation when none was open"); //$NON-NLS-1$
1357 return;
1358 }
1359 }
1360 // notifications will occur outside the synchonized block
1361 if (openComposite !is null) {
1362 if (DEBUG_OPERATION_HISTORY_OPENOPERATION) {
1363 Tracing.printTrace("OPERATIONHISTORY", "Closing operation " //$NON-NLS-1$ //$NON-NLS-2$
1364 ~ (cast(Object)openComposite).toString);
1365 }
1366 endedComposite = openComposite;
1367 openComposite = null;
1368 }
1369 }
1370 // any mode other than EXECUTE was triggered by a request to undo or
1371 // redo something already in the history, so undo and redo
1372 // notification will occur at the end of that sequence.
1373 if (endedComposite !is null) {
1374 if (operationOK) {
1375 if (mode is EXECUTE) {
1376 notifyDone(endedComposite);
1377 }
1378 if (addToHistory) {
1379 add(endedComposite);
1380 }
1381 } else {
1382 if (mode is EXECUTE) {
1383 notifyNotOK(endedComposite);
1384 }
1385 }
1386 }
1387 }
1388
1389 /*
1390 * (non-Javadoc)
1391 *
1392 * @see org.eclipse.core.commands.operations.IOperationHistory#operationChanged(org.eclipse.core.commands.operations.IUndoableOperation)
1393 */
1394 public void operationChanged(IUndoableOperation operation) {
1395 if (undoList.contains(cast(Object)operation) || redoList.contains(cast(Object)operation)) {
1396 notifyChanged(operation);
1397 }
1398 }
1399 }