package com.tinkerpop.pipes.branch; import com.tinkerpop.pipes.Pipe; import com.tinkerpop.pipes.PipeFunction; import com.tinkerpop.pipes.util.AbstractMetaPipe; import com.tinkerpop.pipes.util.MetaPipe; import com.tinkerpop.pipes.util.PipeHelper; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Queue; /** * LoopPipe takes one or two Boolean-based PipeFunctions. * For each object of the LoopPipe, the "while" PipeFunction is called. * The input of the while PipeFunction is a LoopBundle object which is the object plus some metadata. * The boolean of the while PipeFunction determines whether the object should be put back at the start of the LoopPipe or not. * The "emit" PipeFunction determines whether the current object should be emitted or not. * * @author Marko A. Rodriguez (http://markorodriguez.com) */ public class LoopPipe<S> extends AbstractMetaPipe<S, S> implements MetaPipe { private final PipeFunction<LoopBundle<S>, Boolean> whileFunction; private final PipeFunction<LoopBundle<S>, Boolean> emitFunction; private final Pipe<S, S> pipe; private ExpandableLoopBundleIterator<S> expando; public LoopPipe(final Pipe<S, S> pipe, final PipeFunction<LoopBundle<S>, Boolean> whileFunction, final PipeFunction<LoopBundle<S>, Boolean> emitFunction) { this.pipe = pipe; this.whileFunction = whileFunction; this.emitFunction = emitFunction; } public LoopPipe(final Pipe<S, S> pipe, final PipeFunction<LoopBundle<S>, Boolean> whileFunction) { this(pipe, whileFunction, null); } protected S processNextStart() { while (true) { final S s = this.pipe.next(); final LoopBundle<S> loopBundle; if (this.pathEnabled) loopBundle = new LoopBundle<S>(s, this.getCurrentPath(), this.getLoops()); else loopBundle = new LoopBundle<S>(s, null, this.getLoops()); if (whileFunction.compute(loopBundle)) { this.expando.add(loopBundle); if (null != emitFunction && emitFunction.compute(loopBundle)) return s; } else { if (emitFunction == null || emitFunction.compute(loopBundle)) return s; } } } public List<Pipe> getPipes() { return (List) Arrays.asList(this.pipe); } public void setStarts(final Iterator<S> iterator) { this.expando = new ExpandableLoopBundleIterator<S>(iterator); this.pipe.setStarts(this.expando); } public String toString() { return PipeHelper.makePipeString(this, this.pipe); } public int getLoops() { return this.expando.getCurrentLoops() + 1; } public List getCurrentPath() { if (this.pathEnabled) { final List path = new ArrayList(); final List currentPath = this.expando.getCurrentPath(); if (null != currentPath) path.addAll(currentPath); path.addAll(this.pipe.getCurrentPath()); return path; } else { throw new RuntimeException(Pipe.NO_PATH_MESSAGE); } } public void reset() { this.expando.clear(); super.reset(); } public static class LoopBundle<T> { private final List path; private final T t; private final int loops; protected LoopBundle(final T t, final List path, final int loops) { this.t = t; this.path = path; if (null != path) { // remove the join object this.path.remove(this.path.size() - 1); } this.loops = loops; } public List getPath() { return this.path; } public int getLoops() { return this.loops; } public T getObject() { return this.t; } } private class ExpandableLoopBundleIterator<T> implements Iterator<T> { private final Queue<LoopBundle<T>> queue = new LinkedList<LoopBundle<T>>(); private final Iterator<T> iterator; private LoopBundle<T> current; private int totalResets = -1; public ExpandableLoopBundleIterator(final Iterator<T> iterator) { this.iterator = iterator; } public void remove() { throw new UnsupportedOperationException(); } public T next() { if (this.queue.isEmpty()) { this.current = null; if (!this.iterator.hasNext()) { this.incrTotalResets(); } return iterator.next(); } else { this.current = this.queue.remove(); return this.current.getObject(); } } public boolean hasNext() { if (this.queue.isEmpty() && !this.iterator.hasNext()) { this.incrTotalResets(); return false; } else { return true; } } public void add(final LoopBundle<T> loopBundle) { this.queue.add(loopBundle); } public List getCurrentPath() { if (null == this.current) return null; else return this.current.getPath(); } /*public int getCurrentLoops() { if (null == this.current) return 1; else return this.current.getLoops(); }*/ public int getCurrentLoops() { if (null != this.current) { return this.current.getLoops(); } else { if (this.totalResets == -1) return 1; else return totalResets; } } private void incrTotalResets() { if (totalResets == -1) totalResets = 0; this.totalResets++; } public void clear() { this.totalResets = -1; this.current = null; this.queue.clear(); } } public static PipeFunction<Object, Boolean> createTrueFunction() { return new PipeFunction<Object, Boolean>() { public Boolean compute(final Object object) { return Boolean.TRUE; } }; } public static PipeFunction<LoopBundle, Boolean> createLoopsFunction(final int loops) { return new PipeFunction<LoopBundle, Boolean>() { public Boolean compute(final LoopBundle loopBundle) { return loopBundle.getLoops() < loops; } }; } }