package com.sap.runlet.abstractinterpreter.operationaltransformation; import org.eclipse.emf.ecore.EObject; import com.sap.runlet.abstractinterpreter.Side; import com.sap.runlet.abstractinterpreter.objects.EntityObject; import com.sap.runlet.abstractinterpreter.repository.ChangeSetImpl; import com.sap.runlet.abstractinterpreter.repository.EntityDeletion; import com.sap.runlet.abstractinterpreter.repository.LinkChange; import com.sap.runlet.abstractinterpreter.repository.LinkCreation; import com.sap.runlet.abstractinterpreter.repository.LinkDeletion; import com.sap.runlet.abstractinterpreter.repository.RepositoryChange; import com.sap.runlet.abstractinterpreter.util.Tuple.Pair; /** * Takes two {@link Change} objects, one from the client, the other from the * server, and produces two dual changes for the respective other side. * * @author Axel Uhl D043530 * */ public class Transformer<LinkMetaObject extends EObject, LinkEndMetaObject extends EObject, MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage> { private enum Direction { TO_SERVER, TO_CLIENT }; /** * The transformation works such that applying * <tt>transform(c, s).getA()</tt> after <tt>c</tt> leads to the same result * as applying <tt>transform(c, s).getB()</tt> after <tt>s</tt>. */ public Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> transform( Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clientChange, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> serverChange) { if (clientChange.totalSize() == 0 || serverChange.totalSize() == 0) { return new Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(serverChange, clientChange); } else if (clientChange.totalSize() == 1 && serverChange.totalSize() == 1) { return transform(clientChange.iterator().next(), serverChange.iterator().next()); } else { return ot(clientChange, serverChange); } } /** * Perform an operational transformation over the two change lists of which at least * one contains more than one element. */ @SuppressWarnings("unchecked") private Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> ot( Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clientChange, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> serverChange) { int totalClientChanges = clientChange.totalSize(); int totalServerChanges = serverChange.totalSize(); // the clientChanges' last "row" becomes the list of changes to apply to the server and // will be wrapped and returned as the second element of the pair returned Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[][] clientChanges = (Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[][]) new Change<?, ?, ?, ?, ?>[totalServerChanges+1][totalClientChanges]; // the serverChanges' last "row" becomes the list of changes to apply to the client and // will be wrapped and returned as the first element of the pair returned Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[][] serverChanges = (Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[][]) new Change<?, ?, ?, ?, ?>[totalClientChanges+1][totalServerChanges]; // initialize the edges of the trellis int i1=0; for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> singleClientChange : clientChange) { clientChanges[0][i1++] = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(singleClientChange); } i1=0; for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> singleServerChange : serverChange) { serverChanges[0][i1++] = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(singleServerChange); } // Now populate the tellis inside for (int i=0; i<totalClientChanges; i++) { for (int j=0; j<totalServerChanges; j++) { Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> stepResult = transform(clientChanges[j][i], serverChanges[i][j]); clientChanges[j+1][i] = stepResult.getB(); // assign server change into clientChanges, see above serverChanges[i+1][j] = stepResult.getA(); // assign client change into serverChanges, see above } } return new Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>( new ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(serverChanges[totalClientChanges]), new ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(clientChanges[totalServerChanges])); } private Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> transform( RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> client, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> server) { return new Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>( transformSourceChangeForTarget(server, client, Direction.TO_CLIENT), transformSourceChangeForTarget(client, server, Direction.TO_SERVER)); } /** * Takes <tt>change</tt> from the "source" side and checks if any * transformation needs to happen in order to apply the semantically same * change on the "target" side, based on what changes have happend on the * target side concurrently as described by <tt>appliedTargetChangeSet</tt>. * The result of the transformation is appended to <tt>result</tt> and to * <tt>targetChangesPlusTransformedSourceChanges</tt>. The latter is * important because this combined "target" side change set is used to track * ordered link movements on the "target" side in order to be able to * compute the transformations properly. * * @param toServer * if <tt>true</tt>, the "target" side is the server side; * otherwise, the "target" side is the client side. */ private Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transformSourceChangeForTarget(RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> sourceChange, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> targetChange, Direction direction) { Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result; if (sourceChange.isEntityChange()) { if (sourceChange.isCreate()) { // entity create can simply be appended on target side result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } else { // entity delete result = transformEntityDeletion((EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) sourceChange, targetChange); } } else { // link change if (sourceChange.isCreate()) { result = transformLinkCreation((LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) sourceChange, targetChange, direction); } else { result = transformLinkDeletion((LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) sourceChange, targetChange); } } return result; } /** * A link deletion is only applied on the "target" side if none of the * entities to which it connects has been deleted by the * <tt>targetChange</tt> and in case the association is unique, <tt>targetChange</tt> * is not a redundant deletion for the same link on the "target" * side. (Note, that link deletions are implied by entity deletions for all * links connected to that entity known at the time of the entitiy * deletion.) * <p> */ private Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transformLinkDeletion( LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> sourceChange, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> targetChange) { Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result; if (targetChange.isEntityChange() && targetChange.isDelete() && (((EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject().get(Side.LEFT)) || ((EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject().get(Side.RIGHT)))) { // entity was deleted; link deletion would be redundant result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else if (sourceChange.getObject().isUnique() && targetChange.isLinkChange() && targetChange.isDelete() && ((LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject()) && ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition() == sourceChange.getPosition()) { // redundant link deletion result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else if (sourceChange.getObject().isOrdered() && targetChange.isLinkChange() && ChangeSetImpl.areLinksFromSameOrderedEntitySequence(sourceChange, (LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange)) { if (targetChange.isCreate()) { if (sourceChange.getPosition() >= ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition()) { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(new LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange.getObject(), sourceChange.getPosition()+1)); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } else { // targetChange.isDelete() if (sourceChange.getPosition() > ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition()) { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( new LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange.getObject(), sourceChange.getPosition()-1)); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } return result; } /** * A link creation is only applied on the "target" side if none of the * entities to which it connects is deleted by the <tt>targetChange</tt> and * in the case of a unique association <tt>targetChange</tt> is not a * redundant link creation. In case of a unique ordered association, if * <tt>targetChange</tt> is an otherwise equal link created at a different * position, the server side's position wins. Therefore, if the direction is * <tt>TO_SERVER/tt>, the client's {@link LinkCreation} is skipped * whereas in the <tt>TO_CLIENT</tt> case, the server's different insert * posiiton is established on the client by adding a {@link LinkDeletion} at * the client's ("target") create position, followed by a * {@link LinkCreation} at the position to where the server's ("source") * original create position has shifted on the client ("target") in the * meantime. * <p> * * If the "source" link will be created on the "target" side and the * association is ordered, and the <tt>targetChange</tt> is also a link * creation for the same association with the same object at the unordered * end, the link creation position shifts by one if the insertion is after * (for <tt>TO_SERVER</tt>) or after or at (<tt>TO_CLIENT</tt>) the * <tt>targetChange</tt> position. */ @SuppressWarnings("unchecked") private Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transformLinkCreation(LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> sourceChange, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> targetChange, Direction direction) { Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result; if (targetChange.isEntityChange() && targetChange.isDelete() && (((EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject().get(Side.LEFT)) || ((EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject().get(Side.RIGHT)))) { result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else if (sourceChange.getObject().isUnique() && targetChange.isLinkChange() && targetChange.isCreate() && ((LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject().equals(sourceChange.getObject())) { if (((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition() == sourceChange.getPosition()) { // completely redundant creation result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else { // different positions; this implies that the association is ordered (otherwise both would be null) if (direction == Direction.TO_SERVER) { // server position wins; no change on server result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else { // client position needs to be adjusted by sending a delete and create at server's position result = new ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( (Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[]) new Change<?, ?, ?, ?, ?>[] { new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( new LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(((LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getObject(), ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition())), new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( sourceChange) }); } } } else if (sourceChange.getObject().isOrdered() && targetChange.isLinkChange() && ChangeSetImpl.areLinksFromSameOrderedEntitySequence(sourceChange, (LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange)) { if (targetChange.isCreate()) { if (sourceChange.getPosition() > ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition() || direction == Direction.TO_SERVER && sourceChange.getPosition() == ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition()) { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( new LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( sourceChange.getObject(), sourceChange.getPosition()+1)); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } else { // targetChange.isDelete() if (sourceChange.getPosition() > ((LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange).getPosition()) { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( new LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( sourceChange.getObject(), sourceChange.getPosition()-1)); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } return result; } /** * For <tt>targetChange</tt> being a {@link LinkCreation} that has the * entity deleted by <tt>sourceChange</tt> on either side, a corresponding * {@link LinkDeletion} is added to the result before the original * <tt>sourceChange</tt>. If <tt>targetChange</tt> is not a redundant entity * deletion, the <tt>sourceChange</tt> is taken as the <tt>result</tt>. */ @SuppressWarnings("unchecked") private Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transformEntityDeletion(EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> sourceChange, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> targetChange) { Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result; EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> deletedEntity = sourceChange.getObject(); if (targetChange.isLinkChange() && targetChange.isCreate()) { LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc = (LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange; if (lc.getObject().get(Side.LEFT).equals(deletedEntity) || lc.getObject().get(Side.RIGHT).equals(deletedEntity)) { // produce LinkDeletion followed by original entity deletion as result result = new ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( (Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>[]) new Change<?, ?, ?, ?, ?>[] { new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( new LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(lc.getObject(), lc.getPosition())), new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange)}); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } else if (targetChange.isEntityChange() && targetChange.isDelete()) { // if redundant delete, produce empty change for target EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> ed = (EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) targetChange; if (ed.getObject().equals(deletedEntity)) { result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(); } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } } else { result = new SingleChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(sourceChange); } return result; } }