package com.sap.runlet.abstractinterpreter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
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.EmptyObject;
import com.sap.runlet.abstractinterpreter.objects.EntityObject;
import com.sap.runlet.abstractinterpreter.objects.Link;
import com.sap.runlet.abstractinterpreter.objects.MultiValuedObject;
import com.sap.runlet.abstractinterpreter.objects.RunletObject;
import com.sap.runlet.abstractinterpreter.repository.Repository;
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.util.Bag;
import com.sap.runlet.abstractinterpreter.util.HashBag;
import com.sap.runlet.abstractinterpreter.util.ModelAdapter;
/**
* Objects are structurally connected by links which are typed by associations. Each link has two
* ends, at each of which it has one object whose type is a value or entity class. The type of an
* object at an end has to conform with the type specified by the association end's type.
* <p>
*
* A link container is an in-memory cache of links created by an
* {@link RiverInterpreter interpreter} and those loaded from a {@link Repository}. The link
* container keeps track of the entities for which it has authoritative information regarding the
* links attaching to them for a given snapshot and for a given remote association end. It also does
* this for those entities/ends where no links exist in a given snapshot, just to ensure that if
* asked again it knows that the empty link set is the authoritative answer and no round-trip to the
* repository is required.
*
* @author Axel Uhl (D043530)
*/
public abstract class LinkContainer<LinkMetaObject extends EObject,
LinkEndMetaObject extends EObject,
MetaClass extends EObject,
TypeUsage extends EObject,
ClassUsage extends TypeUsage,
StatementType extends EObject,
ExpressionType extends EObject,
SignatureImplementationType extends EObject,
StackFrameType extends StackFrame<LinkEndMetaObject, TypeUsage, ClassUsage, SignatureImplementationType>,
NativeType extends SignatureImplementationType,
InterpreterType extends AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType>> {
/**
* 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 Map<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, 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 Map<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> linksForRight;
/**
* Values are the sets of links that have the key as their
* {@link Link#getAssociation() association}. May be an empty set but never <tt>null</tt>.
* It is possible that an {@link LinkMetaObject} is not contained as a key in this map, meaning
* that this {@link RunletLinkContainer} does not know about any {@link Link}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 Map<LinkMetaObject, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> linksForAssociation;
/**
* Keeps the link maps by the end number of the keys. See also {@link Side#endNumber}<p>
*/
private Map<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>[] mapsByEndNumber;
/**
* Queries that cannot be resolved by the data cached in this link container are
* forwarded to the durably-storing repository. Results are cached, assuming that
* each snapshot in the repository is immutable.
*/
private final Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository;
private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter;
@SuppressWarnings("unchecked")
public LinkContainer(ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter,
Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> repository) {
this.modelAdapter = modelAdapter;
this.repository = repository;
linksForLeft = new HashMap<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
linksForRight = new HashMap<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
mapsByEndNumber = (Map<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, 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>>>();
}
protected ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() {
return modelAdapter;
}
protected Map<ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> getMapForEnd(Side side) {
return mapsByEndNumber[side.endNumber()];
}
protected Repository<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getRepository() {
return repository;
}
protected Map<LinkMetaObject, Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> getLinksForAssociation() {
return linksForAssociation;
}
/**
* Removes a link between the two objects <tt>left</tt> and <tt>right</tt>
* on behalf of the respective association from this {@link RunletLinkContainer}.
* If multiple such links exist (non-unique association end), one of them is
* picked arbitrarily.
*
* @param at
* if a non-</tt>null</tt> value is supplied for an association
* with an ordered end and the association is non-unique,
* <tt>at</tt> specifies which copy of multiple equal links to
* remove. If at least one occurrence of a matching link is found
* but <tt>at</tt> specifies a non-<tt>null</tt> and non-negative
* position that does not match with any of those links, an
* exception is raised.
* @param transactionBuffer
* a non-<tt>null</tt> object; if anything needs to be queried
* from the repository, use the snapshot specified by the buffer
* or use the current one and set it as the transaction buffer's
* fixed snapshot. If the link removed was persistent, this
* change will be recorded in the transaction buffer (see
* {@link TransactionBuffer#linkDeleted(Link, Integer)}
* ). A link is perceived to be persistent if it has a non-
* <tt>null</tt> snapshot or has been recorded in the transaction
* buffer as created.
* @param defaultSnapshot
* if the link is not marked to pertain to a specific snapshot
* (see {@link RepositoryObject#getOrigin()}), consider the
* change to be based on this snapshot identifier. If the
* <tt>transactionBuffer</tt> is not tied to any snapshot yet and
* the link does not pertain to a specific snapshot, the
* <tt>defaultSnapshot</tt> will be used for the transaction
* buffer.
* @param interpreter
* required to perform the hash code change when updating
* <tt>entityOrLink</tt>'s snapshot
*/
public void removeLink(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> left,
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> right,
LinkMetaObject association,
Integer at,
TransactionBuffer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transactionBuffer,
SnapshotIdentifier defaultSnapshot,
AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType>interpreter) {
LinkEndMetaObject rightEnd = getModelAdapter().getEnds(association).get(Side.RIGHT.endNumber());
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> fromLeft = loadLinks(left, Side.LEFT, rightEnd);
if (fromLeft != null) {
LinkEndMetaObject leftEnd = getModelAdapter().getEnds(association).get(Side.LEFT.endNumber());
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> fromRight = loadLinks(right, Side.RIGHT, leftEnd);
if (fromRight != null) {
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> candidates = new HashSet<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(fromLeft);
candidates.retainAll(fromRight);
if (candidates.size() > 0) {
removeLink(candidates.iterator().next(), at, transactionBuffer, defaultSnapshot, interpreter);
}
}
}
}
/**
* Makes sure that links that exist for <tt>from</tt> on <tt>fromSide</tt> in association
* <tt>toEnd.getAssociation()</tt> are loaded and returns them. If this requires fetching them
* from the {@link #getRepository repository}, this will be done.
*/
protected Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> loadLinks(
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> from, Side fromSide, LinkEndMetaObject toEnd) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key = new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(from, toEnd, getModelAdapter());
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> links = getMapForEnd(fromSide).get(key);
if (links == null && ((from instanceof AbstractValueObject<?, ?, ?, ?, ?>) || from.isPersistent())) {
// this container doesn't know anything about this and the from object
// is persistent; ask repository:
links = getRepository().loadLinks(from, toEnd);
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> mutableLinkCollection =
createLinkCollection(key);
if (links != null) {
mutableLinkCollection.addAll(links);
}
// TODO what about adding to the linksForAssociation map?
getMapForEnd(fromSide).put(key, mutableLinkCollection); // now we know
// and cache the result
if (getModelAdapter().isValueType(getModelAdapter().getEndType(toEnd))) {
loadValueLinks(links, fromSide.otherEnd());
}
}
return links;
}
/**
* When a set of links has been loaded during a navigation starting from one object,
* and those links have a value object on their other end, those value objects may
* contain equality-relevant links to other objects. If this link container does not
* yet know the respective emerging link set of the value object, this method will
* establish it, recursively and transitively for all value objects reachable
* through equality-relevant links.<p>
*
* Example: if a link between a <tt>Person</tt> object and an <tt>Address</tt> object
* just got loaded, and <tt>Address</tt> has equality-relevant associations to
* the value class <tt>String</tt> (for role <tt>name</tt>) and to the entity
* class <tt>City</tt>, those two links will be loaded into this link container
* by invoking this method with the link between the <tt>Person</tt> and the
* <tt>Address</tt> instance in <tt>links</tt> and the <tt>address</tt> end
* as <tt>otherEnd</tt>.
*/
@SuppressWarnings("unchecked")
private void loadValueLinks(Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> links,
Side valueClassSide) {
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link:links) {
AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> vo =
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) link.get(valueClassSide);
loadEqualityRelevantLinksOfValue(vo);
}
}
/**
* Add <tt>link</tt> to the directed link maps and the association-keyed map. This does
* not represent a logical <em>link creation</em> but rather loads an already existing
* link into this link container's in-memory structures. It does not verify any constraints
* nor does it throw any events. Links are added to the link collections at the default
* position (e.g., append for ordered ends), so the client has to ensure by the sequence
* of the calls to this method that the correct order is maintained.
*/
private void loadLink(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) {
for (Side side : Side.values()) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key =
new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(link.get(side),
getModelAdapter().getEnds(link.getAssociation())
.get(side.otherEndNumber()), getModelAdapter());
// If a persistent link collection for one end exists but hasn't been loaded yet,
// it must be loaded first; otherwise a new empty collection would be created which
// would then probably overwrite the existing one
loadLinks(link.get(side), side, getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()));
addLinkToMap(link, key, getMapForEnd(side),
/* use default insert position, e.g., append */ null);
}
addLinkToMap(link, link.getAssociation(), getLinksForAssociation(),
/* use default insert position, e.g., append */ null);
}
/**
* Creates a link between the two objects <tt>left</tt> and <tt>right</tt>
* on behalf of the respective association and adds it to this
* {@link RunletLinkContainer}. If the {@link RepositoryObject#getOrigin()
* snapshots} of <tt>left</tt> and <tt>right</tt> differ, an attempt is made
* to unify them, as follows: if the link is equality-relevant only for one
* side and the other side is a {@link ValueObject value object}, that other
* side's value object will be cloned into the snapshot of the object for
* which the link is equality-relevant. For example, if a link between
* <tt>Address</tt> and <tt>String</tt> is to be established that is
* equality-relevant only for the <tt>Address</tt> side, the <tt>String</tt>
* value object will be cloned into the <tt>Address</tt> object's snapshot,
* and the link will be established in that snapshot.
* <p>
*
* If <tt>left</tt>'s and <tt>right</tt>'s snapshots differ and no-one is a
* value for which the link is not equality-relevant, an exception is
* thrown.
*
* @param at
* if a non-<tt>null</tt> value is supplied for an association
* with one ordered end, <tt>at</tt> is used as the insert position.
* It <tt>-1</tt> or <tt>null</tt> is supplied, the link will be
* appended to the end. Note that there is no total ordering on
* all the links of an association but only a semi-ordering over
* those links emerging from one object with the remote end being
* ordered.
* @param transactionBuffer
* a non-<tt>null</tt> object; if the link added should be
* persistent, it will be added to the transaction buffer (see
* {@link TransactionBuffer#linkCreated(Link)}). A link is
* perceived to be persistent, if any of the two objects
* connected by it is an entity object that is persistent. Inc
* ase of composite links, if the link is considered persistent,
* the composite child including its composition hierarchy and
* attaching links will be made persistent if the are not
* persistent yet.
* @param interpreter
* required to perform the hash code change when updating
* <tt>entityOrLink</tt>'s snapshot
*/
public void addLinkFromObjectsForEnds(
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> left,
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> right,
LinkMetaObject association,
Integer at,
TransactionBuffer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transactionBuffer,
InterpreterType interpreter) {
if (!left.getOrigin().equals(right.getOrigin())) {
if ((getModelAdapter().isContributesToEquality(getModelAdapter().getEnds(association).get(Side.LEFT.endNumber())) &&
right instanceof AbstractValueObject<?, ?, ?, ?, ?>)) {
right = right.getCopyWithOrigin(left.getOrigin());
} else if (getModelAdapter().isContributesToEquality(getModelAdapter().getEnds(association).get(Side.RIGHT.endNumber())) &&
left instanceof AbstractValueObject<?, ?, ?, ?, ?>) {
left = left.getCopyWithOrigin(right.getOrigin());
} else {
throw new RuntimeException("Can't establish link between objects in different snapshots: "+
left.getOrigin()+" and "+right.getOrigin());
}
}
assert left.getOrigin().equals(right.getOrigin());
Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link = new Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(left, right, association, left.getOrigin(), getModelAdapter());
addLink(link, at, transactionBuffer, interpreter);
}
@SuppressWarnings("unchecked")
private <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 (key instanceof ObjectAndRemoteAssociationEnd) {
if (((ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>)key).getObject() instanceof EntityObject) {
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> e =
(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>)
((ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>) key).getObject();
if (e.getOrigin() != null) {
// no cross-snapshot links
if (link.getOrigin() != null && !link.getOrigin().equals(e.getOrigin())) {
throw new RuntimeException("Cross snapshot links not allowed");
}
}
}
}
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);
}
}
private boolean doesLinkExist(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key =
new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(
link.get(Side.LEFT),
getModelAdapter().getEnds(link.getAssociation()).get(Side.RIGHT.endNumber()),
getModelAdapter());
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> existingLinks =
getMapForEnd(Side.LEFT).get(key);
return existingLinks != null && existingLinks.contains(link);
}
@SuppressWarnings("unchecked")
private void addLink(final Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>link, Integer at,
TransactionBuffer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transactionBuffer,
InterpreterType interpreter) {
// TODO check multiplicities?
// TODO check aggregation relevance (will "child" move to parent's aggregate?)
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> currentCompositeParent = getCompositeParentForChild(link);
if (!getModelAdapter().isUnique(getModelAdapter().getEnds(link.getAssociation()).get(0))
// if the association is unique, check if the link already exists
|| !doesLinkExist(link)) {
if (currentCompositeParent != null) {
throw new RuntimeException(
"Illegal attempt to establish a second composite parent link for composite child "
+ link.get(getCompositeParentSide(link).otherEnd())
+ " which is already owned by " + currentCompositeParent);
}
for (final Side side : Side.values()) {
if (getModelAdapter().isContributesToEquality(
getModelAdapter().getEnds(link.getAssociation()).get(side.endNumber())) &&
getModelAdapter().isValueType(
getModelAdapter().getEndType(
getModelAdapter().getEnds(link.getAssociation()).get(side.endNumber())))) {
throw new RuntimeException("Illegal attempt to modify a value object. Trying to establish link "+
link);
}
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key = new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(link
.get(side), getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()),
getModelAdapter());
// If a persistent link collection for one end exists but hasn't been loaded yet,
// it must be loaded first; otherwise a new empty collection would be created which
// would then probably overwrite the existing one
loadLinks(link.get(side), side, getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()));
addLinkToMap(link, key, getMapForEnd(side), at);
}
addLinkToMap(link, link.getAssociation(), getLinksForAssociation(), at);
// handle persistence and transaction buffer aspects:
if (shouldBeStoredToRepository(link)) {
if (link.isOrdered() && (at == null || at == -1)) {
at = getLinkPosition(link); // for addition at default position, determine position
}
transactionBuffer.linkCreated(link, at);
Side compositeParentSide = getCompositeParentSide(link);
if (compositeParentSide != null) {
// recurse into composition and mark composite children and all attaching links
// as created in persistence
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> child =
(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) link.get(compositeParentSide.otherEnd());
if (!child.isPersistent()) {
interpreter.storeEntity(child);
}
}
}
}
}
/**
* Determines if and which of the two sides of the <tt>link</tt> is a composite parent end
* of a composite association. If none is, <tt>null</tt> is returned; otherwise, the side
* on which the association end marked as {@link LinkEndMetaObject#isComposite()} resides is returned.
*/
private Side getCompositeParentSide(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) {
Side compositeParentSide = null;
int i=0;
List<LinkEndMetaObject> ends = getModelAdapter().getEnds(link.getAssociation());
for (Side side:Side.values()) {
if (getModelAdapter().isComposite(ends.get(side.endNumber()))) {
compositeParentSide = side;
break;
}
i++;
}
return compositeParentSide;
}
/**
* Computes a bag of {@link RepositoryObject}s containing <tt>toSave</tt>
* and all of its composite children that are transient so far and all
* transient links attached to any of those. This also captures links to
* value objects that are equality-relevant for the <tt>toSave</tt> entity.
* The value objects are referenced by the respective link.
* Equality-relevant links emerging from those value objects are further
* traversed to find other entity objects that are relevant for the value's
* equality and are added to the result if not yet persistent. Value objects
* are expected to describe its value by themselves.
* <p>
* The resulting bag is unordered, hence links may be returned by an
* iterator before the entities linked by it.
* <p>
*
* <tt>toSave</tt> is contained in the resulting bag.
*/
@SuppressWarnings("unchecked")
public Bag<RepositoryObject> getTransientCompositionTree(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> toSave) {
Bag<RepositoryObject> result = new HashBag<RepositoryObject>();
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> allLinks = getAllLinks(toSave);
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> children = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l:allLinks) {
Side compositeParentSide = getCompositeParentSide(l);
if (compositeParentSide != null && l.get(compositeParentSide).equals(toSave)) {
// cast is ok because composition is not possible for values
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> child =
(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) l.get(
compositeParentSide.otherEnd());
// check if the object is transient
if (!child.isPersistent()) {
children.add(child);
// the child will add the composite parent link l to its results
} else {
if (!l.isPersistent()) {
result.add(l);
}
}
} else {
// not a composite link; add link if it leads to an entity or if it is equality-relevant for toSave
Side sideOfToSave = l.getSideOf(toSave);
LinkEndMetaObject toSaveEnd = getModelAdapter().getEnds(l.getAssociation()).get(sideOfToSave.endNumber());
boolean otherEndIsValueType = getModelAdapter().isValueType(
getModelAdapter().getEndType(getModelAdapter().otherEnd(toSaveEnd)));
if (!l.isPersistent() &&
(!otherEndIsValueType || getModelAdapter().isContributesToEquality(toSaveEnd))) {
result.add(l);
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtOtherEnd = l.get(sideOfToSave.otherEnd());
if (otherEndIsValueType) {
result.addAll(getTransientEntitiesEqualityRelevantForValueRecursively(
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) objectAtOtherEnd));
}
}
}
}
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> child:children) {
result.addAll(getTransientCompositionTree(child));
}
result.add(toSave);
return result;
}
/**
* For the value object <tt>objectAtOtherEnd</tt> checks all objects associated with it that
* are relevant for its equality. If again a value object, the results of recursively calling
* this method for the nested value are added to the results. If an entity object that is not yet
* marked persisted, that entity object is passed to {{@link #getTransientCompositionTree(EntityObject)}}
* again to transitively grab all transient objects that need to be stored with the value. Note
* that the value object itself is <em>not</em> added to the result.
*/
@SuppressWarnings("unchecked")
private Bag<RepositoryObject> getTransientEntitiesEqualityRelevantForValueRecursively(
AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> objectAtOtherEnd) {
Bag<RepositoryObject> result = new HashBag<RepositoryObject>();
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> equalityRelevantObjects =
objectAtOtherEnd.getEqualityRelevantAssociationEndValues();
for (Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> coll : equalityRelevantObjects.values()) {
for (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> cto : coll) {
if (cto instanceof AbstractValueObject) {
result.addAll(getTransientEntitiesEqualityRelevantForValueRecursively((AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) cto));
} else {
// entity object
if (!cto.isPersistent()) {
result.addAll(getTransientCompositionTree(
(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) cto));
}
}
}
}
return result;
}
/**
* Computes a bag of {@link RepositoryObject}s containing <tt>object</tt> and all of its
* composite children and all links attached to any of those.
*/
@SuppressWarnings("unchecked")
public Bag<RepositoryObject> getCompositionTree(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> object) {
Bag<RepositoryObject> result = new HashBag<RepositoryObject>();
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> allLinks = getAllLinks(object);
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> children = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>l : allLinks) {
Side compositeParentSide = getCompositeParentSide(l);
if (compositeParentSide != null && l.get(compositeParentSide).equals(object)) {
// cast is ok because composition is not possible for values
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> eo =
(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) l.get(
compositeParentSide.otherEnd());
// check if the object is transient
children.add(eo);
// the child will add the composite parent link l to its results
} else {
// not a composite link; need to add it to the results here
result.add(l);
}
}
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> child : children) {
result.addAll(getCompositionTree(child));
}
result.add(object);
return result;
}
/**
* Obtains all links known to this link container attached to object <tt>o</tt>. Reflexive
* links having the same object on both ends are added only once per occurrence. Of course,
* non-unique links of this kind still occur as many times as they were added.
*/
private List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getAllLinks(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) {
List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> result =
new LinkedList<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
for (LinkEndMetaObject end:getModelAdapter().getConformsToAssociationEnds(
getModelAdapter().getClazz(o.getType()))) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key =
new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(
o, getModelAdapter().otherEnd(end),
getModelAdapter());
Side sideOfEnd = getModelAdapter().getSideOfEnd(end);
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> links = getMapForEnd(sideOfEnd).get(key);
if (links != null) {
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l : links) {
// add them, avoiding adding reflexive links twice
if (sideOfEnd.equals(Side.LEFT) || !l.get(Side.LEFT).equals(l.get(Side.RIGHT))) {
result.add(l);
}
}
}
}
return result;
}
/**
* Checks if a link that is in the process of being added to this link container should
* be stored in the {@link #repository}. This is the case if at least one of the two ends
* is an {@link EntityObject entity object} that is already stored in the repository (indicated
* by a non-<tt>null</tt> snapshot) or is marked for storage in the <tt>transactionBuffer</tt>, and
* if the link is equality-relevant for that entity. Note, that with this definition links to
* value objects are not considered to need storing if they are not relevant for the entity's
* equality.
*
* @param link
* the link for which to check whether it should be stored persistently in the
* repository
*/
private boolean shouldBeStoredToRepository(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) {
for (Side side : Side.values()) {
if (link.get(side) instanceof EntityObject<?, ?, ?, ?, ?> && link.get(side).isPersistent()) {
if (getModelAdapter().isContributesToEquality(
getModelAdapter().getEnds(link.getAssociation()).get(side.endNumber()))) {
return true;
}
}
}
return false;
}
/**
* Finds all child ends of composite associations such that <tt>child</tt>'s class
* conforms to the association end's class, meaning that it could potentially participate
* on that end in an association. Those associations are queried. If a link is found,
* the object at the composite parent end is returned for the first such link found.
* Otherwise, <tt>null</tt> is returned. Navigation happens within
* {@link ClassTypedObject#getOrigin() child's snapshot}.
*/
@SuppressWarnings("unchecked")
private EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getCompositeParent(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> child) {
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> result = null;
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> oarae = getCompositeParentObjectAndChildAssociationEnd(child);
if (oarae != null) {
result = oarae.getObject();
}
return (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) result;
}
/**
* If the link is of a non-composite association (an association with no end marked
* {@link LinkEndMetaObject#isComposite()}), the result is always <tt>null</tt>. If one
* end is marked composite, this method looks for another composite link emerging from
* the composite child of <tt>link</tt>. If such a link is found, the result is the
* parent object of the link found. During finding such a link, all associations are
* considered that contain a composite end and where <tt>link</tt>'s child's class
* conforms to the child end's class of the link found. Navigation happens within
* {@link ClassTypedObject#getOrigin() the child's snapshot}.
*/
private RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> getCompositeParentForChild(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>link) {
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> result = null;
Side compositeParentSide = getCompositeParentSide(link);
if (compositeParentSide != null) {
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> child = link.get(compositeParentSide.otherEnd());
result = getCompositeParent(child);
}
return result;
}
/**
* Ensures that all links that establish the value object <tt>vo</tt> are loaded into
* this link container. This includes all equality-relevant links of <tt>vo</tt> itself
* as well as all equality-relevant links of all value objects that contribute to
* <tt>vo</tt>'s value.
*/
@SuppressWarnings("unchecked")
public void loadEqualityRelevantLinksOfValue(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> vo) {
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> equalityRelevant = vo
.getEqualityRelevantAssociationEndValues();
for (LinkEndMetaObject farEnd : equalityRelevant.keySet()) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key = new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(vo, farEnd, getModelAdapter());
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> linkCollection =
getMapForEnd(getModelAdapter().getSideOfEnd(getModelAdapter().otherEnd(farEnd))).get(key);
// if the link collection already exists, we assume it's up to date
if (linkCollection == null) {
// in this case we'll insert all those links
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> equalityRelevantLinks = vo.getEqualityRelevantLinks(farEnd);
boolean farEndIsValueType = getModelAdapter().isValueType(getModelAdapter().getEndType(farEnd));
Side farSide = getModelAdapter().getSideOfEnd(farEnd);
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> erl : equalityRelevantLinks) {
loadLink(erl);
if (farEndIsValueType) {
loadEqualityRelevantLinksOfValue(
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) erl.get(farSide));
}
}
}
}
}
private <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).get(at).equals(link)) {
throw new RuntimeException("Requested removal of link "+link+
" from specific position "+at+" but found "+
((List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>) set).get(at)+" at that position");
}
// TODO respect *at* parameter in case of list
if (set instanceof List<?> && at != null && at >= 0) {
((List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>) set).remove(at);
} else {
set.remove(link);
}
// Note: don't remove empty collections from map for persistent keys because they still have a meaning.
// They state that the link container is aware of the fact that this link set is empty. This
// avoids loading a "clean" link set from the repository which would be wrong after modifications
// such as a removal took place on the link set.
if (set.isEmpty()) {
if (key instanceof ObjectAndRemoteAssociationEnd<?, ?, ?, ?, ?>) {
ObjectAndRemoteAssociationEnd<?, ?, ?, ?, ?> oarae = (ObjectAndRemoteAssociationEnd<?, ?, ?, ?, ?>) key;
if (!oarae.getObject().isPersistent()) {
map.remove(key);
}
}
}
}
}
protected void removeLink(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link, Integer at,
TransactionBuffer<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> transactionBuffer,
SnapshotIdentifier defaultSnapshot,
AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, StatementType, ExpressionType, SignatureImplementationType, StackFrameType, NativeType, InterpreterType> interpreter) {
// TODO check multiplicities (underflow)?
for (Side side:Side.values()) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key = new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(link.get(side),
getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()),
getModelAdapter());
if (getModelAdapter().isContributesToEquality(
getModelAdapter().getEnds(link.getAssociation()).get(side.endNumber()))
&& getModelAdapter().isValueType(
getModelAdapter().getEndType(getModelAdapter().getEnds(link.getAssociation()).get(side.endNumber())))) {
throw new RuntimeException(
"Illegal attempt to modify a value object. Trying to remove link "
+ link);
}
// If a persistent link collection for one end exists but hasn't been loaded yet,
// it must be loaded first; otherwise a new empty collection would be created which
// would then probably overwrite the existing one
loadLinks(link.get(side), side, getModelAdapter().getEnds(link.getAssociation()).get(side.otherEndNumber()));
removeLinkFromMap(link, key, getMapForEnd(side), at);
}
removeLinkFromMap(link, link.getAssociation(), getLinksForAssociation(), at);
// TODO if link is composite, decide if composite children tree should be made transient
if (link.isPersistent()) {
transactionBuffer.linkDeleted(link, at);
}
}
/**
* Returns a (possible multi-valued, possibly <tt>null</tt>) object resulting from navigating
* the association <tt>over</tt> from <tt>from</tt> on <tt>fromSide</tt> of that
* association to the other end. The collection will be empty if no links exist that match these
* criteria. The collection's type corresponds with the other association end's (the "to" end's)
* uniqueness and orderedness settings. Navigation happens within
* {@link ClassTypedObject#getOrigin() from's snapshot}.
* <p>
*
* If <tt>from</tt> is <tt>null</tt>, <tt>null</tt> will result.
*/
public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> navigate(
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> from,
Side fromSide,
LinkMetaObject over) {
LinkEndMetaObject otherEnd = getModelAdapter().getEnds(over).get(fromSide.otherEndNumber());
ClassUsage toEndType = getModelAdapter().getClassUsage(otherEnd);
Collection<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> resultCollection = RunletObject.
createCollection(getModelAdapter().isOrdered(otherEnd), getModelAdapter().isUnique(otherEnd));
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> result;
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> links = loadLinks(from, fromSide, otherEnd);
if (links != null) {
for (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link : links) {
resultCollection.add(link.get(fromSide.otherEnd()));
}
}
if (!getModelAdapter().isMany(otherEnd)) {
if (resultCollection.size() > 0) {
result = resultCollection.iterator().next();
} else {
result = new EmptyObject<LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(toEndType, getModelAdapter());
}
} else {
result = new MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage>(toEndType, resultCollection,
getModelAdapter().isOrdered(otherEnd), getModelAdapter().isUnique(otherEnd));
}
return result;
}
public ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> getCompositeParentObjectAndChildAssociationEnd(
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> child) {
// look for existing composite links emerging from the child of the new composite link
for (LinkEndMetaObject compositeChildEnd : getModelAdapter().getConformsToCompositeChildAssociationEnds(
getModelAdapter().getClazz(child.getType()))) {
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> parent = navigate(child,
getModelAdapter().getSideOfEnd(compositeChildEnd),
getModelAdapter().getAssociation(compositeChildEnd));
if (parent != null && !parent.isEmpty()) {
return new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>((ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>) parent.iterator().next(),
compositeChildEnd, getModelAdapter());
}
}
return null;
}
/**
* @param key For type {@link ObjectAndRemoteAssociationEnd}, the resulting collection
* is created based on the orderedness of the respective
* {@link ObjectAndRemoteAssociationEnd#getRemoteAssociationEnd() association end}.
* For type {@link LinkMetaObject}, the collection type is used based on orderedness
* and uniqueness of the ends: if at least one end is ordered, 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 (key instanceof ObjectAndRemoteAssociationEnd) {
ObjectAndRemoteAssociationEnd<?, ? extends LinkEndMetaObject, ?, ?, ?> oarae = (ObjectAndRemoteAssociationEnd<?, ? extends LinkEndMetaObject, ?, ?, ?>) key;
result = RunletObject.createCollection(getModelAdapter().isOrdered(oarae.getRemoteAssociationEnd()),
getModelAdapter().isUnique(oarae.getRemoteAssociationEnd()));
} else {
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;
}
/**
* Tells this link container that there are no links for this object yet, and that the link
* container does not need to look up any such links in the persistence {@link #repository}. It
* does so by inserting empty link collections into the directed link maps for all association
* ends in which the <tt>object</tt> could potentially appear.
*/
public void newEntity(EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> object) {
for (LinkEndMetaObject ae:getModelAdapter().getConformsToAssociationEnds(
getModelAdapter().getClazz(object.getType()))) {
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> key =
new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(
object, getModelAdapter().otherEnd(ae),
getModelAdapter());
getMapForEnd(getModelAdapter().getSideOfEnd(ae)).put(key, createLinkCollection(key));
}
}
public Iterable<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> removeUnchangedEntities(Map<Snapshot, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> entitiesFromSnapshot) {
// create subsets for each entityObject
Map<UUID, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>> entitySets = new HashMap<UUID, Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>>();
for (Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> entities : entitiesFromSnapshot.values()) {
for (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity : entities) {
if (!entitySets.containsKey(entity.getId())) {
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> set = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
set.add(entity);
entitySets.put(entity.getId(), set);
}
else {
entitySets.get(entity.getId()).add(entity);
}
}
}
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> result = new HashSet<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>();
// loop over subsets and remove unchanged entities from different snapshots
for (UUID uuid : entitySets.keySet()) {
Set<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> set = entitySets.get(uuid);
if (set.size() > 1) { // only if there are more than one entity versions
List<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> entities = new ArrayList<EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>(set.size());
entities.addAll(set);
Collections.sort(entities, new SnapshotComparator());
for (int i = 0; i < entities.size(); i++) {
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> entity = entities.get(i);
if (i == 0) {
result.add(entity);
}
else {
int predecessorIndex = i - 1;
while (predecessorIndex >= 0) {
EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> predecessor = entities.get(predecessorIndex);
Set<Snapshot> predecessorSnapthots = getRepository().getImmediatePredecessors(entity.getOrigin().getSnapshot());
if (predecessorSnapthots.contains(predecessor.getOrigin().getSnapshot())) {
if (!entity.contentEquals(predecessor)) { // changed?
result.add(entity);
break;
}
}
--predecessorIndex;
} // while (true)
}
} // for all entities[]
} // if set.size() > 1
else {
// just one entity found, add to result
result.addAll(set);
}
} // for all uuids
return result;
}
public class SnapshotComparator implements Comparator<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>> {
@Override
public int compare(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> o1, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> o2) {
return o1.getOrigin().getSnapshot().when().compareTo(
o2.getOrigin().getSnapshot().when());
}
}
/**
* Determines the position of the <tt>link</tt> which is part of an ordered association with respect
* to the object at its unordered end.
*/
public int getLinkPosition(Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link) {
Side orderedSide = link.getOrderedSide();
LinkEndMetaObject orderedEnd = getModelAdapter().getEnds(link.getAssociation()).get(orderedSide.endNumber());
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtUnorderedEnd = link.get(orderedSide.otherEnd());
ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass> oarae = new ObjectAndRemoteAssociationEnd<LinkMetaObject, LinkEndMetaObject, TypeUsage, ClassUsage, MetaClass>(objectAtUnorderedEnd, orderedEnd,
getModelAdapter());
List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> embeddedInList = (List<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>>) getMapForEnd(orderedSide.otherEnd()).get(oarae);
return embeddedInList.indexOf(link);
}
}