| /******************************************************************************* |
| * Copyright (c) 2005, 2012 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.bpel.ui.commands.util; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.bpel.common.extension.model.Extension; |
| import org.eclipse.bpel.common.extension.model.ExtensionMap; |
| import org.eclipse.bpel.common.extension.model.ExtensionmodelPackage; |
| import org.eclipse.bpel.common.extension.model.impl.ExtensionImpl; |
| import org.eclipse.bpel.common.extension.model.impl.ExtensionMapImpl; |
| import org.eclipse.bpel.common.extension.model.notify.ExtensionModelNotification; |
| import org.eclipse.bpel.ui.BPELEditor; |
| import org.eclipse.bpel.ui.BPELUIPlugin; |
| import org.eclipse.bpel.ui.util.BPELUtil; |
| import org.eclipse.bpel.ui.util.ModelHelper; |
| import org.eclipse.emf.common.notify.Adapter; |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.resource.Resource; |
| import org.eclipse.emf.ecore.resource.ResourceSet; |
| import org.eclipse.emf.ecore.util.EContentAdapter; |
| import org.eclipse.core.runtime.Assert; |
| |
| |
| /** |
| * Records the exact changes made to the model during each 'user change'. This |
| * history information can be used to Undo and Redo user changes automatically. |
| */ |
| public class ModelAutoUndoRecorder implements IAutoUndoRecorder { |
| |
| protected Set<Resource> ignoreResources = new HashSet<Resource>(); |
| |
| protected boolean VERBOSE_DEBUG = false; |
| protected boolean DEBUG = false || VERBOSE_DEBUG; |
| |
| protected Set<Notifier> listenerRootSet = new HashSet<Notifier>(); |
| |
| protected List<Object> currentChangeList = null; |
| |
| /** |
| * IUndoHandler to undo/redo a change to the ExtensionMap (adding, changing or |
| * removing an extension entry). |
| */ |
| class EMapSingleChangeHandler implements IUndoHandler { |
| ExtensionMap fExtensionMap; |
| EObject fExtendedObject, fOldExtension, fNewExtension; |
| |
| /** |
| * @param extensionMap |
| * @param extendedObject |
| * @param oldExtension |
| * @param newExtension |
| */ |
| public EMapSingleChangeHandler(ExtensionMap extensionMap, EObject extendedObject, |
| EObject oldExtension, EObject newExtension) |
| { |
| this.fExtensionMap = extensionMap; this.fExtendedObject = extendedObject; |
| this.fOldExtension = oldExtension; this.fNewExtension = newExtension; |
| } |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IUndoHandler#undo() |
| */ |
| public void undo() { |
| if (DEBUG) System.out.println("undo single change"); //$NON-NLS-1$ |
| if (fOldExtension == null) { |
| if (fExtensionMap.containsKey(fExtendedObject)) { |
| fExtensionMap.remove(fExtendedObject); |
| } |
| } else { |
| fExtensionMap.put(fExtendedObject, fOldExtension); |
| } |
| } |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IUndoHandler#redo() |
| */ |
| public void redo() { |
| if (DEBUG) System.out.println("redo single change"); //$NON-NLS-1$ |
| if (fNewExtension == null) { |
| if (fExtensionMap.containsKey(fExtendedObject)) { |
| fExtensionMap.remove(fExtendedObject); |
| } |
| } else { |
| fExtensionMap.put(fExtendedObject, fNewExtension); |
| } |
| } |
| } |
| |
| /** |
| * IUndoHandler to undo/redo a bulk change to the ExtensionMap (i.e. putAll or clear). |
| */ |
| class EMapMultiChangeHandler implements IUndoHandler { |
| ExtensionMap fExtensionMap; |
| Map fOldContents, fNewContents; |
| |
| /** |
| * @param extensionMap |
| * @param oldContents |
| * @param newContents |
| */ |
| public EMapMultiChangeHandler(ExtensionMap extensionMap, Map oldContents, Map newContents) { |
| this.fExtensionMap = extensionMap; |
| this.fOldContents = oldContents; |
| this.fNewContents = newContents; |
| } |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IUndoHandler#undo() |
| */ |
| public void undo() { |
| if (DEBUG) System.out.println("undo multi-change"); //$NON-NLS-1$ |
| fExtensionMap.clear(); |
| if (fOldContents != null) fExtensionMap.putAll(fOldContents); |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IUndoHandler#redo() |
| */ |
| public void redo() { |
| if (DEBUG) System.out.println("redo multi-change"); //$NON-NLS-1$ |
| fExtensionMap.clear(); |
| if (fNewContents != null) { |
| fExtensionMap.putAll(fNewContents); |
| } |
| } |
| } |
| |
| class ModelAutoUndoAdapter extends EContentAdapter { |
| |
| protected ModelAutoUndoRecorder getAutoUndoRecorder() { return ModelAutoUndoRecorder.this; } |
| |
| /** |
| * Handles a containment change by adding and removing the adapter as appropriate. |
| */ |
| @Override |
| protected void handleContainment(Notification notification) { |
| switch (notification.getEventType()) { |
| case Notification.SET: |
| case Notification.UNSET: { |
| Notifier newValue = (Notifier)notification.getNewValue(); |
| if (newValue != null && !newValue.eAdapters().contains(this)) { |
| newValue.eAdapters().add(this); |
| } |
| break; |
| } |
| case Notification.ADD: { |
| Notifier newValue = (Notifier) notification.getNewValue(); |
| if (newValue != null && !newValue.eAdapters().contains(this)) { |
| newValue.eAdapters().add(this); |
| } |
| break; |
| } |
| case Notification.ADD_MANY: { |
| Collection<Notifier> newValues = (Collection<Notifier>) notification.getNewValue(); |
| for(Notifier next : newValues) { |
| if (!next.eAdapters().contains(this)) { |
| next.eAdapters().add(this); |
| } |
| } |
| break; |
| } |
| |
| //if (n.getNotifier() instanceof ResourceSet) return; |
| } |
| |
| } |
| |
| /** |
| * note: super implementation doesn't handle overlapping targets very well |
| * |
| * @see org.eclipse.emf.ecore.util.EContentAdapter#setTarget(org.eclipse.emf.common.notify.Notifier) |
| */ |
| @Override |
| public void setTarget(Notifier aTarget) { |
| this.target = aTarget; |
| |
| List<?> contents = null; |
| |
| if (target instanceof EObject) { |
| contents = ((EObject)aTarget).eContents(); |
| } else if (target instanceof ResourceSet) { |
| contents = ((ResourceSet)target).getResources(); |
| } else if (target instanceof Resource) { |
| contents = ((Resource)target).getContents(); |
| } else { |
| return ; |
| } |
| |
| for (Object next : contents) { |
| Notifier notifier = (Notifier) next ; |
| if (!notifier.eAdapters().contains(this)) { |
| notifier.eAdapters().add(this); |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.emf.ecore.util.EContentAdapter#notifyChanged(org.eclipse.emf.common.notify.Notification) |
| */ |
| |
| @Override |
| public void notifyChanged(Notification n) { |
| switch (n.getEventType()) { |
| case Notification.ADD_MANY: |
| case Notification.REMOVE_MANY: |
| case Notification.ADD: |
| case Notification.REMOVE: |
| case Notification.SET: |
| case Notification.UNSET: |
| case Notification.MOVE: |
| if (!ignoreChange(n)) { |
| recordChange(n); |
| } |
| } |
| super.notifyChanged(n); |
| } |
| |
| } |
| |
| protected ModelAutoUndoAdapter modelAutoUndoAdapter = new ModelAutoUndoAdapter(); |
| |
| protected boolean ignoreChange(Notification n) { |
| Resource res = null; |
| if (n.getNotifier() instanceof ResourceSet) { |
| // don't record resources being added (usually this is due to demand-loading). |
| // TODO: what about resources being deleted? Is this a problem? |
| return true; |
| } |
| if (n.getNotifier() instanceof Resource) { |
| res = (Resource)n.getNotifier(); |
| } else if (n.getNotifier() instanceof EObject) { |
| res = ((EObject)n.getNotifier()).eResource(); |
| } else { |
| // we won't know how to undo this notification anyways, so ignore it. |
| // TODO: this should never occur |
| return true; |
| } |
| if (res != null && ignoreResources.contains(res)) { |
| if (VERBOSE_DEBUG) System.out.println("IGNORING -- "+ //$NON-NLS-1$ |
| (n.isTouch()?"<t> " : "CHG: ")+BPELUtil.debug(n)); //$NON-NLS-1$ //$NON-NLS-2$ |
| return true; |
| } |
| return false; |
| } |
| |
| protected void recordChange(Notification n) { |
| if (currentChangeList == null) { |
| // ignore. |
| //System.out.println("IGNORING!! -- "+ //$NON-NLS-1$ |
| // (n.isTouch()?"<t> " : "CHG: ")+BPELUtil.debug(n)); //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| } |
| |
| // hackedy-hack hack. |
| if (n.getNotifier() instanceof Extension) { |
| // ignore (these are an implementation detail of ExtensionMapImpl). |
| return; |
| } |
| if (n.getNotifier() instanceof ExtensionMapImpl) { |
| // ignore "real" events concerning the EXTENSION_MAP__EXTENSIONS list. |
| // record only "semantic" events (tagged with EXTENSION_MAP__EXTENSIONS_KEY). |
| // those represent put() and remove() calls to the extension map. |
| if (n.getFeatureID(ExtensionMap.class) == ExtensionmodelPackage.EXTENSION_MAP__EXTENSIONS) { |
| if (DEBUG) System.out.println("ignoring impl notification: "+BPELUtil.debug(n)); //$NON-NLS-1$ |
| return; |
| } |
| if (n instanceof ExtensionModelNotification) { |
| // A semantic notification just for us. Thanks Sebastian! |
| ExtensionModelNotification emn = (ExtensionModelNotification)n; |
| ExtensionMap extensionMap = (ExtensionMap)n.getNotifier(); |
| // handle put() |
| if (n.getFeatureID(ExtensionMap.class) == ExtensionModelNotification.EXTENSION_MAP_PUT) { |
| EObject object = (EObject)emn.getArg1(); |
| EObject oldExt = (EObject)emn.getArg2(); |
| EObject newExt = extensionMap.get(object); |
| if (DEBUG) System.out.println("record PUT: "+BPELUtil.debugObject(object)+": "+ //$NON-NLS-1$ //$NON-NLS-2$ |
| BPELUtil.debugObject(oldExt)+" ==> "+BPELUtil.debugObject(newExt)); //$NON-NLS-1$ |
| currentChangeList.add(new EMapSingleChangeHandler(extensionMap, object, oldExt, newExt)); |
| } else if (n.getFeatureID(ExtensionMap.class) == ExtensionModelNotification.EXTENSION_MAP_REMOVE) { |
| EObject object = (EObject)emn.getArg1(); |
| EObject oldExt = (EObject)emn.getArg2(); |
| EObject newExt = null; |
| if (DEBUG) System.out.println("record REMOVE: "+BPELUtil.debugObject(object)+": "+ //$NON-NLS-1$ //$NON-NLS-2$ |
| BPELUtil.debugObject(oldExt)+" ==> "+BPELUtil.debugObject(newExt)); //$NON-NLS-1$ |
| currentChangeList.add(new EMapSingleChangeHandler(extensionMap, object, oldExt, newExt)); |
| } else if (n.getFeatureID(ExtensionMap.class) == ExtensionModelNotification.EXTENSION_MAP_PUTALL) { |
| Map oldContents = (Map)emn.getArg1(); |
| Map newContents = new HashMap(extensionMap); |
| if (DEBUG) System.out.println("record PUTALL: "+oldContents.size()+" items ==> "+newContents.size()+" items"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| currentChangeList.add(new EMapMultiChangeHandler(extensionMap, oldContents, newContents)); |
| } else if (n.getFeatureID(ExtensionMap.class) == ExtensionModelNotification.EXTENSION_MAP_CLEAR) { |
| Map oldContents = (Map)emn.getArg1(); |
| Map newContents = Collections.EMPTY_MAP; |
| if (DEBUG) System.out.println("record CLEAR: "+oldContents.size()+" items ==> "+newContents.size()+" items"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| currentChangeList.add(new EMapMultiChangeHandler(extensionMap, oldContents, newContents)); |
| } else { |
| if (DEBUG) System.out.println("WARNING: ModelAutoUndoRecorder.recordChange(): unknown event type from ExtensionMapImpl"); //$NON-NLS-1$ |
| } |
| return; |
| } |
| if (DEBUG) System.out.println("ExtensionMap: "+BPELUtil.debug(n)); //$NON-NLS-1$ |
| } |
| |
| // handle all other notifications. |
| if (DEBUG) System.out.println((n.isTouch()?"<t> " : "CHG: ")+BPELUtil.debug(n)); //$NON-NLS-1$ //$NON-NLS-2$ |
| currentChangeList.add(n); |
| } |
| |
| /** |
| * @param resource |
| */ |
| public void startIgnoringResource(Resource resource) { |
| ignoreResources.add(resource); |
| } |
| /** |
| * @param resource |
| */ |
| |
| public void stopIgnoringResource(Resource resource) { |
| ignoreResources.remove(resource); |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#startChanges(java.util.List) |
| */ |
| @SuppressWarnings("nls") |
| public void startChanges( List<Object> modelRoots) { |
| if (VERBOSE_DEBUG) { |
| System.out.println("startChanges()"); //$NON-NLS-1$ |
| } |
| if (currentChangeList != null) { |
| throw new IllegalStateException("startChages(): pending current change list"); |
| } |
| currentChangeList = new ArrayList<Object>(); |
| addModelRoots(modelRoots); |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#finishChanges() |
| */ |
| @SuppressWarnings("nls") |
| public List<Object> finishChanges() { |
| if (currentChangeList == null) { |
| throw new IllegalStateException("Nothing to finish, currentChangeList is committed"); |
| } |
| if (VERBOSE_DEBUG) System.out.println("finishChanges(): "+currentChangeList.size()); //$NON-NLS-1$ |
| List<Object> result = currentChangeList; |
| currentChangeList = null; |
| clearModelRoots(); |
| return result; |
| } |
| |
| protected void addModelRoot(Object root) { |
| // TODO: TEMPORARY HACK!! This is to work around the problems with |
| // duplicate/overlapping adapters when we use non-resource roots. |
| // I don't know what the real solution is for this problem. |
| if (root instanceof EObject) { root = ((EObject)root).eResource(); } |
| |
| if (root instanceof Notifier) { |
| List<Adapter> adapters = ((Notifier)root).eAdapters(); |
| // careful: only add adapter if it hasn't already been added, or duplicate |
| // notifications will be recorded and things will break. |
| if (!adapters.contains(modelAutoUndoAdapter)) { |
| if (VERBOSE_DEBUG) System.out.println(" >>> Add Root: "+root); //$NON-NLS-1$ |
| adapters.add(modelAutoUndoAdapter); |
| listenerRootSet.add((Notifier) root); |
| } else { |
| if (VERBOSE_DEBUG) System.out.println(" >>> Overlapping Root: "+root); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#addModelRoots(java.util.List) |
| */ |
| public void addModelRoots(List<Object> modelRoots) { |
| boolean gotExtensionMap = false; |
| |
| for(Object root : modelRoots) { |
| addModelRoot(root); |
| // HACK! treat the ExtensionMap as a model root |
| if (!gotExtensionMap) { |
| BPELEditor bpelEditor = ModelHelper.getBPELEditor(root); |
| if (bpelEditor != null) { |
| addModelRoot(bpelEditor.getExtensionMap()); |
| } |
| gotExtensionMap = true; |
| } |
| } |
| } |
| |
| protected void clearModelRoots() { |
| if (VERBOSE_DEBUG) System.out.println(" <<< Clear Model Roots"); //$NON-NLS-1$ |
| |
| for (Notifier notifier : listenerRootSet) { |
| // HACK! |
| while ((notifier instanceof EObject) && ((EObject)notifier).eContainer().eAdapters().contains(modelAutoUndoAdapter)) { |
| notifier = ((EObject)notifier).eContainer(); |
| } |
| notifier.eAdapters().remove(modelAutoUndoAdapter); |
| } |
| listenerRootSet.clear(); |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#isRecordingChanges() |
| */ |
| public boolean isRecordingChanges() { |
| return (currentChangeList != null); |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#insertUndoHandler(org.eclipse.bpel.ui.commands.util.IUndoHandler) |
| */ |
| public void insertUndoHandler(IUndoHandler undoHandler) { |
| if (currentChangeList != null) { |
| currentChangeList.add(undoHandler); |
| } else { |
| if (DEBUG) System.out.println("WARNING: insertUndoHandler() while not recording changes!"); //$NON-NLS-1$ |
| } |
| } |
| |
| protected void undoNotification(Notification n) { |
| List list; |
| |
| // hack to work around side-effect ordering problems! |
| if (n.getNotifier() instanceof ExtensionImpl) { |
| if (DEBUG) System.out.println("ignore ExtensionImpl change: "+BPELUtil.debug(n)); //$NON-NLS-1$ |
| return; |
| } |
| |
| if (DEBUG) System.out.println((n.isTouch()? "<t> " : "undo: ")+BPELUtil.debug(n)); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| EStructuralFeature feature = (EStructuralFeature)n.getFeature(); |
| |
| if (n.getNotifier() instanceof EObject) { |
| EObject obj = (EObject)n.getNotifier(); |
| |
| switch (n.getEventType()) { |
| case Notification.ADD_MANY: |
| case Notification.REMOVE_MANY: |
| Assert.isTrue(feature.isMany()); |
| obj.eSet(feature, n.getOldValue()); |
| break; |
| |
| case Notification.ADD: |
| Assert.isTrue(feature.isMany()); |
| list = (List)obj.eGet(feature, true); |
| try { |
| list.remove(n.getPosition()); |
| } catch (ClassCastException e) { |
| // it shouldn't be happening but there |
| // is a bug in the WSDL model |
| if (DEBUG) e.printStackTrace(); |
| } |
| break; |
| |
| case Notification.REMOVE: |
| Assert.isTrue(feature.isMany()); |
| list = (List)obj.eGet(feature, true); |
| if (n.getPosition() == Notification.NO_INDEX) { |
| list.add(n.getOldValue()); |
| } else { |
| list.add(n.getPosition(), n.getOldValue()); |
| } |
| break; |
| |
| case Notification.SET: |
| case Notification.UNSET: |
| if (feature.isMany() && n.getPosition() >= 0) { |
| list = (List)obj.eGet(feature, true); |
| list.set(n.getPosition(), n.getOldValue()); |
| } else if (n.wasSet()) { |
| obj.eSet(feature, n.getOldValue()); |
| } else { |
| obj.eUnset(feature); |
| } |
| break; |
| |
| case Notification.MOVE: |
| Assert.isTrue(feature.isMany()); |
| // TODO: does this even work?! |
| obj.eSet(feature, n.getOldValue()); |
| break; |
| |
| default: throw new IllegalStateException(); |
| } |
| |
| } else { |
| // TODO: adding and removing things from resources ?? |
| System.err.println("undoNotification on non-EObject not implemented yet"); //$NON-NLS-1$ |
| (new Exception()).printStackTrace(System.err); |
| } |
| } |
| |
| // TODO: this stuff should be refactored/moved somewhere else. |
| protected void redoNotification(Notification n) { |
| List<Object> list; |
| if (DEBUG) System.out.println((n.isTouch()? "<t> " : "redo: ")+BPELUtil.debug(n)); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| EStructuralFeature feature = (EStructuralFeature)n.getFeature(); |
| |
| |
| if (n.getNotifier() instanceof EObject) { |
| EObject obj = (EObject)n.getNotifier(); |
| |
| switch (n.getEventType()) { |
| case Notification.ADD_MANY: |
| case Notification.REMOVE_MANY: |
| Assert.isTrue(feature.isMany()); |
| obj.eSet(feature, n.getNewValue()); |
| break; |
| |
| case Notification.ADD: |
| Assert.isTrue(feature.isMany()); |
| list = (List)obj.eGet(feature, true); |
| list.add(n.getPosition(), n.getNewValue()); |
| break; |
| |
| case Notification.REMOVE: |
| Assert.isTrue(feature.isMany()); |
| list = (List)obj.eGet(feature, true); |
| if (n.getPosition() == Notification.NO_INDEX) { |
| list.remove(n.getOldValue()); |
| } else { |
| list.remove(n.getPosition()); |
| } |
| break; |
| |
| case Notification.SET: |
| if (feature.isMany() && n.getPosition() >= 0) { |
| list = (List)obj.eGet(feature, true); |
| list.set(n.getPosition(), n.getNewValue()); |
| } else { |
| obj.eSet(feature, n.getNewValue()); |
| } |
| break; |
| |
| case Notification.UNSET: |
| obj.eUnset(feature); |
| break; |
| |
| case Notification.MOVE: |
| Assert.isTrue(feature.isMany()); |
| // TODO: does this even work?! |
| obj.eSet(feature, n.getNewValue()); |
| break; |
| |
| default: throw new IllegalStateException(); |
| } |
| |
| } else { |
| // TODO: adding and removing things from resources ?? |
| System.err.println("redoNotification on non-EObject not implemented yet"); //$NON-NLS-1$ |
| (new Exception()).printStackTrace(System.err); |
| } |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#undo(java.util.List) |
| */ |
| public void undo (List<Object> changes) { |
| if (VERBOSE_DEBUG) System.out.println("UNDOING "+changes.size()+" changes"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| for (int i = changes.size(); --i >= 0; ) { |
| Object change = changes.get(i); |
| if (change instanceof Notification) { |
| try { |
| undoNotification((Notification)change); |
| } catch (RuntimeException e) { |
| BPELUIPlugin.log(e); |
| } |
| } else if (change instanceof IUndoHandler) { |
| ((IUndoHandler)change).undo(); |
| } |
| } |
| } |
| |
| /** |
| * @see org.eclipse.bpel.ui.commands.util.IAutoUndoRecorder#redo(java.util.List) |
| */ |
| public void redo (List<Object> changes) { |
| if (VERBOSE_DEBUG) System.out.println("REDOING "+changes.size()+" changes"); //$NON-NLS-1$ //$NON-NLS-2$ |
| for(Object change : changes) { |
| if (change instanceof Notification) { |
| try { |
| redoNotification((Notification)change); |
| } catch (RuntimeException e) { |
| BPELUIPlugin.log(e); |
| } |
| } else if (change instanceof IUndoHandler) { |
| ((IUndoHandler)change).redo(); |
| } |
| } |
| } |
| |
| /** |
| * @param notifier |
| * @return the undo recorder from the adapter. |
| */ |
| |
| |
| public static IAutoUndoRecorder getFromAdapter (Notifier notifier) { |
| for(Adapter a : notifier.eAdapters()) { |
| if (a instanceof ModelAutoUndoAdapter) { |
| return ((ModelAutoUndoAdapter)a).getAutoUndoRecorder(); |
| } |
| } |
| return null; |
| } |
| } |