Tuple.java
package de.turnertech.tuples;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
/**
* <p>n-Tuple, the core of this package. This class models the concept of an untyped
* n-Tuple of any length. It provides basic functionality for creation and reading
* of immutable Tuples. It implements the Collection interface in a read only
* manner. Any calls to functions which would modify the collection will throw
* exceptions.</p>
*
* <p>This implementation does not support null elements!</p>
*/
public class Tuple implements Collection<Object> {
private class TupleIterator implements Iterator<Object> {
private int currentIndex;
private final Object[] elements;
public TupleIterator(Tuple tuple) {
this.elements = tuple.flatten().toArray();
currentIndex = 0;
}
@Override
public boolean hasNext() {
return currentIndex < elements.length;
}
@Override
public Object next() {
if(currentIndex >= elements.length) {
throw new NoSuchElementException("Attempted to iterate beyond the end of a tuple.");
}
return elements[currentIndex++];
}
}
private final Object[] elements;
/**
* Create an empty tuple
* @return an empty tuple
*/
public static Tuple0 from() {
return new Tuple0();
}
/**
* Create a single tuple
* @param <A> the type of element0
* @param element0 the element to store
* @return the tuple
*/
public static <A> Tuple1<A> from(A element0) {
return new Tuple1<>(element0);
}
/**
* Create a double tuple
* @param <A> the type of element0
* @param <B> the type of element1
* @param element0 the element to store
* @param element1 the element to store
* @return the tuple
*/
public static <A,B> Tuple2<A,B> from(A element0, B element1) {
return new Tuple2<>(element0, element1);
}
/**
* Create a triple tuple
* @param <A> the type of element0
* @param <B> the type of element1
* @param <C> the type of element2
* @param element0 the element to store
* @param element1 the element to store
* @param element2 the element to store
* @return the tuple
*/
public static <A,B,C> Tuple3<A,B,C> from(A element0, B element1, C element2) {
return new Tuple3<>(element0, element1, element2);
}
/**
* Constructs a fixed length, non typed tuple.
*
* @param elements the elements
*/
public Tuple(Object... elements) {
if(elements == null || elements.length == 0) {
this.elements = new Object[0];
} else {
for(Object element : elements) {
Objects.requireNonNull(element);
}
this.elements = elements;
}
}
/**
* Can be considered as tuple.flatten().get(...)
* <pre>
*(4, (), 8, (((), 6))).get(1) == 8
* </pre>
* @param index of the next non null element in the tuple.
* @return the non null element in the tuple.
*/
public Object get(int index) {
if(index < 0 || index >= size()) {
throw new IndexOutOfBoundsException(index);
}
final TupleIterator iterator = new TupleIterator(this);
for(int i = 0; i < index; ++i) {
iterator.next();
}
return iterator.next();
}
/**
* Gets the element at the index in this tuple, such that:
* <pre>
*(4, (), 8, (((), 6))).getElement(1) == ()
* </pre>
* @param index the next element in the Tuple
* @return the element in the Tuple
*/
public Object getElement(int index) {
if(index < 0 || index >= length()) {
throw new IndexOutOfBoundsException(index);
}
return elements[index];
}
/**
* Equivelant to tuple.flatten().size() == 0
*/
public boolean isEmpty() {
return flatten().elements.length == 0;
}
private void flatten(List<Object> out) {
for(Object element : elements) {
if(element instanceof Tuple) {
((Tuple)element).flatten(out);
} else {
out.add(element);
}
}
}
/**
* <p>Removes all Tuples contained in this Tuple, and return a single "flat" Tuple. For example:</p>
* <pre>
*myTuple.toString() = (4, (), 8, (((), 6)))
*myTuple.flatten().toString() = (4, 8, 6)
* </pre>
*
* @return A tuple containing no sub tuples
*/
public Tuple flatten() {
LinkedList<Object> returnCollection = new LinkedList<>();
this.flatten(returnCollection);
return new Tuple(returnCollection.toArray());
}
/**
* Uses Objects.toString() to represent this tuple in the for ((), 2, SomeString, etc)
*/
@Override
public String toString() {
final StringWriter stringWriter = new StringWriter();
stringWriter.write("(");
if(elements.length > 0) {
stringWriter.write(Objects.toString(elements[0], "()"));
}
for(int i = 1; i < elements.length; ++i) {
stringWriter.write(", ");
stringWriter.write(Objects.toString(elements[i], "()"));
}
stringWriter.write(")");
return stringWriter.toString();
}
/**
* Contrary to the rest of the functions, this implementation checks equality
* of the underlying array, and not equality in terms of "Tuple Equality".
* e.g. ((), 5) != (5, ()).
*/
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.deepHashCode(elements);
return result;
}
/**
* Contrary to the rest of the functions, this implementation checks equality
* of the underlying array, and not equality in terms of "Tuple Equality".
* e.g. ((), 5) != (5, ()).
*/
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Tuple other = (Tuple) obj;
return Arrays.deepEquals(elements, other.elements);
}
/**
* <p>Note that the size of a Tuple is not the same as for other collections! For a Tuple,
* the size of the collection is the count of elements contained in this and all sub tuples.
* For example:</p>
* <pre>
*((), 8, ((5)), ()).size() == 2
* </pre>
* @see #length()
* {@inheritDoc}
*/
@Override
public int size() {
int returnSize = 0;
for(Object element : elements) {
returnSize += element instanceof Tuple ? ((Tuple)element).size() : 1;
}
return returnSize;
}
/**
* <p>Gets the length of the Tuple</p>
* <pre>
*((), 8, ((5)), ()).length() == 4
* </pre>
* @see #size()
* @return the length of the tuple
*/
public int length() {
return elements.length;
}
/**
* Equivelant to tuple.flatten().contains(...)
*/
@Override
public boolean contains(Object o) {
for(Object element : elements) {
if(element instanceof Tuple) {
if(((Tuple)element).contains(o)) {
return true;
}
} else {
if(Objects.equals(element, o)) {
return true;
}
}
}
return false;
}
/**
* Equivelant to tuple.flatten().containsAll(...)
*/
@Override
public boolean containsAll(Collection<?> c) {
return Arrays.asList(this.flatten().toArray()).containsAll(Objects.requireNonNull(c));
}
/**
* <p>Returns a "Tuple Aware" iterator which ignores empty tuples, and iterates through child Tuples. For
* example given the Tuple ((9, ((), 1)), 1), this Iterator will respond with:</p>
* <pre>
*iter.next() == 9
*iter.next() == 1
*iter.next() == 1
*iter.next() throws NoSuchElementException
* </pre>
*/
@Override
public Iterator<Object> iterator() {
return new TupleIterator(this);
}
/**
* Returns the raw contents of the Tuple, such that they could be used to create another equal Tuple.
* For example, given the Tuple ((), 1), this function will return an Object[]{Tuple0, 1}.
*/
@Override
public Object[] toArray() {
return elements;
}
/**
* Tuples are multi-typed and do not support 'toArray'
*/
@Override
public <T> T[] toArray(T[] a) {
throw new UnsupportedOperationException("Tuples are multi-typed and do not support 'toArray'");
}
/**
* Tuples are immutable and do not support 'add'
*/
@Override
public boolean add(Object e) {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'add'");
}
/**
* Tuples are immutable and do not support 'remove'
*/
@Override
public boolean remove(Object o) {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'remove'");
}
/**
* Tuples are immutable and do not support 'addAll'
*/
@Override
public boolean addAll(Collection<? extends Object> c) {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'addAll'");
}
/**
* Tuples are immutable and do not support 'removeAll'
*/
@Override
public boolean removeAll(Collection<?> c) {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'removeAll'");
}
/**
* Tuples are immutable and do not support 'retainAll'
*/
@Override
public boolean retainAll(Collection<?> c) {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'retainAll'");
}
/**
* Tuples are immutable and do not support 'clear'
*/
@Override
public void clear() {
throw new UnsupportedOperationException("Tuples are immutable and do not support 'clear'");
}
}