//
//  FisherMill.java
//
package org.apache.velocity.tools;

/**
 * <p>
 * A convenience tool for use in #foreach loops. It wraps a list to let the designer specify
 * a condition to terminate the loop, and reuse the same list in different loops.
 * </p>
 * <p>
 * Example of use:
 * <pre>  
 *  Java
 *  ----
 *  context.put("mill", new FisherMill());
 *   
 *  
 *  VTL
 *  ---
 *  
 *  #set ($list = [1, 2, 3, 5, 8, 13])
 *  #set ($numbers = $mill.wrap($list))
 *  
 *  #foreach ($item in $numbers)
 *  #if ($item < 8) $numbers.more()#end
 *  #end
 *  
 *  $numbers.more()
 *  
 *  
 *  Output
 *  ------
 *  
 *   1 2 3 5
 *  8
 * </pre>
 * </p>
 * <p>
 * <b>Warning:</b> It is not recommended to call hasNext() directly as this method is used
 * to control the #foreach. Use hasMore() instead.
 * </p>
 * <ul><dl>
 * Changes:
 * </dl><li> 10 Sep 02	Original version
 * </li><li> 11 Sep 02	Added compatibility with Velocity 1.2 using ArrayIterator(Object[])
 * <br>			Added checks to throw IllegalStateException if no object is wrapped
 * </li><li> 11 Sep 02	Added toggle in hasNext() so that the tool can be used in two successive #foreach
 * <br>			Added javadoc
 * </li><li> 11 Sep 02	Added stop() on the suggestion of Christoph Reck
 * </li><li> 13 Sep 02	Made FisherMill a subclass of Camwood, with the addition of more()
 * </li></ul>
 * @author Denis Bredelet
 */
public class FisherMill extends Camwood {

    /** True if next contains the value of the next element in the list. */
    protected boolean cachedNext;
    /** The next element in the list, if it was cached. */
    protected Object next;

    /**
     * Create a FisherMill instance to use as tool.
     * When it is created this way, the tool returns a new instance each time wrap() is called.
     * This is useful when you want to allow the designers to create instances.
     */
    public FisherMill()
    {
        this(null);
    }

    /**
     * Create a FisherMill instance to use in #foreach.
     *
     * @param list The list to wrap. 
     */
    public FisherMill(Object list)
    {
        super(list);
        this.cachedNext = false;
    }


    /*
     * Wraps a list with the tool.
     * The list can be an array, a Collection, a Map, an Iterator or an Enumeration.
     * <br>If the list is a Map, the wrapper iterates over the values.
     * <br>If the list is an Iterator or an Enumeration, the wrapper can be used only once.
     *
     * @param list The list to wrap.
     * @return A new wrapper if this object is used as a tool, or itself if it is a wrapper.
     */
    public Camwood wrap(Object list)
    {
        if (isTool())
        {
            return new FisherMill(list);
        }
        else
        {
            super.wrap(list);
            this.cachedNext = false;
            return this;
        }
    }

    /*
     * <p>
     * Resets the wrapper so that it starts over at the beginning of the list.
     * </p>
     * <p>
     * <b>Note to programmers:</b> This method has no effect if the wrapped object is
     * an enumeration or an iterator.
     */
    public void reset()
    {
        if (!isTool())
        {
            super.reset();
            this.cachedNext = false;
        }
    }

    /**
     * <p>
     * Gets the next element in the list. This method is called by #foreach to define $item
     * in:
     * <pre>
     * #foreach( $item in $list )</pre>
     * </p>
     * <p>
     * This method is not intended for template designers, but they can use it if they want
     * to read the value of the next item without doing more().
     * </p>
     *
     * @return The next element in the list.
     * @throws NoSuchElementException if there are no more elements in the list.
     * @see #more()
     * @see #hasMore()
     */
    public Object next()
    {
        if (!this.cachedNext)
        {
            this.next = super.next();
            this.cachedNext = true;
        }
        return this.next;
    }

    /**
     * Returns true if there are more elements in the list and more() was called.
     * <br>This code always return false:
     * <pre>
     * tool.hasNext()? tool.hasNext(): false;</pre>
     * 
     * @return true if there are more elements, and either more() or cont() was called
     *  since last call.
     * @see #hasMore()
     */
    public boolean hasNext()
    {
        if (super.hasNext())
        {
            stop();
            return true;
        }
        else
        {
            return false;
        }
    }

    /**
     * Removes the current element from the list.
     * The current element is defined as the last element that was read from the list, either
     * with next() or with more().
     *
     * @throws UnsupportedOperationException if the wrapped list iterator doesn't support this
     *  operation.
     */
    public void remove() throws UnsupportedOperationException
    {
        // Implemented just for the sake of adding some more comment in the javadoc
        super.remove();
    }


    /**
     * <p>
     * Asks for the next element in the list. This method is to be used by the
     * template designer in #foreach loops.
     * </p>
     * <p>
     * If this method is called in the body of #foreach, the loop continues as
     * long as there are elements in the list.
     * <br>If this method is not called the loop terminates after the current iteration.
     * </p>
     *
     * @return The next element in the list, or null if there are no more elements.
     * @see #hasMore()
     */
    public Object more()
    {
        super.cont();
        if (hasMore())
        {
            Object next = next();
            this.cachedNext = false;
            return next;
        }
        else
        {
            return null;
        }
    }

    /**
     * Proceeds to the next element in the list.
     * Same as more(), but the current element is not added in the template.
     * <br>The #foreach loop will continue after this iteration (if there are more elements).
     * @see #more()
     */
    public void cont()
    {
        more();
    }
}
