package com.sap.runlet.abstractinterpreter.objects; import java.util.Arrays; import java.util.List; import com.sap.runlet.abstractinterpreter.Side; import com.sap.runlet.abstractinterpreter.repository.RepositoryObject; import com.sap.runlet.abstractinterpreter.repository.SnapshotIdentifier; import com.sap.runlet.abstractinterpreter.util.AssociationService; /** * Represents a link between two objects on behalf of an association. Links are immutable and can * only be created, added to and removed from the {@link LinkContainer link container}. The * {@link RunletObject}s on a link's ends are guaranteed not to be multi-objects. * <p> * * Equality and hash code are based on the equality/hashCode of the two ends as well as the * equality/hashCode of the association of which this link is an instance. * * @author Axel Uhl (D043530) */ public class Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage extends TypeUsage> implements RepositoryObject { private ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>[] ends; private LinkMetaObject association; private SnapshotIdentifier origin; private boolean persistent = false; private AssociationService<LinkMetaObject, LinkEndMetaObject, MetaClass, ClassUsage> associationService; @SuppressWarnings("unchecked") public Link(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> left, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> right, LinkMetaObject association, SnapshotIdentifier snapshot, AssociationService<LinkMetaObject, LinkEndMetaObject, MetaClass, ClassUsage> associationService) { assert snapshot != null; assert left.getOrigin().equals(right.getOrigin()); assert left.getOrigin().equals(snapshot); this.ends = (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>[]) new ClassTypedObject<?, ?, ?>[Side.values().length]; this.ends[Side.LEFT.endNumber()] = left; this.ends[Side.RIGHT.endNumber()] = right; this.association = association; this.origin = snapshot; this.associationService = associationService; } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getLeft() { return ends[Side.LEFT.endNumber()]; } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getRight() { return ends[Side.RIGHT.endNumber()]; } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> get(Side side) { return ends[side.endNumber()]; } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getAtOrderedEnd() { Side orderedSide = getOrderedSide(); if (orderedSide == null) { throw new RuntimeException("Trying to obtain ordered end from link "+this+ " of unordered association "+associationService.getAssociationName(getAssociation())); } return get(orderedSide); } public ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> getAtOppositeOfOrderedEnd() { Side orderedSide = getOrderedSide(); if (orderedSide == null) { throw new RuntimeException("Trying to obtain ordered end from link "+this+ " of unordered association "+associationService.getAssociationName(getAssociation())); } return get(orderedSide.otherEnd()); } /** * If <tt>cto</tt> appears on one side, that side is returned. If it appears on no side, * <tt>null</tt> is returned. If it appears on both sides, one side is arbitrarily chosen and * a non-<tt>null</tt> side is returned. */ public Side getSideOf(ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> cto) { for (Side side:Side.values()) { if (get(side).equals(cto)) { return side; } } return null; } public LinkMetaObject getAssociation() { return association; } @Override public int hashCode() { return association.hashCode() ^ ends[0].hashCode() ^ ends[1].hashCode() ^ ((getOrigin() == null) ? 0 : getOrigin().hashCode()); } @SuppressWarnings("unchecked") @Override public boolean equals(Object o) { if (!(o instanceof Link)) { return false; } Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l = (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o; return association.equals(l.association) && Arrays.equals(l.ends, ends) && (getOrigin() == l.getOrigin() || ((getOrigin() != null) && getOrigin().equals( l.getOrigin()))); } public int logicalHashCode() { return association.hashCode() ^ ends[0].logicalHashCode() ^ ends[1].logicalHashCode(); } @SuppressWarnings("unchecked") public boolean logicallyEquals(Object o) { if (!(o instanceof Link)) { return false; } Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> l = (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o; return association.equals(l.association) && Arrays.equals(l.ends, ends); } /** * Fixing the origin (snapshot) of a link also adjusts the snapshots of the two ends. */ @Override public Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getCopyWithOrigin(SnapshotIdentifier snapshotIdentifier) { Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> linkWithSnapshotAdjusted; if (getOrigin() != snapshotIdentifier) { linkWithSnapshotAdjusted = new Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>( get(Side.LEFT).getCopyWithOrigin(snapshotIdentifier), get(Side.RIGHT).getCopyWithOrigin(snapshotIdentifier), getAssociation(), snapshotIdentifier, associationService); linkWithSnapshotAdjusted.setPersistent(isPersistent()); } else { linkWithSnapshotAdjusted = this; } return linkWithSnapshotAdjusted; } @SuppressWarnings("unchecked") public Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone() { try { return (Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException("Internal error: link should be cloneable: "+this, e); } } @Override public SnapshotIdentifier getOrigin() { return origin; } @Override public boolean isPersistent() { return persistent; } @Override public void setPersistent(boolean persistent) { this.persistent = persistent; } @Override public boolean isUnique() { return associationService.isUnique(associationService.getEnds(getAssociation()).get(0)); } /** * Tells if the association of which this link is an instance has an * {@link Multiplicity#isOrdered() ordered} end. */ public boolean isOrdered() { return getOrderedSide() != null; } /** * Tells which of the two sides of the {@link #getAssociation() association} of which * this link is an instance is ordered. Returns <tt>null</tt> if both ends are unordered. */ public Side getOrderedSide() { List<LinkEndMetaObject> ends = associationService.getEnds(getAssociation()); for (Side side : Side.values()) { if (associationService.isOrdered(ends.get(side.endNumber()))) { return side; } } return null; } @Override public String toString() { return associationService.getAssociationName(getAssociation()) + Arrays.toString(ends); } }