/******************************************************************************* * Copyright (c) 2008-2011 Chair for Applied Software Engineering, * Technische Universitaet Muenchen. * 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: ******************************************************************************/ package org.eclipse.emf.emfstore.client.model.impl; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.Platform; import org.eclipse.emf.common.command.Command; import org.eclipse.emf.common.command.CommandStack; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature.Setting; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.emf.ecore.util.EcoreUtil.Copier; import org.eclipse.emf.edit.domain.EditingDomain; import org.eclipse.emf.emfstore.client.model.CompositeOperationHandle; import org.eclipse.emf.emfstore.client.model.Configuration; import org.eclipse.emf.emfstore.client.model.WorkspaceManager; import org.eclipse.emf.emfstore.client.model.changeTracking.NotificationToOperationConverter; import org.eclipse.emf.emfstore.client.model.changeTracking.commands.CommandObserver; import org.eclipse.emf.emfstore.client.model.changeTracking.commands.EMFStoreCommandStack; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.NotificationInfo; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.filter.FilterStack; import org.eclipse.emf.emfstore.client.model.changeTracking.notification.recording.NotificationRecorder; import org.eclipse.emf.emfstore.client.model.util.WorkspaceUtil; import org.eclipse.emf.emfstore.common.CommonUtil; import org.eclipse.emf.emfstore.common.model.IdEObjectCollection; import org.eclipse.emf.emfstore.common.model.ModelElementId; import org.eclipse.emf.emfstore.common.model.util.EObjectChangeNotifier; import org.eclipse.emf.emfstore.common.model.util.IdEObjectCollectionChangeObserver; import org.eclipse.emf.emfstore.common.model.util.ModelUtil; import org.eclipse.emf.emfstore.common.model.util.SettingWithReferencedElement; import org.eclipse.emf.emfstore.common.observer.PostCreationObserver; import org.eclipse.emf.emfstore.server.model.versioning.operations.AbstractOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.CompositeOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.CreateDeleteOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.MultiReferenceOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.OperationsFactory; import org.eclipse.emf.emfstore.server.model.versioning.operations.ReferenceOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.SingleReferenceOperation; import org.eclipse.emf.emfstore.server.model.versioning.operations.impl.CreateDeleteOperationImpl; import org.eclipse.emf.emfstore.server.model.versioning.operations.semantic.SemanticCompositeOperation; /** * Tracks changes on any given {@link IdEObjectCollection}. * * @author koegel * @author emueller */ public class OperationRecorder implements CommandObserver, IdEObjectCollectionChangeObserver { /** * Name of unknown creator. */ public static final String UNKOWN_CREATOR = "unknown"; private EMFStoreCommandStack emfStoreCommandStack; private int currentOperationListSize; private EditingDomain editingDomain; private Set<EObject> currentClipboard; private List<AbstractOperation> operations; private List<EObject> removedElements; private NotificationToOperationConverter converter; private boolean commandIsRunning; private NotificationRecorder notificationRecorder; private boolean isRecording; private IdEObjectCollection rootEObject; private CompositeOperation compositeOperation; private List<OperationRecorderListener> operationRecordedListeners; private EObjectChangeNotifier changeNotifier; private boolean checkForIncomingCrossReferences; private boolean cutOffIncomingCrossReferences; private boolean emitOperationsWhenCommandCompleted; /** * Constructor. * * @param projectSpace * the project space the change tracker is operating on. */ public OperationRecorder(IdEObjectCollection rootEObject, EObjectChangeNotifier changeNotifier) { // projectSpace) { // this.projectSpace = projectSpace; // this.isRecording = false; this.rootEObject = rootEObject; this.changeNotifier = changeNotifier; this.operations = new ArrayList<AbstractOperation>(); operationRecordedListeners = new ArrayList<OperationRecorderListener>(); editingDomain = Configuration.getEditingDomain(); CommandStack commandStack = editingDomain.getCommandStack(); // operations = new ArrayList<AbstractOperation>(); if (commandStack instanceof EMFStoreCommandStack) { emfStoreCommandStack = (EMFStoreCommandStack) commandStack; emfStoreCommandStack.addCommandStackObserver(this); } else { throw new IllegalStateException("Setup of ResourceSet is invalid, there is no EMFStoreCommandStack!"); } removedElements = new ArrayList<EObject>(); converter = new NotificationToOperationConverter(rootEObject); // explicitly disable checks for cross-references checkForIncomingCrossReferences = false; cutOffIncomingCrossReferences = false; IConfigurationElement[] elements = Platform.getExtensionRegistry().getConfigurationElementsFor( "org.eclipse.emf.emfstore.client.recording.options"); if (elements != null && elements.length > 0) { checkForIncomingCrossReferences = Boolean.parseBoolean(elements[0] .getAttribute("checkForIncomingCrossReferences")); cutOffIncomingCrossReferences = Boolean.parseBoolean(elements[0] .getAttribute("cutOffIncomingCrossReferences")); } emitOperationsWhenCommandCompleted = true; } public void clearOperations() { operations.clear(); } // TODO: EM, remove method public EObjectChangeNotifier getChangeNotifier() { return changeNotifier; } public IdEObjectCollection getCollection() { return rootEObject; } /** * @return the removedElements */ public List<EObject> getRemovedElements() { return removedElements; } private Set<EObject> getModelElementsFromClipboard() { Set<EObject> result = new HashSet<EObject>(); if (editingDomain == null) { return result; } Collection<Object> clipboard = editingDomain.getClipboard(); if (clipboard == null) { return result; } for (Object element : clipboard) { if (element instanceof EObject) { result.add((EObject) element); } } return result; } public void modelElementAdded(IdEObjectCollection project, EObject modelElement) { // if element was just pasted from clipboard then do nothing if (this.getModelElementsFromClipboard().contains(modelElement)) { return; } if (isRecording) { // notify Post Creation Listeners with change tracking switched off since only attribute changes are allowd stopChangeRecording(); WorkspaceManager.getObserverBus().notify(PostCreationObserver.class).onCreation(modelElement); startChangeRecording(); Set<EObject> allModelElements = new HashSet<EObject>(); allModelElements.add(modelElement); allModelElements.addAll(ModelUtil.getAllContainedModelElements(modelElement, false)); // collect in- and out-going cross-reference for containment tree of modelElement List<SettingWithReferencedElement> crossReferences = ModelUtil.collectOutgoingCrossReferences(rootEObject, allModelElements); List<SettingWithReferencedElement> ingoingCrossReferences = collectIngoingCrossReferences(rootEObject, allModelElements); crossReferences.addAll(ingoingCrossReferences); // TODO: check if all cross-references are cut on copy CreateDeleteOperation createDeleteOperation = createCreateDeleteOperation(modelElement, false); // collect recorded operations and add to create operation List<ReferenceOperation> recordedOperations = generateCrossReferenceOperations(crossReferences); createDeleteOperation.getSubOperations().addAll(recordedOperations); if (this.compositeOperation != null) { compositeOperation.getSubOperations().add(createDeleteOperation); } else { if (commandIsRunning && emitOperationsWhenCommandCompleted) { operations.add(createDeleteOperation); } else { operationRecorded(createDeleteOperation); } } } } public static List<SettingWithReferencedElement> collectIngoingCrossReferences(IdEObjectCollection collection, Set<EObject> allModelElements) { List<SettingWithReferencedElement> settings = new ArrayList<SettingWithReferencedElement>(); for (EObject modelElement : allModelElements) { Collection<Setting> inverseReferences = WorkspaceManager.getInstance().findInverseCrossReferences( modelElement); for (Setting setting : inverseReferences) { if (!ModelUtil.shouldBeCollected(collection, allModelElements, setting.getEObject())) { continue; } EReference reference = (EReference) setting.getEStructuralFeature(); EClassifier eType = reference.getEType(); if (reference.isContainer() || reference.isContainment() || !reference.isChangeable() || (!(eType instanceof EClass))) { continue; } SettingWithReferencedElement settingWithReferencedElement = new SettingWithReferencedElement(setting, modelElement); settings.add(settingWithReferencedElement); } } return settings; } private List<ReferenceOperation> generateCrossReferenceOperations( Collection<SettingWithReferencedElement> crossReferences) { List<ReferenceOperation> result = new ArrayList<ReferenceOperation>(); for (SettingWithReferencedElement setting : crossReferences) { EObject referencedElement = setting.getReferencedElement(); // fetch ID of referenced element ModelElementId newModelElementId = rootEObject.getModelElementId(referencedElement); if (newModelElementId == null) { newModelElementId = rootEObject.getDeletedModelElementId(referencedElement); } EObject eObject = setting.getSetting().getEObject(); EReference reference = (EReference) setting.getSetting().getEStructuralFeature(); if (setting.getSetting().getEStructuralFeature().isMany()) { int position = ((List<EObject>) eObject.eGet(reference)).indexOf(referencedElement); MultiReferenceOperation multiRefOp = NotificationToOperationConverter.createMultiReferenceOperation( rootEObject, eObject, reference, Arrays.asList(referencedElement), true, position); result.add(multiRefOp); } else { SingleReferenceOperation singleRefOp = NotificationToOperationConverter.createSingleReferenceOperation( rootEObject, null, newModelElementId, reference, eObject); result.add(singleRefOp); } } return result; } private void operationsRecorded(List<? extends AbstractOperation> operations) { for (OperationRecorderListener listener : operationRecordedListeners) { listener.operationsRecorded(operations); } } public void addOperationRecorderListener(OperationRecorderListener listener) { operationRecordedListeners.add(listener); } public void removeOperationRecorderListener(OperationRecorderListener listener) { operationRecordedListeners.remove(listener); } /** * Starts change recording on this workspace, resumes previous recordings if * there are any. * * @generated NOT */ public void startChangeRecording() { if (notificationRecorder == null) { notificationRecorder = new NotificationRecorder(); } isRecording = true; } /** * * * @param notificationDisabled * the notificationDisabled to set */ public void disableNotifications(boolean notificationDisabled) { if (this.changeNotifier != null) { this.changeNotifier.disableNotifications(notificationDisabled); } } /** * Stops current recording of changes and adds recorded changes to this * project spaces changes. * * @generated NOT */ public void stopChangeRecording() { this.isRecording = false; } private List<AbstractOperation> recordingFinished() { // create operations from "valid" notifications, log invalid ones, // accumulate the ops List<AbstractOperation> ops = new LinkedList<AbstractOperation>(); List<NotificationInfo> rec = notificationRecorder.getRecording().asMutableList(); for (NotificationInfo n : rec) { if (!n.isValid()) { WorkspaceUtil.log("INVALID NOTIFICATION MESSAGE DETECTED: " + n.getValidationMessage(), null, 0); continue; } else { AbstractOperation op = converter.convert(n); if (op != null) { ops.add(op); } else { // we should never get here, this would indicate a // consistency error, // n.isValid() should have been false WorkspaceUtil.log("INVALID NOTIFICATION CLASSIFICATION," + " notification is valid, but cannot be converted to an operation: " + n.toString(), null, 0); continue; } } } return ops; } /** * Returns the notification recorder of the project space. * * @return the notification recorder */ public NotificationRecorder getNotificationRecorder() { return notificationRecorder; } /** * Create a CreateDeleteOperation. * * @param modelElement * the model element to delete or create * @param delete * whether the element is deleted or created * @return the operation */ private CreateDeleteOperation createCreateDeleteOperation(EObject modelElement, boolean delete) { CreateDeleteOperation createDeleteOperation = OperationsFactory.eINSTANCE.createCreateDeleteOperation(); createDeleteOperation.setDelete(delete); EObject element = modelElement; List<EObject> allContainedModelElements = ModelUtil.getAllContainedModelElementsAsList(element, false); allContainedModelElements.add(element); Copier copier = new Copier(true, false); EObject copiedElement = copier.copy(element); copier.copyReferences(); List<EObject> copiedAllContainedModelElements = ModelUtil.getAllContainedModelElementsAsList(copiedElement, false); copiedAllContainedModelElements.add(copiedElement); for (int i = 0; i < allContainedModelElements.size(); i++) { EObject child = allContainedModelElements.get(i); if (ModelUtil.isIgnoredDatatype(child)) { continue; } EObject copiedChild = copiedAllContainedModelElements.get(i); ModelElementId childId = rootEObject.getModelElementId(child); ((CreateDeleteOperationImpl) createDeleteOperation).getEObjectToIdMap().put(copiedChild, childId); } createDeleteOperation.setModelElement(copiedElement); createDeleteOperation.setModelElementId(rootEObject.getModelElementId(modelElement)); createDeleteOperation.setClientDate(new Date()); return createDeleteOperation; } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.common.model.util.ProjectChangeObserver#modelElementRemoved(org.eclipse.emf.emfstore.common.model.Project, * org.eclipse.emf.emfstore.common.model.ModelElement) */ public void modelElementRemoved(IdEObjectCollection project, EObject modelElement) { if (isRecording) { removedElements.add(modelElement); } } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.client.model.changeTracking.commands.CommandObserver#commandCompleted(org.eclipse.emf.common.command.Command) */ public void commandCompleted(Command command) { // means that we have not seen a command start yet // if (currentClipboard == null) { // return; // } List<EObject> deletedElements = new ArrayList<EObject>(); for (int i = removedElements.size() - 1; i >= 0; i--) { EObject removedElement = removedElements.get(i); if (!rootEObject.containsInstance(removedElement)) { if (!deletedElements.contains(removedElement)) { deletedElements.add(0, removedElement); } } } Set<EObject> newElementsOnClipboardAfterCommand = getModelElementsFromClipboard(); // newElementsOnClipboardAfterCommand.removeAll(currentClipboard); // handle deleted elements => cut command for (EObject deletedElement : deletedElements) { if (newElementsOnClipboardAfterCommand.contains(deletedElement)) { // TODO: EM, where to put cut elements? // element was cut // projectSpace.getProject().getCutElements().add(deletedElement); } else { // element was deleted handleElementDelete(deletedElement); // cleanResources(deletedElement); } } operationsRecorded(operations); removedElements.clear(); operations.clear(); commandIsRunning = false; // remove all deleted elements newElementsOnClipboardAfterCommand.removeAll(deletedElements); } private void deleteOutgoingCrossReferencesOfContainmentTree(EObject modelElement) { deleteOutgoingCrossReferences(modelElement); for (EObject child : ModelUtil.getAllContainedModelElements(modelElement, false)) { deleteOutgoingCrossReferences(child); } } private void deleteOutgoingCrossReferences(EObject modelElement) { // delete all non containment cross references to other elments for (EReference reference : modelElement.eClass().getEAllReferences()) { EClassifier eType = reference.getEType(); // if the reference is a containment map feature and its referenced entries do have at least one // non-containment reference (e.g. key or value) // then delete the map entries // instead of waiting for the referenced element to be cut of from the map entry // in the children recursion // since cutting of a key or value reference will render the map into an invalid state which can result in // unresolved proxies. if (Map.Entry.class.isAssignableFrom(eType.getInstanceClass()) && reference.isContainment() && reference.isChangeable() && eType instanceof EClass) { EClass mapEntryEClass = (EClass) eType; if (hasOnlyContainmentReferences(mapEntryEClass)) { modelElement.eUnset(reference); continue; } @SuppressWarnings("unchecked") List<EObject> mapEntriesEList = (List<EObject>) modelElement.eGet(reference); // copy list before clearing reference // TODO is this really the underlying list List<EObject> mapEntries = new ArrayList<EObject>(mapEntriesEList); Resource resource = this.editingDomain.getResourceSet().createResource(URI.createURI("AAA")); resource.getContents().add(modelElement); EcoreUtil.resolveAll(modelElement); modelElement.eUnset(reference); for (EObject mapEntry : mapEntries) { handleElementDelete(mapEntry); } continue; } if (reference.isContainer() || reference.isContainment() || !reference.isChangeable()) { continue; } if (eType instanceof EClass) { modelElement.eUnset(reference); } } } private boolean hasOnlyContainmentReferences(EClass mapEntryEClass) { for (EReference mapEntryReference : mapEntryEClass.getEAllReferences()) { if (!mapEntryReference.isContainment()) { return false; } } return true; } private void handleElementDelete(EObject deletedElement) { deleteOutgoingCrossReferencesOfContainmentTree(deletedElement); if (!CommonUtil.isSelfContained(deletedElement, true)) { throw new IllegalStateException( "Element was removed from containment of project but still has cross references!: " + rootEObject.getDeletedModelElementId(deletedElement).getId()); // TODO: EM, remove project cast, if possible } else if (checkForIncomingCrossReferences) { // delete incoming cross references Collection<Setting> inverseReferences = WorkspaceManager.getInstance().findInverseCrossReferences( deletedElement); ModelUtil.deleteIncomingCrossReferencesFromParent(inverseReferences, deletedElement); // ModelUtil.handleIncomingCrossReferences(deletedElement, (Project) rootEObject, // cutOffIncomingCrossReferences); } if (!isRecording) { return; } CreateDeleteOperation deleteOperation = OperationsFactory.eINSTANCE.createCreateDeleteOperation(); deleteOperation.setClientDate(new Date()); List<EObject> allContainedModelElements = ModelUtil.getAllContainedModelElementsAsList(deletedElement, false); allContainedModelElements.add(deletedElement); EObject copiedElement = EcoreUtil.copy(deletedElement); deleteOperation.setModelElement(copiedElement); deleteOperation.setModelElementId(rootEObject.getDeletedModelElementId(deletedElement)); List<EObject> copiedAllContainedModelElements = ModelUtil.getAllContainedModelElementsAsList(copiedElement, false); copiedAllContainedModelElements.add(copiedElement); for (int i = 0; i < allContainedModelElements.size(); i++) { EObject child = allContainedModelElements.get(i); EObject copiedChild = copiedAllContainedModelElements.get(i); ModelElementId childId = rootEObject.getDeletedModelElementId(child); ((CreateDeleteOperationImpl) deleteOperation).getEObjectToIdMap().put(copiedChild, childId); } deleteOperation.setDelete(true); List<CompositeOperation> compositeOperationsToDelete = new ArrayList<CompositeOperation>(); deleteOperation.getSubOperations().addAll( extractReferenceOperationsForDelete(deletedElement, compositeOperationsToDelete)); operations.removeAll(compositeOperationsToDelete); if (this.compositeOperation != null) { this.compositeOperation.getSubOperations().add(deleteOperation); // TODO: EM, save resource // projectSpace.saveResource(compositeOperation.eResource()); try { compositeOperation.eResource().save(null); } catch (IOException e) { e.printStackTrace(); } } else { if (commandIsRunning) { operations.add(deleteOperation); } else { operationRecorded(deleteOperation); } } // remove deleted model element and children from resource // ModelUtil.removeModelElementAndChildrenFromResource(deletedElement); } @SuppressWarnings("unchecked") private List<ReferenceOperation> extractReferenceOperationsForDelete(EObject deletedElement, List<CompositeOperation> compositeOperationsToDelete) { Set<ModelElementId> allDeletedElementsIds = new HashSet<ModelElementId>(); for (EObject child : ModelUtil.getAllContainedModelElements(deletedElement, false)) { ModelElementId childId = rootEObject.getDeletedModelElementId(child); allDeletedElementsIds.add(childId); } allDeletedElementsIds.add(rootEObject.getDeletedModelElementId(deletedElement)); List<ReferenceOperation> referenceOperationsForDelete = new ArrayList<ReferenceOperation>(); // if (currentOperationListSize >= operations.size()) { // return referenceOperationsForDelete; // } List<AbstractOperation> newOperations = operations.subList(0, operations.size()); List<AbstractOperation> l = new ArrayList<AbstractOperation>(); for (int i = newOperations.size() - 1; i >= 0; i--) { AbstractOperation operation = newOperations.get(i); if (belongsToDelete(operation, allDeletedElementsIds)) { referenceOperationsForDelete.add(0, (ReferenceOperation) operation); l.add(operation); continue; } if (operation instanceof CompositeOperation && ((CompositeOperation) operation).getMainOperation() != null) { CompositeOperation compositeOperation = (CompositeOperation) operation; boolean doesNotBelongToDelete = false; for (AbstractOperation subOperation : compositeOperation.getSubOperations()) { if (!belongsToDelete(subOperation, allDeletedElementsIds)) { doesNotBelongToDelete = true; break; } } if (!doesNotBelongToDelete) { referenceOperationsForDelete.addAll(0, (Collection<? extends ReferenceOperation>) compositeOperation.getSubOperations()); compositeOperationsToDelete.add(compositeOperation); } continue; } break; } operations.removeAll(l); return referenceOperationsForDelete; } private boolean belongsToDelete(AbstractOperation operation, Set<ModelElementId> allDeletedElementsIds) { if (operation instanceof ReferenceOperation) { ReferenceOperation referenceOperation = (ReferenceOperation) operation; Set<ModelElementId> allInvolvedModelElements = referenceOperation.getAllInvolvedModelElements(); if (allInvolvedModelElements.removeAll(allDeletedElementsIds)) { return isDestructorReferenceOperation(referenceOperation); } } return false; } private boolean isDestructorReferenceOperation(ReferenceOperation referenceOperation) { if (referenceOperation instanceof MultiReferenceOperation) { MultiReferenceOperation multiReferenceOperation = (MultiReferenceOperation) referenceOperation; return !multiReferenceOperation.isAdd(); } else if (referenceOperation instanceof SingleReferenceOperation) { SingleReferenceOperation singleReferenceOperation = (SingleReferenceOperation) referenceOperation; return singleReferenceOperation.getOldValue() != null && singleReferenceOperation.getNewValue() == null; } return false; } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.client.model.changeTracking.commands.CommandObserver#commandFailed(org.eclipse.emf.common.command.Command, * org.eclipse.core.runtime.OperationCanceledException) */ public void commandFailed(Command command, Exception exception) { // this is a backup in order to remove obsolete operations. In most // (all?) cases though, the rollback of the // transaction does this. if (compositeOperation != null) { for (int i = compositeOperation.getSubOperations().size() - 1; i >= currentOperationListSize; i--) { compositeOperation.getSubOperations().remove(i); } } } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.client.model.changeTracking.commands.CommandObserver#commandStarted(org.eclipse.emf.common.command.Command) */ public void commandStarted(Command command) { // if (compositeOperation == null) { currentOperationListSize = 0; // } else { // currentOperationListSize = compositeOperation.getSubOperations() // .size(); // } currentClipboard = getModelElementsFromClipboard(); commandIsRunning = true; } public CompositeOperation getCompositeOperation() { return compositeOperation; } public CompositeOperationHandle beginCompositeOperation() { if (compositeOperation != null) { throw new IllegalStateException("Can only have one composite at once!"); } compositeOperation = OperationsFactory.eINSTANCE.createCompositeOperation(); CompositeOperationHandle handle = new CompositeOperationHandle(this, compositeOperation); notificationRecorder.newRecording(); operations.add(compositeOperation); // currentOperationListSize++; return handle; } /** * Replace and complete the current composite operation. * * @param semanticCompositeOperation * the semantic operation that replaces the composite operation */ public void endCompositeOperation(SemanticCompositeOperation semanticCompositeOperation) { // operations.remove(operations.size() - 1); // operations.add(semanticCompositeOperation); compositeOperation = semanticCompositeOperation; endCompositeOperation(); } /** * Complete the current composite operation. */ // TODO: EM, remove method public void endCompositeOperation() { // operationRecorded(compositeOperation); // projectSpace.notifyOperationExecuted(compositeOperation); // if (commandIsRunning) { // operations.add(compositeOperation); // } else { // operationRecorded(compositeOperation); // } // operations.remove(compositeOperation); this.compositeOperation = null; } /** * Aborts the current composite operation. */ public void abortCompositeOperation() { if (operations.size() > 0) { AbstractOperation lastOp = operations.get(operations.size() - 1); stopChangeRecording(); try { lastOp.reverse().apply(getCollection()); operations.remove(operations.size() - 1); } finally { startChangeRecording(); } this.removedElements.clear(); } notificationRecorder.stopRecording(); compositeOperation = null; currentOperationListSize = operations.size(); removedElements.clear(); } // TODO: EM, update dirty state // updateDirtyState(); public void notify(Notification notification, IdEObjectCollection collection, EObject modelElement) { // filter unwanted notifications if (FilterStack.DEFAULT.check(new NotificationInfo(notification))) { return; } // save(modelElement); if (isRecording) { notificationRecorder.record(notification); } if (notificationRecorder.isRecordingComplete()) { if (isRecording) { List<AbstractOperation> ops = recordingFinished(); // add resulting operations as suboperations to composite or // top-level // operations if (compositeOperation != null) { compositeOperation.getSubOperations().addAll(ops); // FIXME: ugly hack for recording of create operation cross // references if (compositeOperation.eResource() != null) { try { compositeOperation.eResource().save(Configuration.getResourceSaveOptions()); } catch (IOException e) { // TODO: EM, handle exception e.printStackTrace(); } } } else { if (ops.size() > 1) { CompositeOperation op = OperationsFactory.eINSTANCE.createCompositeOperation(); op.getSubOperations().addAll(ops); // set the last operation as the main one for natural // composites op.setMainOperation(ops.get(ops.size() - 1)); op.setModelElementId((ModelElementId) EcoreUtil.copy(op.getMainOperation().getModelElementId())); if (commandIsRunning && emitOperationsWhenCommandCompleted) { operations.add(op); } else { operationRecorded(op); } } else if (ops.size() == 1) { if (commandIsRunning && emitOperationsWhenCommandCompleted) { operations.add(ops.get(0)); } else { operationRecorded(ops.get(0)); } } } } // if (!commandIsRunning) { // saveDirtyResources(); // } } } private void operationRecorded(AbstractOperation op) { operationsRecorded(Arrays.asList(op)); } /** * {@inheritDoc} * * @see org.eclipse.emf.emfstore.common.model.util.IdEObjectCollectionChangeObserver#collectionDeleted(org.eclipse.emf.emfstore.common.model.IdEObjectCollection) */ public void collectionDeleted(IdEObjectCollection collection) { // do nothing } public void setEmitOperationsWhenCommandCompleted(boolean emitOperationsImmediately) { this.emitOperationsWhenCommandCompleted = emitOperationsImmediately; } }