| /******************************************************************************* |
| * 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: |
| * koegel |
| ******************************************************************************/ |
| package org.eclipse.emf.emfstore.internal.server.model.versioning.operations.util; |
| |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.emfstore.internal.common.model.ModelElementId; |
| import org.eclipse.emf.emfstore.internal.common.model.util.ModelUtil; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AbstractOperation; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.AttributeOperation; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CompositeOperation; |
| import org.eclipse.emf.emfstore.internal.server.model.versioning.operations.CreateDeleteOperation; |
| |
| /** |
| * Canonizes a list of operations. Removes all operations that are not necessary |
| * to achieve the same result when the list of operations is applied to a |
| * project. Contract: project.apply(opList) = project.apply(cannonizedOpList) |
| * |
| * @author koegel |
| */ |
| public final class OperationsCanonizer { |
| |
| /** |
| * Private constructor. |
| */ |
| private OperationsCanonizer() { |
| // do nothing |
| } |
| |
| /** |
| * Canonize the operation list. |
| * |
| * @param operations |
| * a list of operations (the list is order by creation time) |
| */ |
| public static void canonize(List<AbstractOperation> operations) { |
| |
| try { |
| foldComposites(operations); |
| foldAttributes(operations); |
| foldAttributesIntoCreates(operations); |
| foldAttributesIntoDeletes(operations); |
| foldCreatesAndDeletes(operations); |
| |
| // BEGIN SUPRESS CATCH EXCEPTION |
| } catch (final RuntimeException e) { |
| ModelUtil.log("Runtime exception in " + OperationsCanonizer.class.getName(), e, IStatus.ERROR); //$NON-NLS-1$ |
| } |
| // END SUPRESS CATCH EXCEPTION |
| |
| } |
| |
| // neighbouring create and delete will be removed |
| private static void foldCreatesAndDeletes(List<AbstractOperation> operations) { |
| |
| for (int i = 0; i < operations.size() - 1; i++) { |
| |
| // look for a create operation |
| final AbstractOperation opLeft = operations.get(i); |
| if (!(opLeft instanceof CreateDeleteOperation)) { |
| continue; |
| } |
| final CreateDeleteOperation createOp = (CreateDeleteOperation) opLeft; |
| |
| if (createOp.isDelete()) { |
| continue; |
| } |
| |
| // ok, we got one, see if the next one is a matching delete |
| final AbstractOperation opRight = operations.get(i + 1); |
| if (!(opRight instanceof CreateDeleteOperation)) { |
| continue; |
| } |
| final CreateDeleteOperation deleteOp = (CreateDeleteOperation) opRight; |
| |
| if (!deleteOp.isDelete()) { |
| continue; |
| } |
| |
| // ok, we got a create followed by a delete, if they have matching |
| // ids, remove them |
| |
| if (createOp.getModelElementId().equals(deleteOp.getModelElementId())) { |
| // remove both |
| operations.remove(i + 1); |
| operations.remove(i); |
| i = Math.max(0, i - 2); // reexamine the preceeding index |
| |
| } |
| |
| } |
| |
| } |
| |
| private static void foldAttributesIntoCreates(List<AbstractOperation> operations) { |
| |
| // look for suitable create operation |
| for (int i = 0; i < operations.size() - 1; i++) { |
| |
| final AbstractOperation opLeft = operations.get(i); |
| if (!(opLeft instanceof CreateDeleteOperation)) { |
| continue; |
| } |
| final CreateDeleteOperation createOp = (CreateDeleteOperation) opLeft; |
| |
| if (createOp.isDelete()) { |
| continue; |
| } |
| |
| // found valid create operation, now looking for attribute |
| // operations for |
| // the new object |
| for (int j = i + 1; j < operations.size(); j++) { |
| |
| final AbstractOperation opRight = operations.get(j); |
| |
| if (opRight instanceof AttributeOperation |
| && opLeft.getModelElementId().equals(opRight.getModelElementId())) { |
| |
| final AttributeOperation attOp = (AttributeOperation) opRight; |
| // found an attribute change for the new object |
| // now merge it into the created object and discard the |
| // attribute operation |
| final EStructuralFeature feature = createOp.getModelElement().eClass() |
| .getEStructuralFeature(attOp.getFeatureName()); |
| createOp.getModelElement().eSet(feature, attOp.getNewValue()); |
| |
| operations.remove(j); // remove attribute operation |
| j--; // reexamine the index after removal |
| |
| } |
| |
| // stop if a composite operation occurs, that contains an |
| // attribute operation on created object |
| if (opRight instanceof CompositeOperation) { |
| |
| if (containsAttributeChangeTo((CompositeOperation) opRight, createOp.getModelElementId())) { |
| break; |
| } |
| } |
| } |
| } |
| } |
| |
| private static void foldAttributesIntoDeletes(List<AbstractOperation> operations) { |
| |
| // look for suitable delete operation |
| for (int i = operations.size() - 1; i > 0 && i < operations.size(); i--) { |
| |
| final AbstractOperation opRight = operations.get(i); |
| if (!(opRight instanceof CreateDeleteOperation)) { |
| continue; |
| } |
| final CreateDeleteOperation deleteOp = (CreateDeleteOperation) opRight; |
| |
| if (!deleteOp.isDelete()) { |
| continue; |
| } |
| |
| // found valid delete operation, now looking for attribute |
| // operations for |
| // the object |
| for (int j = i - 1; j >= 0; j--) { |
| |
| final AbstractOperation opLeft = operations.get(j); |
| |
| if (opLeft instanceof AttributeOperation |
| && opRight.getModelElementId().equals(opLeft.getModelElementId())) { |
| |
| final AttributeOperation attOp = (AttributeOperation) opLeft; |
| // found an attribute change for the object, that is deleted |
| // now merge it into the deleted object and discard the |
| // attribute operation |
| final EStructuralFeature feature = deleteOp.getModelElement().eClass() |
| .getEStructuralFeature(attOp.getFeatureName()); |
| deleteOp.getModelElement().eSet(feature, attOp.getOldValue()); |
| |
| operations.remove(j); // remove attribute operation |
| i--; // keep main loop consistent |
| |
| } |
| |
| // stop if a composite operation occurs, that contains an |
| // attribute operation on created object |
| if (opLeft instanceof CompositeOperation) { |
| |
| if (containsAttributeChangeTo((CompositeOperation) opLeft, deleteOp.getModelElementId())) { |
| break; |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| private static boolean containsAttributeChangeTo(CompositeOperation comp, ModelElementId modelElementId) { |
| |
| for (final AbstractOperation op : comp.getSubOperations()) { |
| |
| if (op instanceof AttributeOperation && modelElementId.equals(op.getModelElementId())) { |
| return true; |
| } |
| |
| } |
| |
| return false; |
| } |
| |
| private static void foldComposites(List<AbstractOperation> operations) { |
| |
| final List<CompositeOperation> emptyComposites = new LinkedList<CompositeOperation>(); |
| for (final AbstractOperation op : operations) { |
| |
| if (!(op instanceof CompositeOperation)) { |
| continue; |
| } |
| final CompositeOperation comp = (CompositeOperation) op; |
| |
| // safeguard preparation: if the main operation of the composite has |
| // been canonized away, |
| // the canonization is reverted. This generates more operations, |
| // but leaves the "intention" of the composite intact. |
| AbstractOperation operationCopy = null; |
| |
| if (comp.getMainOperation() != null) { |
| operationCopy = ModelUtil.clone(comp); |
| // operationCopy = ModelUtil.eObjectToString(comp); |
| } |
| |
| OperationsCanonizer.canonize(comp.getSubOperations()); |
| if (comp.getSubOperations().size() == 0) { |
| emptyComposites.add(comp); |
| } |
| // safeguard implementation: restore original composite operation if |
| // the main operation has been canonized |
| // away |
| else if (comp.getMainOperation() != null && !comp.getSubOperations().contains(comp.getMainOperation())) { |
| |
| final CompositeOperation restored = (CompositeOperation) operationCopy; |
| comp.getSubOperations().clear(); |
| comp.getSubOperations().addAll(restored.getSubOperations()); |
| comp.setMainOperation(restored.getMainOperation()); |
| } |
| } |
| operations.removeAll(emptyComposites); |
| } |
| |
| private static void foldAttributes(List<AbstractOperation> operations) { |
| |
| for (int i = 0; i < operations.size() - 1; i++) { |
| |
| final AbstractOperation opLeft = operations.get(i); |
| if (!(opLeft instanceof AttributeOperation)) { |
| continue; |
| } |
| final AttributeOperation attOpLeft = (AttributeOperation) opLeft; |
| |
| for (int j = i + 1; j < operations.size(); j++) { |
| |
| final AbstractOperation opRight = operations.get(j); |
| |
| if (opRight instanceof AttributeOperation |
| && opLeft.getModelElementId().equals(opRight.getModelElementId())) { |
| |
| final AttributeOperation attOpRight = (AttributeOperation) opRight; |
| if (attOpLeft.getFeatureName().equals(attOpRight.getFeatureName())) { |
| // merge opLeft and opRight in opLeft |
| attOpLeft.setNewValue(attOpRight.getNewValue()); |
| operations.remove(j); // remove opRight |
| j--; // reexamine the index after removal |
| } |
| |
| } |
| |
| if (opRight instanceof CreateDeleteOperation || opRight instanceof CompositeOperation) { |
| break; |
| } |
| |
| } |
| // if the remaining leftOp is a noop, remove it altogether |
| if (attOpLeft.getNewValue() == null && attOpLeft.getOldValue() == null |
| || attOpLeft.getNewValue() != null && attOpLeft.getNewValue().equals(attOpLeft.getOldValue())) { |
| operations.remove(i); |
| i--; // reexamine the index after removal |
| } |
| |
| } |
| |
| } |
| } |