package com.tinkerpop.pipes;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.ArrayList;

/**
 * An AbstractPipe provides most of the functionality that is repeated in every instance of a Pipe.
 * Any subclass of AbstractPipe should simply implement processNextStart(). The standard model is
 * <pre>
 * protected E processNextStart() throws NoSuchElementException {
 * S s = this.starts.next();
 * E e = // do something with the S to yield an E
 * return e;
 * }
 * </pre>
 * If the current incoming S is not to be emitted and there are no other S objects to process and emit, then throw a NoSuchElementException.
 *
 * @author Marko A. Rodriguez (http://markorodriguez.com)
 */
public abstract class AbstractPipe<S, E> implements Pipe<S, E> {

    protected Iterator<S> starts;
    private E nextEnd;
    private E currentEnd;
    private boolean available = false;
    protected boolean ensurePipeStarts = true;
    protected boolean pathEnabled = false;

    public void setStarts(final Pipe<?, S> starts) {
        this.starts = starts;
    }

    public void setStarts(final Iterator<S> starts) {
        if (ensurePipeStarts) {
            IdentityPipe<S> pipe = new IdentityPipe<S>();
            pipe.setStarts(starts);
            this.starts = pipe;
        } else {
            this.starts = starts;
        }
    }

    public void setStarts(final Iterable<S> starts) {
        this.setStarts(starts.iterator());
    }

    public void enablePath() {
        if (!this.pathEnabled) {
            this.pathEnabled = true;
            if (this.starts instanceof Path) {
                Path path = (Path)this.starts;
                path.enablePath();
            }
        }
    }

    public ArrayList path() {
        if (!this.pathEnabled) {
            throw new UnsupportedOperationException("To use path(), you must call enablePath() before iteration begins.");
        }
        return addElementToPath(getPathToHere(), this.getCurrentEnd());
    }

    protected ArrayList getPathToHere() {
        if (this.starts instanceof Path) {
            Path path = (Path)this.starts;
            return path.path();
        } else {
            return new ArrayList();
        }
    }

    protected Object getCurrentEnd() {
        return this.currentEnd;
    }

    protected ArrayList addElementToPath(ArrayList pathElements, Object nextElement) {
        int size = pathElements.size();
        if (size == 0 || pathElements.get(size - 1) != nextElement) {
            pathElements.add(nextElement);
        }
        return pathElements;
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }

    public E next() {
        this.currentEnd = null;
        if (this.available) {
            this.available = false;
            this.currentEnd = this.nextEnd;
            return this.nextEnd;
        } else {
            this.currentEnd = this.processNextStart();
            return this.currentEnd;
        }
    }

    public boolean hasNext() {
        if (this.available)
            return true;
        else {
            try {
                this.nextEnd = this.processNextStart();
                this.available = true;
                return true;
            } catch (NoSuchElementException e) {
                this.available = false;
                return false;
            }
        }
    }

    public Iterator<E> iterator() {
        return this;
    }

    protected abstract E processNextStart() throws NoSuchElementException;

}


@@ -1932,473 +1932,49 @@
     
-return addElementToPath(getPathToHere(), this.getCurrentEnd());%0A    %7D%0A%0A    protected ArrayList getPathToHere() %7B%0A        if (this.starts instanceof Path) %7B%0A            Path path = (Path)this.starts;%0A            return path.path();%0A        %7D else %7B%0A            return new ArrayList();%0A        %7D%0A    %7D%0A%0A    protected Object getCurrentEnd() %7B%0A        return this.currentEnd;%0A    %7D%0A%0A    protected ArrayList addElementToPath(ArrayList pathElements, Object nextElement) %7B
+ArrayList pathElements = getPathToHere();
 %0A   
@@ -2065,27 +2065,31 @@
 - 1) != 
-nextElement
+getCurrentEnd()
 ) %7B%0A    
@@ -2117,19 +2117,23 @@
 add(
-nextElement
+getCurrentEnd()
 );%0A 
@@ -3115,11 +3115,313 @@
 ption;%0A%0A
+    protected ArrayList getPathToHere() %7B%0A        if (this.starts instanceof Path) %7B%0A            Path path = (Path)this.starts;%0A            return path.path();%0A        %7D else %7B%0A            return new ArrayList();%0A        %7D%0A    %7D%0A%0A    protected E getCurrentEnd() %7B%0A        return this.currentEnd;%0A    %7D%0A
 %7D%0A%0A

Clean up the path refactoring now that I know what methods need to be available to be overridden.