package com.sap.runlet.abstractinterpreter.objects;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.Map;
import com.sap.runlet.abstractinterpreter.LinkContainer;
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.ModelAdapter;
/**
* Represents an object that is instance of a value class. Key functionality
* of a value object is that it can be cloned, e.g., to produce a copy which then
* can have composite parts replaced as compared to the original.<p>
*
* Equality/hash code is based on the class of this object (formally:
* <tt>getType().getClazz()</tt>) and all the values of those association ends whose
* other end attaches to this class and is marked as contributing to equality. It would
* not be possible to determine those association links from the {@link LinkContainer}
* because the link container requires the equality definition already to look up
* this object in its data structures. Therefore, the equality-relevant association
* end values need to be replicated into this object. This redundancy is managed by
* the {@link LinkContainer} alone.<p>
*
* @author Axel Uhl (D043530)
*/
public class AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage extends TypeUsage>
extends ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> implements Cloneable {
/**
* Maintains the immutable equality-related association end values ("properties") for this
* value object. Keys are the association ends on the far end; values are the
* collections (respecting ordering and uniqueness properties of the association end)
* containing the values that correspond with the currently existing links for
* the association.<p>
*
* None of the value collections is empty. If removing a link would turn the collection
* empty, the whole entry for the key will be removed.
*/
private Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> equalityRelevantAssociationEndValues;
private final ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter;
/**
* Constructs an object representing a value. Values are characterized by the combination of the
* values of the opposite ends of those association ends attaching to this object's class and
* marked as contributing to equality {@see AssociationEnd#isContributesToEquality()}.
* @param getModelAdapter() TODO
* @param interpreter
* needed to evaluate any actual object parameter expressions on the class type
* definition <tt>type</tt>
*/
protected AbstractValueObject(ClassUsage type, SnapshotIdentifier snapshot,
ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) {
super(type, snapshot);
this.modelAdapter = modelAdapter;
equalityRelevantAssociationEndValues = Collections.emptyMap();
}
/**
* Constructs an object representing a value, initializing its equality-defining properties
* which from then on are immutable. Values are generally characterized by the combination of
* the values of the opposite ends of those association ends attaching to this object's class
* and marked as contributing to equality {@see AssociationEnd#isContributesToEquality()}.
*
* @param interpreter
* needed to evaluate any actual object parameter expressions on the class type
* definition <tt>type</tt>
*/
public AbstractValueObject(ClassUsage type,
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> propertyValues,
SnapshotIdentifier snapshot,
ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> modelAdapter) {
this(type, snapshot, modelAdapter);
equalityRelevantAssociationEndValues = Collections.unmodifiableMap(propertyValues);
}
/**
* Creates a clone of this value object by copying the references to all attributes
* including the type definition, native objects for potential <tt>NativeObject</tt> subclasses
* as well as the map that contains the equality-relevant "property" values and the
* {@link ClassTypedObject#getOrigin() snapshot}. The clone returned may, e.g., get
* its snapshot altered without this object having its snapshot altered. However,
* it is not permissible to try to write into {@link #equalityRelevantAssociationEndValues}
* because it would change the equality/hashCode definitions for this value object.
*/
@SuppressWarnings("unchecked")
public AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> clone() {
AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> result =
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) super.clone();
return result;
}
protected ModelAdapter<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> getModelAdapter() {
return modelAdapter;
}
public Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> getEqualityRelevantAssociationEndValues() {
return Collections.unmodifiableMap(equalityRelevantAssociationEndValues);
}
/**
* From the {@link #equalityRelevantAssociationEndValues} map creates a set of links that a
* {@link LinkContainer} may load and that establish (part of) this value from the link
* container's perspective, limited to the association indicated by
* <tt>equalityRelevantFarEnd</tt>. The links returned share the same
* {@link SnapshotIdentifier} as this value returns from its
* {@link RepositoryObject#getOrigin()} operation. The objects at the far end are cloned if
* their {@link RunletObject#getOrigin snapshot} does not match this value's snapshot, and the
* clone is assigned the same snapshot identifier returned by this value's
* {@link RepositoryObject#getOrigin() snapshot}.<p>
*
* This method only returns links connected to this value object. It does not recurse
* into other value objects that are equality-relevant for this value object.<p>
*
* Example: If an <tt>Address</tt> class has two equality-relevant associations, one to
* <tt>String</tt> with far end <tt>street</tt>, one to <tt>City</tt> with far end <tt>city</tt>,
* then if the <tt>street</tt> end is passed to this method, it will create a link from this
* <tt>Address</tt> object to the <tt>String</tt> object that this value has as its <tt>street</tt>
* role. If the <tt>String</tt> value has a different snapshot identifier than this value,
* it will be cloned and the snapshot of the clone will be adjusted to match this object's
* snapshot identifier.
*/
public Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> getEqualityRelevantLinks(
LinkEndMetaObject equalityRelevantFarEnd) {
Collection<Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>> links =
createCollection(getModelAdapter().isOrdered(equalityRelevantFarEnd),
getModelAdapter().isUnique(equalityRelevantFarEnd));
Side farSide = getModelAdapter().getSideOfEnd(equalityRelevantFarEnd);
boolean thisIsLeft = farSide.equals(Side.RIGHT);
Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> link;
for (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtFarEnd : equalityRelevantAssociationEndValues
.get(equalityRelevantFarEnd)) {
ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> objectAtFarEndWithSnapshotAdjusted = objectAtFarEnd
.getCopyWithOrigin(getOrigin());
if (thisIsLeft) {
link = new Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(
this, objectAtFarEndWithSnapshotAdjusted, getModelAdapter().getAssociation(equalityRelevantFarEnd), getOrigin(),
getModelAdapter());
} else {
link = new Link<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>(
objectAtFarEndWithSnapshotAdjusted, this, getModelAdapter().getAssociation(equalityRelevantFarEnd), getOrigin(),
getModelAdapter());
}
links.add(link);
}
return links;
}
/**
* The hash code takes into account the class of which which value object is an instance
* as well as all the property values where the association ends are marked relevant
* for the equality of this object.
*/
@Override
public int hashCode() {
return logicalHashCode() ^ ((getOrigin() == null)?0:getOrigin().hashCode());
}
/**
* Two values are considered Java-technically equal if they are instance of the same class and
* all their equality-relevant properties have equal values (meaning they are
* {@link #logicallyEquals logically equal} and originate from the same
* {@link #getOrigin snapshot}.
*/
@SuppressWarnings("unchecked")
@Override
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 AbstractValueObject)) {
return false;
}
if (this == oro) {
return true;
}
AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> vo =
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) oro;
return logicallyEquals(vo) && (getOrigin() == vo.getOrigin()
|| ((getOrigin() != null)
&& getOrigin().equals(vo.getOrigin())
)
);
}
/**
* Two values are considered logically equal if they are instance of the same class and all their
* equality-relevant properties have equal values.
*/
@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 AbstractValueObject)) {
return false;
}
if (this == oro) {
return true;
}
AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage> vo =
(AbstractValueObject<LinkMetaObject, LinkEndMetaObject, MetaClass, TypeUsage, ClassUsage>) oro;
return getModelAdapter().getClazz(getType()).equals(getModelAdapter().getClazz(vo.getType())) &&
logicallyEquals(equalityRelevantAssociationEndValues, vo.equalityRelevantAssociationEndValues);
}
@Override
public int logicalHashCode() {
return getType().getClass().hashCode() ^ equalityRelevantAssociationEndValues.hashCode();
}
/**
* Compares the two maps based on the {@link RunletObject#logicallyEquals(RunletObject) logical equality}
* of the {@link RunletObject}s contained in the value collections.
*/
@SuppressWarnings("unchecked") // need to cast from Collection<ClassTypedObject> to Collection<RiverObject>
protected boolean logicallyEquals(
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> a,
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> b) {
if (a == b) {
return true;
}
if (a.keySet().equals(b.keySet())) {
for (LinkEndMetaObject key:a.keySet()) {
Collection<?> c_a = a.get(key);
Collection<?> c_b = b.get(key);
if (!logicallyEquals((Collection<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>) c_a,
(Collection<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>) c_b)) {
return false;
}
}
return true;
} else {
return false;
}
}
protected boolean logicallyEquals(Collection<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> c_a,
Collection<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> c_b) {
boolean result = true;
if (c_a.size() != c_b.size()) {
result = false;
} else {
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i_a = c_a.iterator();
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i_b = c_b.iterator();
while (result && i_a.hasNext()) {
if (!i_a.next().logicallyEquals(i_b.next())) {
result = false;
}
}
}
return result;
}
@Override
public boolean isUnique() {
return false;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getModelAdapter().getClassUsageName(getType()));
sb.append("(");
boolean first = true;
Map<LinkEndMetaObject, Collection<ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> values =
getEqualityRelevantAssociationEndValues();
for (LinkEndMetaObject ae:values.keySet()) {
if (!first) {
sb.append(", ");
} else {
first = false;
}
sb.append(getModelAdapter().getEndName(ae));
sb.append(": ");
if (values.get(ae).size() > 1) {
if (getModelAdapter().isUnique(ae)) {
sb.append('{');
}
if (getModelAdapter().isOrdered(ae)) {
sb.append('[');
}
if (!getModelAdapter().isOrdered(ae) && !getModelAdapter().isUnique(ae)) {
sb.append('(');
}
}
boolean ffirst = true;
for (ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> cto : values.get(ae)) {
if (!ffirst) {
sb.append(", ");
} else {
ffirst = false;
}
sb.append(cto);
}
if (values.get(ae).size() > 1) {
if (getModelAdapter().isOrdered(ae)) {
sb.append(']');
}
if (getModelAdapter().isUnique(ae)) {
sb.append('}');
}
if (!getModelAdapter().isOrdered(ae) && !getModelAdapter().isUnique(ae)) {
sb.append(')');
}
}
}
sb.append(")");
return sb.toString();
}
}