package com.sap.runlet.abstractinterpreter.objects;
import java.util.Collection;
import java.util.Iterator;
import java.util.Stack;
/**
* Represents a multi-object that is instance of one of the different
* kinds of type definitions, such as class, function or nested (see {@link TypeDefinition} and
* subclasses). It holds a reference to its (runtime) type. The reference to the type is immutable.
* <p>
*
* @author Axel Uhl (D043530)
*/
public class MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage extends TypeUsage>
extends RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> {
private final Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> objects;
private final boolean unique;
private final boolean ordered;
/**
* An iterator over this multi-object that guarantees to return only single
* objects from its {@link #next()} operation. Any multi-object that would be
* returned by the multi-object's regular iterator because of nesting will
* be unwound by this flattening iterator.
*/
private class FlatteningIterator implements Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> {
/**
* The stack of iterators as corresponds with the unwinding of the object nesting
* structure. Invariant: <tt>peek().hasNext() || size()==1</tt>. In other words,
* consumed iterators are popped off the stack after the last call to <tt>next</tt>.
*/
private Stack<Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>> iteratorStack = new Stack<Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>>();
/**
* Either <tt>null</tt> in case there is no next object or a {@link RunletObject} that
* is not a {@link MultiValuedObject}.
*/
private RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> next;
public FlatteningIterator() {
iteratorStack.push(MultiValuedObject.this.iterator());
advanceToNextSingleObject();
}
/**
* If the {@link #iteratorStack} is empty, sets {@link #next} to <tt>null</tt> and
* returns. Otherwise, consumes the next element of the stack's top iterator. If that yields
* a single object, assigns it to {@link #next}. Otherwise, creates an iterator over the
* multi-object, pushes it onto the stack and recurses. If the stack's top iterator has no
* next element, the iterator is popped off the stack, and this method is called
* recursively, unless the stack is empty in which case the method sets {@link #next} to
* <tt>null</tt> and returns.
*/
private void advanceToNextSingleObject() {
if (iteratorStack.isEmpty()) {
next = null;
} else {
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i = iteratorStack.peek();
if (i.hasNext()) {
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> candidate = i.next();
if (candidate instanceof MultiValuedObject<?, ?, ?>) {
iteratorStack.push(candidate.iterator());
advanceToNextSingleObject();
} else if (candidate instanceof EmptyObject<?, ?, ?, ?>) {
// skip empty object
advanceToNextSingleObject();
} else {
next = candidate;
}
} else {
iteratorStack.pop();
advanceToNextSingleObject();
}
}
}
@Override
public boolean hasNext() {
return next != null;
}
@Override
public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> next() {
RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> result = next;
advanceToNextSingleObject();
return result;
}
@Override
public void remove() {
throw new UnsupportedOperationException("No remove on multi-object");
}
}
/**
* An iterator wrapping an iterable and skipping the first element. This gives
* "tail" behavior.
*
* @author Axel Uhl (D043530)
*/
private static class TailIterable<T> implements Iterable<T> {
private Iterable<T> wrapped;
private static class TailIterator<T> implements Iterator<T> {
private Iterator<T> iter;
public TailIterator(Iterable<T> iterable) {
iter = iterable.iterator();
if (iter.hasNext()) {
iter.next(); // skip first
}
}
@Override
public boolean hasNext() {
return iter.hasNext();
}
@Override
public T next() {
return iter.next();
}
@Override
public void remove() {
throw new RuntimeException("Remove not supported on multi-object");
}
}
public TailIterable(Iterable<T> i) {
wrapped = i;
}
public Iterator<T> iterator() {
return new TailIterator<T>(wrapped);
}
}
public MultiValuedObject(TypeUsage type, Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> objects,
boolean ordered, boolean unique) {
super(type);
this.ordered = ordered;
this.unique = unique;
// TODO this assertion should be re-activated once we have solved the NestedTypeDefinition / OutputMultiplicities problem
// assert(RiverInterpreter.isMultiple(type.getUpperMultiplicity()));
this.objects = objects;
}
@Override
public Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> iterator() {
// TODO ensure that remove() will throw an exception when called on the resulting iterator
return objects.iterator();
}
@Override
public Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> flatten() {
return new Iterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>() {
public Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> iterator() {
return new FlatteningIterator();
}
};
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object o) {
if (!(o instanceof RunletObject)) {
return false;
}
if (this == o) {
return true;
}
return this.logicallyEquals((RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>) o);
}
@Override
public boolean logicallyEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) {
if (this == o) {
return true;
}
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> it1 = this.iterator();
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> it2 = o.iterator();
while (it1.hasNext()) {
if (!it2.hasNext() || !it1.next().logicallyEquals(it2.next())) {
return false;
}
}
if (it2.hasNext()) {
return false;
}
return true;
}
@Override
public int logicalHashCode() {
int result = 198274309;
for (RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> ro:this) {
result ^= ro.logicalHashCode();
}
return result;
}
/**
* Pushes this down to {@link #contentEquals} calls of the contained objects, respectively,
* unless <tt>o</tt> is identical to this object or the test fails for other obvious reasons
* such as different size.
*/
@Override
public boolean contentEquals(RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> o) {
if (this == o) {
return true;
}
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> it1 = this.iterator();
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> it2 = o.iterator();
while (it1.hasNext()) {
if (!it2.hasNext() || !it1.next().contentEquals(it2.next())) {
return false;
}
}
if (it2.hasNext()) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = 198274309;
for (RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> ro:this) {
result ^= ro.hashCode();
}
return result;
}
@Override
public int size() {
if (objects instanceof Collection<?>) {
return ((Collection<?>) objects).size();
} else {
int result = 0;
Iterator<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>> i=iterator();
while (i.hasNext()) {
result++;
i.next();
}
return result;
}
}
public boolean isUnique() {
return unique;
}
public boolean isOrdered() {
return ordered;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (size() > 0) { // FIXME: replace by getType.isMany() when OutputMultiplicity issue is fixed
boolean first = true;
if (getType() != null && unique) {
if (ordered) {
sb.append("{[");
} else {
sb.append('{');
}
} else {
if (getType() != null && ordered) {
sb.append("[");
} else {
sb.append('(');
}
}
for (RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> ro : this) {
if (!first) {
sb.append(", ");
}
sb.append(ro);
first = false;
}
if (getType() != null && unique) {
if (ordered) {
sb.append("]}");
} else {
sb.append('}');
}
} else {
if (getType() != null && ordered) {
sb.append("]");
} else {
sb.append(')');
}
}
} else {
// MultiObject has Upper Multiplicity == 1
if (!this.isEmpty()) {
sb.append(this.iterator().next());
}
else {
sb.append(EmptyObject.MESSAGE);
}
}
return sb.toString();
}
/**
* Returns the tail of this {@link MultiValuedObject}.
*/
public RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage> tail() {
return new MultiValuedObject<LinkEndMetaObject, TypeUsage, ClassUsage>(getType(),
new TailIterable<RunletObject<LinkEndMetaObject, TypeUsage, ClassUsage>>(this), ordered, unique);
}
}