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'");
    }
    
}