package com.sap.runlet.abstractinterpreter.objects; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.NoSuchElementException; import javax.naming.OperationNotSupportedException; import com.sap.runlet.abstractinterpreter.repository.Snapshot; import com.sap.runlet.abstractinterpreter.util.HashBag; /** * Represents an object that could be a multi-object and that is instance of one of the different * kinds of type definitions, such as class, function or nested (see {@link TypeUsage} and * subclasses). It holds a reference to its (runtime) type. The reference to the type is immutable. * <p> * * Because it can potentially be a multi-object, this class implements the * {@link Iterable} interface. The instances returned by the respective iterator * are again {@link RunletObject}s. For a single-valued object the iterator returns * <tt>this</tt> as the only instance. * * TODO could implement equals and hashCode as implementation of Equals expression type * * @author Axel Uhl (D043530) */ public abstract class RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage extends TypeUsage> implements Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>, Cloneable { /** * The object's runtime type. The multiplicity information does not * necessarily represent the actual number of objects in case of a * multi-valued object. */ private TypeUsage type; /** * A flavor of an array list that does never hold two equal objects at the * same time. * * @author Axel Uhl (D043530) */ private static class UniqueArrayList<T> extends ArrayList<T> { private static final long serialVersionUID = -1688206130944887279L; @Override public boolean add(T element) { if (!contains(element)) { return super.add(element); } else { return false; } } @Override public void add(int index, T element) { if (!contains(element)) { super.add(index, element); } } @Override public boolean addAll(Collection<? extends T> c) { Collection<? extends T> toAdd = new ArrayList<T>(c); toAdd.removeAll(this); return super.addAll(toAdd); } @Override public boolean addAll(int index, Collection<? extends T> c) { Collection<? extends T> toAdd = new ArrayList<T>(c); toAdd.removeAll(this); return super.addAll(index, toAdd); } @Override public T set(int index, T element) { super.set(index, null); if (contains(element)) { return remove(index); } else { return super.set(index, element); } } } public RunletObject(TypeUsage type) { this.type = type; } public int size() { return 1; } public TypeUsage getType() { return type; } public Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> iterator() { return new Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>() { private boolean nextCalled = false; @Override public boolean hasNext() { return !nextCalled; } @Override public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> next() { if (nextCalled) { throw new NoSuchElementException("called next twice on a single-valued object"); } nextCalled = true; return RunletObject.this; } @Override public void remove() { throw new RuntimeException( new OperationNotSupportedException("no remove on RiverObject")); } }; } /** * Objects may be nested {@link MultiValuedObject multi-objects}, for example because * method calls with output multiplicity <tt>n</tt> are called on a multi-object. This is * particularly inconvenient in case a client wants to deal with elementary objects that * are no multi-objects. This operation flattens an object to the extent that its * {@link #iterator()} operation is guaranteed to return only objects that are not * multi-objects. */ public Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> flatten() { return this; } public boolean isEmpty() { return !iterator().hasNext(); } /** * Called by the {@link LinkContainer} to announce that a link to another object has * been established which is relevant for this object's equality. This default implementation * does nothing, but {@link ValueObject} and its descendants should evaluate this and * account for the change in value that this represents. Note that for values this * only has to be expected during the construction phase of the value object because * values are generally immutable. */ public void equalityRelevantLinkAdded(LinkEndMetaObject to, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> added) { // does nothing; subclasses may be interested in accounting for the change } /** * Called by the {@link LinkContainer} to announce that a link to another object has * been removed which was relevant for this object's equality. This default implementation * does nothing, but {@link ValueObject} and its descendants should evaluate this and * account for the change in value that this represents. Note that for value objects this * means a change to another value. This should only happen if the runtime can guarantee * that the value object is not shared. */ public void equalityRelevantLinkRemoved(LinkEndMetaObject to, ClassTypedObject<LinkEndMetaObject, TypeUsage, ClassUsage> removed) { // does nothing; subclasses may be interested in accounting for the change } /** * Based on uniqueness and orderedness, creates a Java collection appropriate * for implementing these characteristics. The resulting collection can be used * to represents a multi-valued object. */ public static <T> Collection<T> createCollection(boolean ordered, boolean unique) { Collection<T> result; if (ordered) { if (unique) { result = new UniqueArrayList<T>(); } else { result = new ArrayList<T>(); } } else { if (unique) { result = new HashSet<T>(); } else { result = new HashBag<T>(); } } return result; } /** * Logical equality implements the "==" operator of the programming model which * is defined slightly different from the Java equality of the {@link RunletObject}s * representing them in the interpreter. In particular, there is this issue of * the {@link Snapshot}. While in the interpreter it is essential to be able to * distinguish otherwise equal instances if they come from different snapshots, * for a logical equality comparison the snapshot does not matter. For example, * if the number 42 was obtained from snapshot 7, then navigating an association * from that copy of 42 shall navigate using snapshot 7. However, if this copy of 42 * is compared to any other copy of 42, the result should be that these are logically * equal.<p> * * The default implementation used here defaults to the Java equality. Subclasses * that know about snapshots need to distinguish, though. */ public boolean logicallyEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> other) { return this.equals(other); } /** * 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. By default, this implementation uses {@link #logicallyEquals(RunletObject)}. */ public boolean contentEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) { return logicallyEquals(o); } /** * Computes a hash code that is the same for two objects that compare equal with * {@link #equalsWithinSameSnapshot}. By default, this is the {@link #logicalHashCode()}, * but entities should redefine this to be ID-based. */ public int hashCodeWithinSnapshot() { return this.logicalHashCode(); } /** * Computes a hash code that is based only on those features that also contribute to * the definition of {@link #logicallyEquals(RunletObject)}. The default implementation * is to use the regular hash code. */ public int logicalHashCode() { return hashCode(); } }