package com.sap.runlet.abstractinterpreter.objects; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import java.util.UUID; import org.eclipse.emf.ecore.EObject; import com.sap.runlet.abstractinterpreter.AbstractRunletInterpreter; import com.sap.runlet.abstractinterpreter.repository.SnapshotIdentifier; import com.sap.runlet.abstractinterpreter.util.ModelAdapter; import com.sap.runlet.abstractinterpreter.util.Tuple.Pair; /** * Represents an object that is instance of a entity class. It is only a container for an ID * uniquely identifying the object to be referenced by association links, as well as a reference to * its type. Both, type and ID are immutable. * <p> * * Hash code and equals are implemented based on the ID. * * @author Axel Uhl (D043530) */ public class EntityObject<LinkMetaObject extends EObject, LinkEndMetaObject extends EObject, MetaClass extends EObject, TypeUsage extends EObject, ClassUsage extends TypeUsage> extends ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> { private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter; private final AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, ?, ?, ?, ?, ?, ?> interpreter; private final UUID id; /** * Generates a new UUID for the object. To be used when a new object is constructed that * requires a new identity. * @param modelAdapter TODO * @param interpreter TODO */ public EntityObject(ClassUsage type, SnapshotIdentifier snapshot, ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter, AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, ?, ?, ?, ?, ?, ?> interpreter) { super(type, snapshot); this.modelAdapter = modelAdapter; this.interpreter = interpreter; id = UUID.randomUUID(); } /** * Constructs an object based on a previously known ID, such as when loading the object from * some persistent representation. * @param modelAdapter TODO * @param interpreter TODO */ public EntityObject(ClassUsage type, UUID id, SnapshotIdentifier snapshot, ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter, AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, ?, ?, ?, ?, ?, ?> interpreter) { super(type, snapshot); this.modelAdapter = modelAdapter; this.interpreter = interpreter; this.id = id; } private AbstractRunletInterpreter<MetaClass, TypeUsage, ClassUsage, LinkMetaObject, LinkEndMetaObject, ?, ?, ?, ?, ?, ?> getInterpreter() { return interpreter; } private ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() { return modelAdapter; } public UUID getId() { return id; } @SuppressWarnings("unchecked") public EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone() { return (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) super.clone(); } /** * Computes a hash code based on ID and snapshot, consistent with {@link #equals} */ public int hashCode() { return id.hashCode() ^ (( getOrigin() == null) ? 0 : getOrigin().hashCode() ); } /** * Checks equality of ID and snapshot */ @SuppressWarnings("unchecked") public boolean equals(Object o) { if (!(o instanceof RunletObject)) { return false; } Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> oIter = ((RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>) o).iterator(); if (!oIter.hasNext()) { return false; } RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> oro = oIter.next(); if (oIter.hasNext() || !(oro instanceof EntityObject)) { return false; } if (this == oro) { return true; } EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> other = (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) oro; return getId().equals(other.getId()) && (getOrigin() == other.getOrigin() || ((getOrigin() != null) && getOrigin().equals(other.getOrigin()) ) ); } /** * Content-wise, two entities equal each other if all the objects reached by navigating all * equality-relevant associations are {@link #contentEquals} recursively to those objects reached when * navigating the same association starting from <tt>o</tt>. The snapshot is not considered in this * comparison. */ @SuppressWarnings("unchecked") public boolean contentEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) { if (this == o) { return true; } Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> oIter = o.iterator(); if (!oIter.hasNext()) { return false; } RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> oro = oIter.next(); if (oIter.hasNext() || !(oro instanceof EntityObject)) { return false; } if (this == oro) { return true; } return !isDifferentFrom((EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o, new HashSet<Pair<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>>()); } /** * Logically, two entities equal each other if they have equal IDs. The {@link #getOrigin() snapshot} * to which they pertain does not matter for this comparison.<p> */ @SuppressWarnings("unchecked") @Override public boolean logicallyEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) { if (this == o) { return true; } Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> oIter = o.iterator(); if (!oIter.hasNext()) { return false; } RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> oro = oIter.next(); if (oIter.hasNext() || !(oro instanceof EntityObject)) { return false; } if (this == oro) { return true; } return getId().equals(((EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) oro).getId()); } /** * Returns <code>true</code> if the two objects are different. RiverObjects are always different * if they have different concrete subclasses. Otherwise they are compared with respect to their * content but ignoring snapshot containment. */ @SuppressWarnings("unchecked") private boolean areDifferent(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o1, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o2, Set<Pair<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> alreadyCompared) { Pair<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> pair = new Pair<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(o1, o2); if (alreadyCompared.contains(pair)) { return false; // if a pair has already been investigated or is currently under investigation, we // assume by default that they are not different; if they still are, some invocation up // the call stack will return true. } alreadyCompared.add(pair); if (!o1.getClass().equals(o2.getClass())) { return true; } if (o1 instanceof EntityObject) { return ((EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o1).isDifferentFrom( (EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o2, alreadyCompared); } if (o1 instanceof AbstractValueObject) { return areDifferent((AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o1, (AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) o2); } if (o1 instanceof MultiValuedObject) { MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage> m1 = (MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage>) o1; MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage> m2 = (MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage>) o2; // must be different if size differs if (m1.size() != m2.size()) { return true; } // not different if size 0 else if (m1.size() == 0) { return false; } Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i1 = m1.flatten().iterator(); Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i2 = m2.flatten().iterator(); while (i1.hasNext()) { if (areDifferent(i1.next(), i2.next(), alreadyCompared)) { return true; } } return false; } throw new RuntimeException("Illegal class in 'areDifferent': " + o1.getClass().getName()); } /** * Delegates to {@link ValueObject#logicallyEquals(RunletObject)}. * @param v1 * @param v2 * @return */ private boolean areDifferent(AbstractValueObject<?, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> v1, AbstractValueObject<?, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> v2) { return v1.logicallyEquals(v2); } /** * Returns <code>true</code> if two copies of the same entity are different. An entity from the same * snapshot is always equal to itself. If two versions from different snapshots * are compared, the entities are equal if all outgoing associations are logically * equal along the composition hierarchy. * * @precondition this and e2 are the same entity instance * @param e2 * {@link EntityObject} to compare * @param alreadyCompared * @return <code>true</code> if the {@link EntityObject}s there are changes from e1 to e2, * <code>false</code> otherwise. */ public boolean isDifferentFrom( EntityObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> e2, Set<Pair<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>, RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> alreadyCompared) { // if both entities have the same ID and come from the same snapshot they have to be equal as well if (this.getId().equals(e2.getId()) && this.getOrigin().equals(e2.getOrigin())) { return false; } for (LinkEndMetaObject ae : getModelAdapter().getEqualityRelevantAssociationEnds(getModelAdapter().getClazz(this.getType()))) { LinkEndMetaObject otherEnd = getModelAdapter().otherEnd(ae); RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o1 = getInterpreter().navigate( this, getModelAdapter().getSideOfEnd(otherEnd).otherEnd(), getModelAdapter().getAssociation(otherEnd)); RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o2 = getInterpreter().navigate( e2, getModelAdapter().getSideOfEnd(otherEnd).otherEnd(), getModelAdapter().getAssociation(otherEnd)); if (!o1.logicallyEquals(o2)) { // FIXME what about endless recursion issues here? return true; } if (getModelAdapter().isComposite(ae)) { if (areDifferent(o1, o2, alreadyCompared)) { return true; } } } return false; } @Override public int hashCodeWithinSnapshot() { return this.logicalHashCode(); } @Override public int logicalHashCode() { return id.hashCode(); } @Override public String toString() { StringBuilder sb = new StringBuilder("Entity "); sb.append(getId()); sb.append(" of class "); sb.append(getModelAdapter().getClassUsageName(getType())); sb.append(" @ "); sb.append(getOrigin()); return sb.toString(); } @Override public boolean isUnique() { return true; } }