blob: af9a94d6b2bfbcfad44579912af29b8c1477f984 [file] [log] [blame]
/*******************************************************************************
* 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
}
}
}
}