package com.sap.runlet.abstractinterpreter.repository.simpleimpl;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.ecore.EObject;
import com.sap.runlet.abstractinterpreter.Side;
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.repository.SimpleLinkContainer;
import com.sap.runlet.abstractinterpreter.repository.Snapshot;
import com.sap.runlet.abstractinterpreter.util.HashBag;
import com.sap.runlet.abstractinterpreter.util.ModelAdapter;
/**
* A container for {@link Link}s, similar to the {@link RunletLinkContainer}, but much simpler.
* Used in the {@link InMemoryRepository} to keep track of the links stored in a
* {@link Snapshot} and supporting an efficient navigation from one end across an
* {@link data.classes.Association} to the objects at the other end, meaning a loading
* of all the links attached to a given object that are type of a specific association.<p>
*
* @author Axel Uhl (D043530)
*/
public class SimpleLinkContainerImpl<LinkMetaObject extends EObject,
LinkEndMetaObject extends EObject,
MetaClass extends EObject,
TypeUsage extends EObject,
ClassUsage extends TypeUsage>
implements SimpleLinkContainer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> {
/**
* A value-holder class for an object and a remote association end. Use instances of this class
* as key for maps in which to store the links for navigating from the <tt>object</tt> to the
* remote association end. The object's {@link ClassTypedObject#getOrigin() snapshot} is ignored
* during {@link #hashCode()} and {@link #equals(Object)} because this entire link container is
* in the scope of a single snapshot anyway. This also avoids trouble during lookups where the
* object passed in has a snapshot identifier not equal to the one used when the object was
* stored in the snapshot.
*
* @author Axel Uhl (D043530)
*/
public class ObjectAndRemoteAssociationEndIgnoringSnapshot {
private ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> object;
private LinkEndMetaObject remoteAssociationEnd;
public ObjectAndRemoteAssociationEndIgnoringSnapshot(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> object, LinkEndMetaObject remoteAssociationEnd) {
this.object = object;
this.remoteAssociationEnd = remoteAssociationEnd;
}
public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getObject() {
return object;
}
public LinkEndMetaObject getRemoteAssociationEnd() {
return remoteAssociationEnd;
}
public boolean equals(Object o) {
@SuppressWarnings("unchecked")
ObjectAndRemoteAssociationEndIgnoringSnapshot oCast = (ObjectAndRemoteAssociationEndIgnoringSnapshot) o;
return (getObject() == null && oCast.getObject() == null) ||
getObject() != null && getObject().logicallyEquals(
oCast.getObject()) &&
getRemoteAssociationEnd().equals(oCast.getRemoteAssociationEnd());
}
public int hashCode() {
return ((getObject()==null)?0:getObject().logicalHashCode()) ^ getRemoteAssociationEnd().hashCode();
}
public String toString() {
StringBuilder result = new StringBuilder();
result.append("ObjectAndRemoteAssociationEnd from object ");
result.append(getObject());
result.append(" via association ");
result.append(getModelAdapter().getAssociationName(getModelAdapter().getAssociation(getRemoteAssociationEnd())));
result.append(" to end ");
result.append(getModelAdapter().getEndName(getRemoteAssociationEnd()));
result.append(" of type ");
result.append(getModelAdapter().getClassName(
getModelAdapter().getEndType(getRemoteAssociationEnd())));
return result.toString();
}
}
/**
* Values are the sets of links that have the key as their {@link Link#getLeft() left} end.
* May be an empty set but never <tt>null</tt>. It is possible that a {@link ClassTypeObject}
* is not contained as a key in this map, meaning that this {@link RunletLinkContainer} does not
* know about the links connected to that object; in this case, queries need to be forwarded
* to the {@link #repository} for resolution.<p>
*
* <pre>Invariant:
* if ObjectAndRemoteAssociationEnd.getObject() instanceOf EntityObject then
* if entityObject.getSnapshot() != null then
* for all links: link.getSnapshot() == null
* || link.getSnapshot().equals(entityObject.getSnapshot())
* </pre>
*/
private HashMap<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> linksForLeft;
/**
* Values are the sets of links that have the key as their {@link Link#getRight() right} end.
* May be an empty set but never <tt>null</tt>. It is possible that a {@link ClassTypeObject}
* is not contained as a key in this map, meaning that this {@link RunletLinkContainer} does not
* know about the links connected to that object; in this case, queries need to be forwarded
* to the {@link #repository} for resolution.<p>
*
* <pre>Invariant:
* if ObjectAndRemoteAssociationEnd.getObject() instanceOf EntityObject then
* if entityObject.getSnapshot() != null then
* for all links: link.getSnapshot() == null
* || link.getSnapshot().equals(entityObject.getSnapshot())
* </pre>
*/
private HashMap<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> linksForRight;
/**
* Values are the sets of links that have the key as their
* {@link Link<Association, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>#getAssociation() association}. May be an empty set but never <tt>null</tt>.
* It is possible that an {@link Association} is not contained as a key in this map, meaning
* that this {@link Link<Association, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>Container} does not know about any {@link Link<Association, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>}s that have this object
* at their right end. As opposed to {@link #linksForLeft} and {@link #linksForRight}, if
* this map contains an association as a key, this does not mean that this link container
* knows the full extent of the association. Besides, links are currently not keyed
* by snapshot which could result in link collections containing links from multiple different
* snapshots.
*/
private HashMap<LinkMetaObject, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> linksForAssociation;
private HashBag<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> allLinks;
/**
* Keeps the link maps by the end number of the keys. See also {@link Side#endNumber}
*/
private final Map<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>[] mapsByEndNumber;
private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter;
@SuppressWarnings("unchecked")
public SimpleLinkContainerImpl(ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) {
this.modelAdapter = modelAdapter;
linksForLeft = new HashMap<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
linksForRight = new HashMap<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
mapsByEndNumber = (Map<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>[]) new Map<?, ?>[Side.values().length];
mapsByEndNumber[Side.LEFT.endNumber()] = linksForLeft;
mapsByEndNumber[Side.RIGHT.endNumber()] = linksForRight;
linksForAssociation = new HashMap<LinkMetaObject, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
allLinks = new HashBag<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
}
public SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone() {
SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result =
new SimpleLinkContainerImpl<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(getModelAdapter());
result.allLinks = allLinks.clone();
result.linksForAssociation = cloneMapWithLinkCollectionAsValue(linksForAssociation);
result.linksForLeft = cloneMapWithLinkCollectionAsValue(linksForLeft);
result.linksForRight = cloneMapWithLinkCollectionAsValue(linksForRight);
result.mapsByEndNumber[Side.LEFT.endNumber()] = result.linksForLeft;
result.mapsByEndNumber[Side.RIGHT.endNumber()] = result.linksForRight;
return result;
}
private ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() {
return modelAdapter;
}
private <T> HashMap<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> cloneMapWithLinkCollectionAsValue(HashMap<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> map) {
HashMap<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> result = new HashMap<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
for (T key:map.keySet()) {
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> linkCollection = createLinkCollection(key);
linkCollection.addAll(map.get(key));
result.put(key, linkCollection);
}
return result;
}
public void addLink(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) {
for (Side side : Side.values()) {
ObjectAndRemoteAssociationEndIgnoringSnapshot key = new ObjectAndRemoteAssociationEndIgnoringSnapshot(link.get(side),
getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()));
addLinkToMap(link, key, getMapForEnd(side), at);
}
addLinkToMap(link, link.getAssociation(), getLinksForAssociation(), at);
allLinks.add(link);
}
public void removeLink(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at) {
for (Side side : Side.values()) {
ObjectAndRemoteAssociationEndIgnoringSnapshot key = new ObjectAndRemoteAssociationEndIgnoringSnapshot(link.get(side),
getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()));
removeLinkFromMap(link, key, getMapForEnd(side), at);
}
removeLinkFromMap(link, link.getAssociation(), getLinksForAssociation(), at);
allLinks.remove(link);
}
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> all(LinkMetaObject a) {
return Collections.unmodifiableCollection(getLinksForAssociation().get(a));
}
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getAllLinks() {
return Collections.unmodifiableCollection(allLinks);
}
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> navigate(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> from, LinkEndMetaObject toEnd) {
ObjectAndRemoteAssociationEndIgnoringSnapshot key = new ObjectAndRemoteAssociationEndIgnoringSnapshot(from, toEnd);
Side fromSide = getSideOfEnd(toEnd).otherEnd();
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> result = getMapForEnd(fromSide).get(key);
if (result == null) {
result = new ArrayList<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(0);
}
return result;
}
public Map<ObjectAndRemoteAssociationEndIgnoringSnapshot, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> getMapForEnd(Side side) {
return mapsByEndNumber[side.endNumber()];
}
public <T> void addLinkToMap(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, T key, Map<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> map, Integer at) {
if (ObjectAndRemoteAssociationEndIgnoringSnapshot.class.isAssignableFrom(key.getClass())) {
@SuppressWarnings("unchecked")
ObjectAndRemoteAssociationEndIgnoringSnapshot keyCast = (ObjectAndRemoteAssociationEndIgnoringSnapshot) key;
if (keyCast.getObject() instanceof EntityObject<?, ?, ?, ?, ?>) {
EntityObject<?, ?, ?, ?, ?> e = (EntityObject<?, ?, ?, ?, ?>) keyCast.getObject();
if (e.getOrigin() != null) {
assert link.getOrigin() == null || link.getOrigin().equals(e.getOrigin());
}
}
}
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> set = map.get(key);
if (set == null) {
set = createLinkCollection(key);
map.put(key, set);
}
if (at != null && at >= 0 && set instanceof List<?>) {
((List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>) set).add(at, link);
} else {
set.add(link);
}
}
public <T> void removeLinkFromMap(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, T key, Map<T, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> map, Integer at) {
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> set = map.get(key);
if (set != null) {
if (at != null && at >= 0 && set instanceof List<?>) {
((List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>) set).remove(at);
} else {
set.remove(link);
}
}
}
/**
* @param key For type {@link ObjectAndRemoteAssociationEndIgnoringSnapshot}, the resulting collection
* is created based on the orderedness of the respective
* {@link ObjectAndRemoteAssociationEndIgnoringSnapshot#getRemoteAssociationEnd() association end}.
* For type <tt>linkMetaObject</tt>, the collection type is used based on orderedness
* and uniqueness of the ends: if at least one end is orderer, an ordered collection
* is returned; if at least one end is non-unique, a collection supporting duplicates
* is returned.<p>
*
* For all other types of <tt>key</tt>, a {@link LinkedHashSet} is used.
*/
@SuppressWarnings("unchecked")
private Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> createLinkCollection(Object key) {
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> result;
if (ObjectAndRemoteAssociationEndIgnoringSnapshot.class.isAssignableFrom(key.getClass())) {
ObjectAndRemoteAssociationEndIgnoringSnapshot oarae = (ObjectAndRemoteAssociationEndIgnoringSnapshot) key;
result = RunletObject.createCollection(
getModelAdapter().isOrdered(oarae.getRemoteAssociationEnd()),
getModelAdapter().isUnique(oarae.getRemoteAssociationEnd()));
} else {
// TODO This unchecked cast is ugly; and originally there even was an instanceof check which is not possible anymore due to the use of generics; split into two methods, one for LinkMetaObject, one for ObjectAndRemoteAssociationEndIgnoringSnapshot?
LinkMetaObject a = (LinkMetaObject) key;
boolean ordered = false;
boolean unique = true;
for (Side side : Side.values()) {
if (getModelAdapter().isOrdered(getModelAdapter().getEnds(a).get(side.endNumber()))) {
ordered = true;
}
if (!getModelAdapter().isUnique(getModelAdapter().getEnds(a).get(side.endNumber()))) {
unique = false;
}
}
result = RunletObject.createCollection(ordered, unique);
}
return result;
}
public Map<LinkMetaObject, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> getLinksForAssociation() {
return linksForAssociation;
}
Side getSideOfEnd(LinkEndMetaObject end) {
if (getModelAdapter().getEnds(getModelAdapter().getAssociation(end)).get(Side.LEFT.endNumber()).equals(end)) {
return Side.LEFT;
} else {
return Side.RIGHT;
}
}
}