package com.sap.runlet.abstractinterpreter.repository; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import org.eclipse.emf.ecore.EObject; import com.sap.runlet.abstractinterpreter.objects.ClassTypedObject; import com.sap.runlet.abstractinterpreter.objects.EntityObject; import com.sap.runlet.abstractinterpreter.objects.Link; import com.sap.runlet.abstractinterpreter.operationaltransformation.Change; import com.sap.runlet.abstractinterpreter.util.ClassFilterIterator; import com.sap.runlet.abstractinterpreter.util.ModelAdapter; import com.sap.runlet.abstractinterpreter.util.Tuple.Pair; /** * Describes a set of changes to data. This can include creation and deletion of * entity objects as well as creation and deletion of links, both subsumed by * the {@link RepositoryObject} interface. For links of an ordered association * an insert / delete position must be specified. * <p> * * When links or objects are announced for creation or deletion and this change * set already contains the inverse operation for the link/object, then instead * of adding a new change the inverse change will be removed from this change * set. For links of an ordered association this means that the positional * changes of such links have to be tracked with every modification that occurs * "before" a link because only then the "inverse" of an operation can be * recognized. * <p> * * If, for example, <tt>l1</tt> and <tt>l2</tt> are links for the same ordered, * non-unique association, then * * <pre> * objectCreated(l1, 0) * objectCreated(l2, 0) --> l2, l1 * objectCreated(l2, 1) --> l2, l2, l1 * objectCreated(l1, 1) --> l2, l1, l2, l1 * objectDeleted(l1, 3) --> l2, l1, l2 * </pre> * * should produce the link sequence <tt>l2, l1, l2</tt> because the second * occurrence in the list (which was the first to be inserted) should be deleted * by <tt>objectDeleted(l1, 3)</tt>. This should leave the change sequence as * follows: * * <pre> * objectCreated(l2, 0) --> l2 * objectCreated(l2, 1) --> l2, l2 * objectCreated(l1, 1) --> l2, l1, l2 * </pre> * * Note, that in case of unique associations less effort needs to be spent * tracking the positions because inserts will be re-played in the order in * which they occurred, and deletion can delete the only element equal to the * one to be deleted, regardless of its position. * <p> * * Furthermore, the order in which events occurred is preserved. This is * particularly important for later replay regarding the order of creations and * deletions as well as the order and positions at which links have been * inserted to or deleted from an (ordered) association. * <p> * * If an entity is already marked as created or deleted, a second request to * mark it the same way is ignored. For links, redundant mark requests are * ignored if the association that the link instantiates has "unique" * multiplicities (see {@link Multiplicity#isUnique()}). Otherwise, multiple * create/delete requests need to be honored because they mean to create/delete * multiple copies of the respective link. * <p> * * There is a logical grouping of {@link LinkCreation}s possible. First of all, * those that insert into the same ordered link sequence (see * {@link #areLinksFromSameOrderedEntitySequence(LinkChange, LinkChange)}) form * one group each. Those groups can further be subdivided into one or more * "contiguous stretches" (see {@link StretchOfContiguousLinkCreations}) where * each such stretch contains {@link LinkCreation}s that insert before the same * original element. With this, each {@link LinkCreation}'s relative position * within its stretch can be determined. For each original element it is then * possibile to determine the stretch of {@link LinkCreation}s in this change * set that end up immediately before this original element. For each stretch, * the stretch's length can be determined, too. * <p> * * When the original element that separates two adjacent stretches of * {@link LinkCreation}s is deleted, later inserts into any of these two * stretches have to be unambiguously assigned to one of the two stretches. For * inserts where there is at least one previously existing element of one of the * stretches between the new insert and the delete position, assigning the * insert to one of the two stretches is obvious. We onlz have to arbitrate the * case where the insert happens "between" the stretches. Since link insert * positions are always specified as a "before" position, it seems intuitive to * assign a link insertion happening at the boundary between the stretches as an * insert into the right stretch because it happens before that stretch's first * link. * <p> * * TODO Currently, adding changes to a change set will have squared effort over * the number of changes because adding a change has to scan all previous * changes for inverse operations. Hashing / caching the changes for O(1) access * will help but is non-trivial given that the cache will have to consider * multiple insertions / deletions of equal links in case of non-unique * associations. * * @author Axel Uhl (D043530) * */ public class ChangeSetImpl<LinkMetaObject extends EObject, LinkEndMetaObject extends EObject, MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage> implements MutableChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> { private final List<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> changes; /** * Remembers the link creations for ordered associations by object to which * the links attach on their unordered end. The value is a map that preserves * the order in which the creations occurred where the keys describe the creation * and the values define the position at which the link has ended up after all * changes recorded so far in this change set's full list of {@link #changes}. */ private final Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>> linkCreationsInOrderedAssociations; /** * Remembers the link deletions for ordered associations by object to which * the links attach on their unordered end. The value is a map that preserves * the order in which the deletions occurred where the keys describe the deletion * and the values define the position at which the link has ended up after all * changes recorded so far in this change set's full list of {@link #changes}. */ private final Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>> linkDeletionsInOrderedAssociations; private ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() { return modelAdapter; } public ChangeSetImpl(ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) { this.modelAdapter = modelAdapter; changes = new LinkedList<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(); linkCreationsInOrderedAssociations = new HashMap<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>>(); linkDeletionsInOrderedAssociations = new HashMap<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>>(); } public ChangeSetImpl(Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changes, ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) { this(modelAdapter); for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { change.addTo(this); } } private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter; public Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>> getLinkCreationsInOrderedAssociations() { return Collections.unmodifiableMap(linkCreationsInOrderedAssociations); } public Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>> getLinkDeletionsInOrderedAssociations() { return Collections.unmodifiableMap(linkDeletionsInOrderedAssociations); } @Override public synchronized void entityCreated(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) { EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> deletion = getDeletionOf(entity); if (deletion != null) { changes.remove(deletion); } else { if (!entity.isUnique() || getCreationOf(entity) == null) { EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entityCreation = new EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(entity, getModelAdapter()); changes.add(entityCreation); } } } private EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getCreationOf( EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) { for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { if (change.isEntityChange() && change.isCreate() && change.getObject().equals(entity)) { return (EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; } } return null; } private EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getDeletionOf( EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) { for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { if (change.isEntityChange() && change.isDelete() && change.getObject().equals(entity)) { return (EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; } } return null; } private LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getCreationOf( Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) { for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { if (change.isLinkChange() && change.isCreate() && change.getObject().equals(link)) { return (LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; } } return null; } private LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getDeletionOf( Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) { for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { if (change.isLinkChange() && change.isDelete() && change.getObject().equals(link)) { return (LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; } } return null; } @Override public synchronized void entityDeleted(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity) { EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> creation = getCreationOf(entity); if (creation != null) { changes.remove(creation); } else { if (getDeletionOf(entity) == null) { EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entityDeletion = new EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(entity, getModelAdapter()); changes.add(entityDeletion); } } } @Override public synchronized void linkCreated(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) { if (link.isOrdered() && (at == null || at == -1)) { throw new RuntimeException("Link position for inserting into ordered association "+ getModelAdapter().getAssociationName(link.getAssociation())+" not specified"); } // From here on it is safe to assume that for links of ordered associations a // valid int has been specified for the "at" argument. LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> deletion = getDeletionOf(link); boolean cancel = false; // does the creation cancel out with a previous deletion? if (deletion != null) { if (link.isOrdered()) { // Check if the link created would appear at the same position to which the // link deletion may have shifted over time Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> deletionKey = new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd()); int currentPosOfDeletedLink = linkDeletionsInOrderedAssociations.get(deletionKey).get(deletion); // at is guaranteed to be non-null here; see check at beginning of method if (currentPosOfDeletedLink == at) { cancel = true; } } else { cancel = true; } } if (cancel) { removeChange(deletion); } else { if (!link.isUnique() || getCreationOf(link) == null) { LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkCreation = new LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(link, at); changes.add(linkCreation); if (link.isOrdered()) { Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> creationKey = new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd()); if (at != null) { // null means append, nothing to shift anyway shiftOrderedLinkChanges(link.getAssociation(), link.getAtOppositeOfOrderedEnd(), at, +1); } Map<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer> creations = linkCreationsInOrderedAssociations.get(creationKey); if (creations == null) { creations = new HashMap<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>(); linkCreationsInOrderedAssociations.put(creationKey, creations); } creations.put(linkCreation, at); } } } } /** * For all link creations and deletions in association <tt>association</tt> with the object * at the unordered end being <tt>unorderedEnd</tt> and with their position being greater or * equal <tt>at</tt>, increases their current position by <tt>delta</tt>. For negative values * of <tt>delta</tt> this effectively means a decrease. */ private void shiftOrderedLinkChanges(LinkMetaObject association, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> unorderedEnd, int at, int delta) { shiftOrderedLinkChanges(association, unorderedEnd, at, delta, linkCreationsInOrderedAssociations); shiftOrderedLinkChanges(association, unorderedEnd, at, delta, linkDeletionsInOrderedAssociations); } /** * For all link creations and deletions in association <tt>association</tt> with the object * at the unordered end being <tt>unorderedEnd</tt> and with their position being greater or * equal <tt>at</tt>, increases their current position by <tt>delta</tt>. For negative values * of <tt>delta</tt> this effectively means a decrease. */ private <T extends RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> void shiftOrderedLinkChanges( LinkMetaObject association, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> unorderedEnd, int at, int delta, Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<T, Integer>> map) { Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> key = new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(association, unorderedEnd); Map<T, Integer> theChanges = map.get(key); if (theChanges != null) { for (T change : theChanges.keySet()) { if (theChanges.get(change) >= at) { theChanges.put(change, theChanges.get(change) + delta); } } } } @Override public synchronized void linkDeleted(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) { if (link.isOrdered() && (at == null || at == -1)) { throw new RuntimeException("Link position for deleting from ordered association "+ getModelAdapter().getAssociationName(link.getAssociation())+" not specified"); } LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> creation = getCreationOf(link); boolean cancel = false; // does the creation cancel out with a previous deletion? if (creation != null) { if (link.isOrdered()) { // Check if the link created would appear at the same position to which the // link deletion may or may not have shifted over time Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> deletionKey = new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd()); int currentPosOfCreatedLink = linkCreationsInOrderedAssociations.get(deletionKey).get(creation); if (currentPosOfCreatedLink == at) { cancel = true; } } else { cancel = true; } } if (cancel) { removeChange(creation); } else { if (!link.isUnique() || getDeletionOf(link) == null) { LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkDeletion = new LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(link, at); changes.add(linkDeletion); if (link.isOrdered()) { Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> deletionKey = new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd()); shiftOrderedLinkChanges(link.getAssociation(), link.getAtOppositeOfOrderedEnd(), at, -1); Map<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer> deletions = linkDeletionsInOrderedAssociations.get(deletionKey); if (deletions == null) { deletions = new HashMap<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer>(); linkDeletionsInOrderedAssociations.put(deletionKey, deletions); } deletions.put(linkDeletion, at); } } } } @Override public Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getChanges() { return new ArrayList<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(changes).iterator(); } /** * Checks if the two link changes describe changes to the same ordered association with * the same entity on the unordered end. This makes the two links part of the same * link sequence that represents the ordered "collection" of objects on the ordered * end when navigated from that same object at the unordered end. */ public static <LinkMetaObject extends EObject, LinkEndMetaObject extends EObject, MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage> boolean areLinksFromSameOrderedEntitySequence( LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc1, LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc2) { return (lc1.getObject().isOrdered() && lc1.getObject().getAssociation().equals(lc2.getObject().getAssociation()) && lc1.getObject().getAtOppositeOfOrderedEnd().logicallyEquals(lc2.getObject().getAtOppositeOfOrderedEnd())); } /** * For a link change of an association with one ordered end determines at * what <em>original</em> position the change would have occurred if it had * been the only change in this change set. Note, that this method will * return the same result for multiple contiguous inserts from this change * set. * <p> * * @param linkChange * needs to be contained in this change set's {@link #changes}. * The original position of the change is computed based on the * changes preceding <tt>linkChange</tt>. */ public int getOriginalPosition(LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkChange) { if (!linkChange.getObject().isOrdered()) { throw new RuntimeException("Can't determine a position for a link of an unordered association"); } RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change = null; int positionMappedToI = linkChange.getPosition(); // backward-traverse all changes timewise preceding linkChange int indexOfLinkChange = changes.indexOf(linkChange); int startReverseIterationAt; if (indexOfLinkChange == -1) { throw new RuntimeException("link change " + linkChange + " not contained in change set " + this + ". Can't determine its original position."); } else { startReverseIterationAt = indexOfLinkChange; } /* * TODO idea: traverse backwards, track position within the contiguous stretch of inserts; */ for (ListIterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> i = changes.listIterator(startReverseIterationAt); i.hasPrevious();) { change = i.previous(); if (change.isLinkChange()) { LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc = (LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; if (areLinksFromSameOrderedEntitySequence(lc, linkChange)) { // manipulation of same "sequence"; check if it affects a // position before that of linkChange if (lc.isCreate()) { if (lc.getPosition() < positionMappedToI) { positionMappedToI--; } } else { if (lc.getPosition() <= positionMappedToI) { positionMappedToI++; } } } } } return positionMappedToI; } /** * This change set conflicts with another if any of the following conditions apply: * <p> * * <ul> * <li>an entity that was created in the one was deleted in the other</li> * <li>an entity was deleted in the one while the other contains a change of a link of which at * least one end is the entity deleted in the one (although it may be conceivable that the * resolution of such a situation is to let the delete win)</li> * <li>a link created in one was deleted in the other</li> * </ul> * * @return <tt>true</tt> if this change set conflicts with <tt>other</tt> */ @Override public synchronized boolean conflictsWith(ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> other) { // TODO implement conflictsWith return false; } /** * Returns true if the {@link ChangeSetImpl changeSet} is empty. A {@link ChangeSetImpl changeSet} is empty * if no entities or links are created or deleted. */ @Override public boolean isEmpty() { return changes.size() == 0; } /** * Postcondition: <tt>this.isEmpty()</tt> */ @Override public synchronized void clear() { changes.clear(); linkCreationsInOrderedAssociations.clear(); linkDeletionsInOrderedAssociations.clear(); } @Override public Iterator<EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getEntityCreations() { return new ClassFilterIterator<EntityCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>( EntityCreation.class, changes.iterator()); } @Override public Iterator<EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getEntityDeletions() { return new ClassFilterIterator<EntityDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>( EntityDeletion.class, changes.iterator()); } /** * Create a clone of this ChangeSet that can be safely modified. */ @SuppressWarnings("unchecked") public ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone() { ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone = new ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(getModelAdapter()); // a map that keeps track of which cloned change descriptors replace which original change descriptors Map<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> map = new HashMap<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(); // make clones of the changes so that we can safely modify insert positions for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : changes) { RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clonedChange = (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change.clone(); map.put(change, clonedChange); clone.changes.add(clonedChange); } cloneWithChangeObjectAsKeyOfValueMap(linkCreationsInOrderedAssociations, clone.linkCreationsInOrderedAssociations, map); cloneWithChangeObjectAsKeyOfValueMap(linkDeletionsInOrderedAssociations, clone.linkDeletionsInOrderedAssociations, map); return clone; } @SuppressWarnings("unchecked") private <T extends RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<T, Integer>> cloneWithChangeObjectAsKeyOfValueMap( Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<T, Integer>> from, Map<Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Map<T, Integer>> result, Map<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> mapping) { for (Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> key : from.keySet()) { Map<T, Integer> value = from.get(key); Map<T, Integer> copiedValueMap = new HashMap<T, Integer>(); result.put(key, copiedValueMap); for (T change : value.keySet()) { copiedValueMap.put((T) mapping.get(change), value.get(change)); } } return result; } /** * Removes the <tt>change</tt> from this changeset.<p> * * Precondition: {@link #getChanges()} contains <tt>change</tt> */ public void removeChange(RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change) { changes.remove(change); if (change.isLinkChange()) { LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkChange = (LinkChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link = linkChange.getObject(); if (link.isOrdered()) { if (change.isDelete()) { Map<LinkDeletion<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer> map = linkDeletionsInOrderedAssociations .get(new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd())); map.remove(linkChange); shiftOrderedLinkChanges(link.getAssociation(), link.getAtOppositeOfOrderedEnd(), linkChange.getPosition(), +1); } else { Map<LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Integer> map = linkCreationsInOrderedAssociations .get(new Pair<LinkMetaObject, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>( link.getAssociation(), link.getAtOppositeOfOrderedEnd())); map.remove(linkChange); shiftOrderedLinkChanges(link.getAssociation(), link.getAtOppositeOfOrderedEnd(), linkChange.getPosition(), -1); } } } } @Override public Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> iterator() { return getChanges(); } /** * Determines the stretch of the given {@link LinkCreation}. This is the contiguous stretch of * link insertions into an ordered association with the same element on the unordered end * before the same original element on the ordered end. */ public StretchOfContiguousLinkCreations getStretch(LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkCreation) { return new StretchOfContiguousLinkCreations(linkCreation); } /** * Determines the stretch of contiguous link insertions into the given association * with the specified object on the unordered end and before the original position * specified. */ public StretchOfContiguousLinkCreations getStretch(LinkMetaObject association, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectOnUnorderedEnd, int beforeOriginalPosition) { return new StretchOfContiguousLinkCreations(beforeOriginalPosition, association, objectOnUnorderedEnd); } /** * A subsequence of {@link ChangeSetImpl#changes} consisting only of * {@link LinkCreation} changes with {@link LinkChange#getObject() * getObject()}.{@link Link#isOrdered() isOrdered()} with the same object on * the unordered end that all happen before the same element of the original * snapshot on the ordered end (meaning in the same link sequence in one * contiguous stretch). * * @author Axel Uhl D043530 * */ public class StretchOfContiguousLinkCreations { /** * The original position before which this stretch of link creations * takes place, in the snapshot on which the enclosing change set will be based. * * @see ChangeSetImpl#getOriginalPosition(LinkChange) */ private int beforeOriginalPosition; /** * The ordered association into which this stretch inserts links */ private LinkMetaObject association; /** * The object at the unordered end for all insertions of this stretch */ private ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtUnorderedEnd; protected StretchOfContiguousLinkCreations(LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkCreation) { if (!linkCreation.getObject().isOrdered()) { throw new RuntimeException("Stretches of link insertions are only managed for ordered associations. "+ linkCreation.getObject().getAssociation()+" is not ordered"); } beforeOriginalPosition = getOriginalPosition(linkCreation); association = linkCreation.getObject().getAssociation(); objectAtUnorderedEnd = linkCreation.getObject().getAtOppositeOfOrderedEnd(); } protected StretchOfContiguousLinkCreations(int beforeOriginalPosition, LinkMetaObject association, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtUnorderedEnd) { super(); this.beforeOriginalPosition = beforeOriginalPosition; this.association = association; this.objectAtUnorderedEnd = objectAtUnorderedEnd; } /** * Returns the number of link creations in this stretch */ public int getLength() { // TODO implement StretchOfContiguousLinkCreations.getLength() return 0; } /** * Returns the position of the original element before this stretch is * inserted in the snapshot on which the enclosing change set is based. */ public int getBeforeOriginalPosition() { return beforeOriginalPosition; } /** * This stretch consists of a number of {@link LinkCreation} changes for * an ordered association that are or have been or shall be executed in * order. Each has a position relative to the object on the unordered * end. Additionally, for each such {@link LinkCreation} we can * determine the position relative to which it needs to be inserted * <em>within</em> this stretch. This view removes the complexity of * other link creations and deletions left of this stretch. * <p> * * Note, that the position returned by this method does not represent * the final position of the <tt>linkCreation</tt> within this stretch. * For example, if the creation sequence is (0, a), (0, b), (1, c), (1, * d), then we have a stretch consisting of four links to b, d, c, a. * The (0, a) change has shifted to final position 3. Yet, this method will * return 0 for it because when replaying this stretch in order, it needs * to be replayed as (0, a), (0, b), (1, c), (1, d) again, only potentially * shifted altogether in case on the target side something got inserted before * the original element at position 0. */ public int getPositionInStretchInOrder(LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkCreation) { for (RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change : ChangeSetImpl.this) { if (change.isLinkChange() && change.isCreate()) { LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc = (LinkCreation<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) change; if (lc.getObject().getAssociation().equals(association) && lc.getObject().getAtOppositeOfOrderedEnd().equals(objectAtUnorderedEnd)) { } } } return 0; // TODO } } @Override public int totalSize() { return changes.size(); } }