package com.sap.runlet.abstractinterpreter.repository.simpleimpl;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import org.eclipse.emf.ecore.EObject;
import com.sap.runlet.abstractinterpreter.objects.AbstractValueObject;
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.objects.RunletObject;
import com.sap.runlet.abstractinterpreter.operationaltransformation.Change;
import com.sap.runlet.abstractinterpreter.operationaltransformation.ChangeList;
import com.sap.runlet.abstractinterpreter.operationaltransformation.EmptyChange;
import com.sap.runlet.abstractinterpreter.operationaltransformation.Transformer;
import com.sap.runlet.abstractinterpreter.repository.AbstractSnapshotIdentifier;
import com.sap.runlet.abstractinterpreter.repository.ChangeSet;
import com.sap.runlet.abstractinterpreter.repository.ChangeSetImpl;
import com.sap.runlet.abstractinterpreter.repository.FixedSnapshot;
import com.sap.runlet.abstractinterpreter.repository.Repository;
import com.sap.runlet.abstractinterpreter.repository.RepositoryChange;
import com.sap.runlet.abstractinterpreter.repository.RepositoryObject;
import com.sap.runlet.abstractinterpreter.repository.Snapshot;
import com.sap.runlet.abstractinterpreter.repository.SnapshotIdentifier;
import com.sap.runlet.abstractinterpreter.repository.SnapshotImpl;
import com.sap.runlet.abstractinterpreter.repository.Tag;
import com.sap.runlet.abstractinterpreter.util.ModelAdapter;
import com.sap.runlet.abstractinterpreter.util.Tuple.Pair;
public class InMemoryRepository<LinkMetaObject extends EObject,
LinkEndMetaObject extends EObject,
MetaClass extends EObject,
TypeUsage extends EObject,
ClassUsage extends TypeUsage> implements Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>{
private Map<Snapshot, SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> linkContainers;
private Map<Snapshot, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> entities;
private Map<Snapshot, Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>> entitiesByClass;
/**
* Holds all snapshots of this repository that don't have a successor.<p>
*
* <b>Invariant:</b> <tt>tipsOfBranches.contains(current)</tt>
*/
private Set<Snapshot> tipsOfBranches;
/**
* Keeps track of what the predecessors of each snapshot are. This data
* is used to answer any topology query about snapshots.
*
* @see #precedes(Snapshot, Snapshot)
* @see #getTopology(Snapshot, Snapshot)
*/
private Map<Snapshot, Set<Snapshot>> predecessors;
/**
* Keeps track of the changes that connect predecessor snapshots with their
* successors. For any snapshot <tt>predecessor</tt> that appears in a value set in
* {@link #predecessors} for key <tt>successor</tt>, an entry with key
* <tt>new Pair(predecessor, successor)</tt> exists in this map such that the
* value for this pair is the change set that transforms the <tt>predecessor</tt>
* snapshot into the <tt>successor</tt> snapshot.
*/
protected Map<Pair<Snapshot, Snapshot>, ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> changes;
private SnapshotIdentifier trunk = new Tag("HEAD of TRUNK");
/**
* The current snapshot
*/
private Snapshot current;
private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter;
public InMemoryRepository(ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) {
this.modelAdapter = modelAdapter;
linkContainers = new HashMap<Snapshot, SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
entities = new HashMap<Snapshot, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
entitiesByClass = new HashMap<Snapshot, Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>>();
tipsOfBranches = new HashSet<Snapshot>();
predecessors = new HashMap<Snapshot, Set<Snapshot>>();
changes = new HashMap<Pair<Snapshot, Snapshot>, ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
current = addEmptySnapshot();
}
protected ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() {
return modelAdapter;
}
/**
* Returns a collection that equals the collection passed, except that all
* {@link RepositoryObject}s contained have their snapshot
* {@link RepositoryObject#getCopyWithOrigin(SnapshotIdentifier) adjusted} to
* <tt>toSnapshot</tt>.
*/
@SuppressWarnings("unchecked")
private <T extends RepositoryObject> Collection<T> adjustSnapshot(Collection<T> collection,
SnapshotIdentifier toSnapshot) {
// The assumption that the collection class has a default constructor is a bit risky...
// If anyone knows a better way, please fix this.
try {
Collection<T> result;
if (collection.size() == 0) {
result = Collections.emptyList();
} else {
result = collection.getClass().newInstance();
for (T ro : collection) {
result.add((T) ro.getCopyWithOrigin(toSnapshot));
}
}
return result;
} catch (Exception e) {
throw new RuntimeException("Collection class "+collection.getClass().getName()+
" does not have a default constructor; can't clone", e);
}
}
private Snapshot addEmptySnapshot() {
Snapshot result = new SnapshotImpl();
addSnapshot(result, /* predecessor */ null, null,
new SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(getModelAdapter()),
new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(),
new HashMap<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>());
return result;
}
@Override
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> allLinks(LinkMetaObject association, SnapshotIdentifier fromSnapshot) {
/** check invariants */
assert association != null && fromSnapshot != null;
resolveIfNotBound(fromSnapshot);
assert has(fromSnapshot.getSnapshot());
return adjustSnapshot(getLinkContainers().get(fromSnapshot.getSnapshot()).all(association), fromSnapshot);
}
@Override
public Collection<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> all(MetaClass clazz, SnapshotIdentifier fromSnapshot) {
Collection<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> result;
/** check invariants */
assert clazz != null && fromSnapshot != null;
// TODO contemplate resolving to multiple snapshots and returning instances from all of them
resolveIfNotBound(fromSnapshot);
assert has(fromSnapshot.getSnapshot());
Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> entitiesByClassFromSnapshot = getEntitiesByClass().get(fromSnapshot.getSnapshot());
if (entitiesByClassFromSnapshot != null) {
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> allEntitiesFromSnapshot = entitiesByClassFromSnapshot.get(clazz);
if (allEntitiesFromSnapshot != null) {
result = adjustSnapshot(allEntitiesFromSnapshot, fromSnapshot);
} else {
result = Collections.emptySet();
}
} else {
result = Collections.emptySet();
}
return result;
}
@Override
public Snapshot getCurrent() {
return current;
}
private void setCurrent(Snapshot newHeadOfTrunk) {
Snapshot oldCurrent = getCurrent();
if (oldCurrent != null) {
tipsOfBranches.remove(oldCurrent);
}
current = newHeadOfTrunk;
tipsOfBranches.add(newHeadOfTrunk);
}
@Override
public boolean has(Snapshot snapshot) {
/** check invariants */
assert snapshot != null;
return getSnapshots().contains(snapshot);
}
@Override
public Snapshot apply(ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changeset, SnapshotIdentifier applyTo) {
/** check invariants */
assert changeset != null && applyTo != null;
resolveIfNotBound(applyTo);
assert applyTo.getSnapshot() != null;
assert getSnapshots().contains(applyTo.getSnapshot());
Snapshot result = new SnapshotImpl();
SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> snapshotLinkContainer = getLinkContainers().get(applyTo.getSnapshot()).clone();
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> snapshotEntities = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> eo : getEntities().get(applyTo.getSnapshot())) {
snapshotEntities.add(eo.clone());
}
for (Iterator<RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> i = changeset.getChanges(); i.hasNext(); ) {
RepositoryChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> change = i.next();
change.apply(snapshotEntities, snapshotLinkContainer);
}
Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> snapshotEntitiesByClass = new HashMap<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> eo:snapshotEntities) {
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> set = snapshotEntitiesByClass.get(
getModelAdapter().getClazz(eo.getType()));
if (set == null) {
set = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
snapshotEntitiesByClass.put(getModelAdapter().getClazz(eo.getType()), set);
}
set.add(eo);
}
addSnapshot(result, applyTo, changeset, snapshotLinkContainer,
snapshotEntities, snapshotEntitiesByClass);
/** check post conditions */
assert has(result);
return result;
}
private void addSnapshot(Snapshot snapshot, SnapshotIdentifier predecessor,
ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changesFromPredecessor,
SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> snapshotLinkContainer,
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> snapshotEntities,
Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> snapshotEntitiesByClass) {
HashSet<Snapshot> preds = new HashSet<Snapshot>();
if (predecessor != null) {
resolveIfNotBound(predecessor);
if (predecessor.getSnapshot().equals(getCurrent())) {
// successor of previous current becomes current
setCurrent(snapshot);
}
if (predecessor.getSnapshot() != null) {
preds.add(predecessor.getSnapshot());
changes.put(new Pair<Snapshot, Snapshot>(predecessor.getSnapshot(), snapshot), changesFromPredecessor);
tipsOfBranches.remove(predecessor.getSnapshot());
}
}
getLinkContainers().put(snapshot, snapshotLinkContainer);
getEntities().put(snapshot, Collections.unmodifiableSet(snapshotEntities));
getEntitiesByClass().put(snapshot, Collections.unmodifiableMap(snapshotEntitiesByClass));
predecessors.put(snapshot, preds);
tipsOfBranches.add(snapshot);
}
private void resolveIfNotBound(SnapshotIdentifier snapshotIdentifier) {
if (snapshotIdentifier.getSnapshot() == null) {
resolve(snapshotIdentifier);
}
}
private Map<Snapshot, SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getLinkContainers() {
return linkContainers;
}
private Map<Snapshot, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> getEntities() {
return entities;
}
private Map<Snapshot, Map<MetaClass, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>> getEntitiesByClass() {
return entitiesByClass;
}
private Set<Snapshot> getSnapshots() {
return getEntities().keySet();
}
@Override
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> loadLinks(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> from, LinkEndMetaObject otherEnd) {
assert (from instanceof AbstractValueObject<?, ?, ?, ?, ?>) || from.isPersistent();
SnapshotIdentifier si = from.getOrigin();
resolveIfNotBound(si);
assert has(si.getSnapshot());
SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> lc = getLinkContainers().get(si.getSnapshot());
return adjustSnapshot(lc.navigate(from, otherEnd), from.getOrigin());
}
@Override
public boolean directlyPrecedes(Snapshot a, Snapshot b) {
Set<Snapshot> preds = getImmediatePredecessors(b);
return preds != null && preds.contains(a);
}
@Override
public boolean precedes(Snapshot a, Snapshot b) {
return getPath(a, b) != null;
}
/**
* If <tt>from</tt> precedes <tt>to</tt>, a list of {@link Snapshot}s results of which the
* first (0) element is <tt>from</tt>, the last element it <tt>to</tt> and for all <tt>i>0</tt>
* the following condition holds: <tt>directlyPrecedes(result.get(i-1), result.get(i))</tt>. If
* <tt>from</tt> is not a predecessor of <tt>to</tt>, <tt>null</tt> is returned.
*/
protected List<Snapshot> getPath(Snapshot from, Snapshot to) {
List<Snapshot> result = null;
// the algorithm avoids recursion and therefore should scale also for longer paths
Map<Snapshot, List<Snapshot>> searchBaseForAWithPath = new HashMap<Snapshot, List<Snapshot>>();
List<Snapshot> pathForPredecessor = new LinkedList<Snapshot>();
pathForPredecessor.add(to);
searchBaseForAWithPath.put(to, pathForPredecessor);
while (result == null && !searchBaseForAWithPath.isEmpty()) {
if (searchBaseForAWithPath.containsKey(from)) {
result = searchBaseForAWithPath.get(from);
} else {
Set<Snapshot> preds = new HashSet<Snapshot>();
Map<Snapshot, List<Snapshot>> nextSearchBaseForAWithPath = new HashMap<Snapshot, List<Snapshot>>();
for (Snapshot s : searchBaseForAWithPath.keySet()) {
Set<Snapshot> sPredecessors = predecessors.get(s);
if (sPredecessors != null) {
for (Snapshot predecessor : sPredecessors) {
List<Snapshot> pathForSPredecessor = new LinkedList<Snapshot>(searchBaseForAWithPath.get(s));
pathForSPredecessor.add(0, predecessor); // prepend predecessor to path from s to "to"
nextSearchBaseForAWithPath.put(predecessor, pathForSPredecessor);
}
preds.addAll(sPredecessors);
}
}
searchBaseForAWithPath = nextSearchBaseForAWithPath;
}
}
return result;
}
@Override
public void resolve(SnapshotIdentifier tag) {
// dispatches resolution through the different implementations in AbstractSnapshotIdentifier
// subclasses
((AbstractSnapshotIdentifier) tag).resolve(this);
}
public Snapshot resolveTag(Tag tag) {
if (trunk.equals(tag)) {
return getCurrent();
} else {
// TODO support tags for branches other than the trunk
return null;
}
}
public Snapshot resolveLastBeforeDateOnBranch(Date timestamp, Tag branchIdentifier) {
Snapshot result = null;
Set<Snapshot> candidates = new HashSet<Snapshot>();
candidates.add(resolveTag(branchIdentifier));
Set<SnapshotIdentifier> resultSet = getLastSnapshotsBeforeOrAt(timestamp, candidates);
if (resultSet.size() > 0) {
SnapshotIdentifier si = resultSet.iterator().next();
resolve(si);
result = si.getSnapshot();
}
return result;
}
@Override
public SnapshotIdentifier getTrunkIdentifier() {
return trunk.clone();
}
@Override
public Set<Snapshot> getImmediatePredecessors(Snapshot snapshot) {
Set<Snapshot> preds = predecessors.get(snapshot);
Set<Snapshot> result = new HashSet<Snapshot>();
if (preds != null) {
result.addAll(preds);
}
return result;
}
@Override
public Set<SnapshotIdentifier> getLastSnapshotsBeforeOrAt(Date fromLastConcurrentSnapshotsBefore) {
/* Starts from the tips of all branches and works its way through the predecessors.
It is assumed that time descends while walking through the predecessors. On each
path, the first snapshot found that has a timestamp before fromLastConcurrentSnapshotsBefore
will be added to the result set. All its immediate and transitive predecessors will be added
to the inverse result set, and the descent into that path stops. Finally, when all branches
have been explored, the inverse result set will be subtracted from the result set so far,
removing any snapshots that have a successor before the date requested on another branch.
*/
Set<Snapshot> candidates = new HashSet<Snapshot>(tipsOfBranches);
return getLastSnapshotsBeforeOrAt(fromLastConcurrentSnapshotsBefore, candidates);
}
private Set<SnapshotIdentifier> getLastSnapshotsBeforeOrAt(Date fromLastConcurrentSnapshotsBefore, Set<Snapshot> candidates) {
Set<Snapshot> resultSnapshots = new HashSet<Snapshot>();
Set<Snapshot> noCandidates = new HashSet<Snapshot>();
while (!candidates.isEmpty()) {
Set<Snapshot> newCandidates = new HashSet<Snapshot>();
for (Snapshot candidate:candidates) {
if (!noCandidates.contains(candidate)) {
if (candidate.when().equals(fromLastConcurrentSnapshotsBefore) ||
candidate.when().before(fromLastConcurrentSnapshotsBefore)) {
// found one; don't check its predecessors anymore
resultSnapshots.add(candidate);
noCandidates.addAll(getAllPredecessors(candidate));
} else {
noCandidates.add(candidate);
// candidate was at or after requested date; check its predecessors, if
// there are any
newCandidates.addAll(getImmediatePredecessors(candidate));
}
}
}
candidates.addAll(newCandidates);
candidates.removeAll(resultSnapshots);
candidates.removeAll(noCandidates);
}
Set<SnapshotIdentifier> result = new HashSet<SnapshotIdentifier>();
for (Snapshot resultSnapshot:resultSnapshots) {
result.add(new FixedSnapshot(resultSnapshot));
}
return result;
}
private Set<Snapshot> getAllPredecessors(Snapshot snapshot) {
Set<Snapshot> result = new HashSet<Snapshot>();
Set<Snapshot> immediate = getImmediatePredecessors(snapshot);
result.addAll(immediate);
for (Snapshot immediatePredecessor:immediate) {
result.addAll(getAllPredecessors(immediatePredecessor));
}
return result;
}
@Override
public Set<Snapshot> getAllSnapshots() {
Set<Snapshot> resultSnapshots = new HashSet<Snapshot>();
Queue<Snapshot> snapshotQueue = new LinkedBlockingQueue<Snapshot>();
// add all tips to queue for traversal
snapshotQueue.addAll(tipsOfBranches);
// iterate over queue of snapshots and add them to the results if not present yet
while (snapshotQueue.peek() != null) {
Snapshot snapshot2 = snapshotQueue.remove();
if (resultSnapshots.add(snapshot2)) {
snapshotQueue.addAll(getImmediatePredecessors(snapshot2));
}
}
return resultSnapshots;
}
@Override
public List<List<Snapshot>> getPathsFromLastCommonAncestor(Snapshot... snapshots) {
Map<Snapshot, List<Snapshot>> result = new LinkedHashMap<Snapshot, List<Snapshot>>();
Snapshot commonAncestor = null;
for (Snapshot s : snapshots) {
// Invariant: result contains all s from snapshots traversed so far as key and
// the list contained as the respective value denotes the path from
// the commonAncestor to the key snapshot.
if (commonAncestor == null) {
commonAncestor = s;
List<Snapshot> pathFromCommonAncestorToS = new LinkedList<Snapshot>();
pathFromCommonAncestorToS.add(s);
result.put(s, pathFromCommonAncestorToS);
} else {
List<Snapshot> pathFromCommonAncestorToS = getPath(commonAncestor, s);
if (pathFromCommonAncestorToS == null) {
// commonAncestor is no ancestor of s; traverse through commonAncestor's
// ancestors until bottom is reached or an ancestor of s is found
Snapshot newCommonAncestor = null;
// the lists in the candidatesAndPath values contain the paths from the key candidates
// up to but excluding the commonAncestor snapshot
Map<Snapshot, List<Snapshot>> candidatesAndPath = new HashMap<Snapshot, List<Snapshot>>();
List<Snapshot> emptyList = Collections.emptyList();
candidatesAndPath.put(commonAncestor, emptyList);
List<Snapshot> pathFromNewCommonAncestorToS = null;
// Loop Invariant: pathFromNewCommonAncestorToS != null iff newCommonAncestor != null
while (newCommonAncestor == null && candidatesAndPath.size() > 0) {
Map<Snapshot, List<Snapshot>> newCandidatesAndPath = new HashMap<Snapshot, List<Snapshot>>();
for (Snapshot c : candidatesAndPath.keySet()) {
pathFromNewCommonAncestorToS = getPath(c, s); // null for the first iteration where c==commonAncestor
if (pathFromNewCommonAncestorToS != null) {
newCommonAncestor = c;
newCandidatesAndPath.put(c, candidatesAndPath.get(c));
break;
} else {
for (Snapshot immediateCandidatePredecessor : getImmediatePredecessors(c)) {
List<Snapshot> immediatePredecessorPath = new LinkedList<Snapshot>(
candidatesAndPath.get(c));
immediatePredecessorPath.add(0, immediateCandidatePredecessor);
newCandidatesAndPath.put(immediateCandidatePredecessor, immediatePredecessorPath);
}
}
}
candidatesAndPath = newCandidatesAndPath;
}
if (newCommonAncestor == null) {
return null; // no common ancestor found
}
// extend all existing paths by prepending the path from the new common ancestor to the
// previous common ancestor (excluding the previous common ancestor because that's what
// the paths already start with)
for (Snapshot r : result.keySet()) {
if (r != s) {
List<Snapshot> newPath = new LinkedList<Snapshot>(result.get(r));
newPath.addAll(0, candidatesAndPath.get(newCommonAncestor));
result.put(r, newPath); // replacement doesn't harm iterator, no modCount increase
}
}
result.put(s, pathFromNewCommonAncestorToS);
commonAncestor = newCommonAncestor;
} else {
result.put(s, pathFromCommonAncestorToS);
}
}
}
return new LinkedList<List<Snapshot>>(result.values());
}
@Override
public Snapshot merge(Snapshot fromBranch, Snapshot intoTrunk) {
List<List<Snapshot>> paths = getPathsFromLastCommonAncestor(fromBranch, intoTrunk);
if (paths == null) {
throw new RuntimeException("Can't merge "+fromBranch+" into "+intoTrunk+
" because they don't have a common ancestor");
}
Iterator<List<Snapshot>> pathIter = paths.iterator();
Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changesOnBranch = getChanges(pathIter.next());
Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changesOnTrunk = getChanges(pathIter.next());
Transformer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> t = new Transformer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>();
Pair<Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>, Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> mergingChanges = t.transform(changesOnBranch, changesOnTrunk);
ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> mergingChangeSetForTrunk =
new ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(
mergingChanges.getB(), getModelAdapter());
Snapshot mergeResult = apply(mergingChangeSetForTrunk, new FixedSnapshot(intoTrunk));
predecessors.get(mergeResult).add(fromBranch); // declare merged snapshot a successor of fromBranch
changes.put(new Pair<Snapshot, Snapshot>(fromBranch, mergeResult),
new ChangeSetImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(
mergingChanges.getA(), getModelAdapter()));
return mergeResult;
}
protected Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getChanges(List<Snapshot> snapshotList) {
Change<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result;
Iterator<Snapshot> iter = snapshotList.iterator();
if (!iter.hasNext()) {
result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>();
} else {
Snapshot successor = iter.next();
if (!iter.hasNext()) {
// there was only one snapshot on the path
result = new EmptyChange<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>();
} else {
ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changeList =
new ChangeList<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>();
while (iter.hasNext()) {
Snapshot predecessor = successor;
successor = iter.next();
ChangeSet<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> changeSetBetweenPredAndSucc = changes.get(new Pair<Snapshot, Snapshot>(predecessor, successor));
changeList.add(changeSetBetweenPredAndSucc);
}
result = changeList;
}
}
return result;
}
public boolean testSnapshotEquality(Snapshot s1, Snapshot s2) {
if (s1 == null || s2 == null) {
throw new IllegalArgumentException("Snapshot must not be null");
}
if (s1 != s2) {
Set<EntityObjectWrapper> es1 = new HashSet<EntityObjectWrapper>();
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> o: entities.get(s1)) {
es1.add(new EntityObjectWrapper(o));
}
Set<EntityObjectWrapper> es2 = new HashSet<EntityObjectWrapper>();
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> o: entities.get(s2)) {
es2.add(new EntityObjectWrapper(o));
}
Set<LinkWrapper> lc1 = new HashSet<LinkWrapper>();
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l: linkContainers.get(s1).getAllLinks()) {
lc1.add(new LinkWrapper(l));
}
Set<LinkWrapper> lc2 = new HashSet<LinkWrapper>();
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l: linkContainers.get(s2).getAllLinks()) {
lc2.add(new LinkWrapper(l));
}
// entity sets and link sets should be of same size
if (! ( es1.equals(es2) && lc1.equals(lc2) ) ) {
return false;
}
}
return true;
}
private class EntityObjectWrapper {
private EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> delegate;
public EntityObjectWrapper(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> o) {
delegate = o;
}
@SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (o instanceof EntityObject) {
return delegate.logicallyEquals((RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>) o);
} else if (EntityObjectWrapper.class.isAssignableFrom(o.getClass())) {
return delegate.logicallyEquals(((EntityObjectWrapper) o).delegate);
} else {
return false;
}
}
public int hashCode() {
return delegate.logicalHashCode();
}
}
private class LinkWrapper {
private Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> delegate;
public LinkWrapper(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l) {
delegate = l;
}
@Override
public boolean equals(Object o) {
if (o instanceof Link<?, ?, ?, ?, ?>) {
return delegate.logicallyEquals(o);
} else if (LinkWrapper.class.isAssignableFrom(o.getClass())) {
@SuppressWarnings("unchecked")
LinkWrapper oAsLinkWrapper = (LinkWrapper) o;
return delegate.logicallyEquals(oAsLinkWrapper.delegate);
} else {
return false;
}
}
@Override
public int hashCode() {
return delegate.logicalHashCode();
}
}
}