/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:  
 *       "This product includes software developed by the 
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 */ 

package javax.servlet.jsp.jstl.core;

import javax.servlet.jsp.*;
import javax.servlet.jsp.tagext.*;

/**
 * <p>JSTL 1.0 では，
 * LoopTagインタフェース(このインタフェースとJSP1.2で定義されている
 * javax.servlet.jsp.tagext.IterationTagとを混同しないようにして下さい) 
 * を実装することにより，開発者がカスタム反復タグを作成することができるようになっています．
 * LoopTag はカスタムタグを認識し，型安全に連動するための反復タグのメカニズムを確立します．
 * </p>
 * 
 * <p>殆どの反復タグは実際の反復的な振る舞いと関連する同様な振る舞いをするので，
 * JSTL 1.0 は実装を促進するためにこの基底となるサポートクラスを提供します．
 * 多くの反復タグはこれを継承し，hasNext()と next()メソッドを単に実装するだけでしょう．
 * これらのメソッドの実装は，反復終了するためのハンドラのためのコンテンツを提供します．
 *
 * <p>特に, この基底クラスは次の項目のサポートを提供します．:</p>
 * <ul>
 *  <li> 反復制御 (protectedな next() と hasNext())
 *  <li> 部分集合化 (begin, end, step 機能, 
 *       部分集合化用パラメータが有効範囲内かの妥当性検証を含む)
 *  <li> アイテムの取り出し (getCurrent())
 *  <li> 状態の取り出し (LoopTagStatus)
 *  <li> 属性の exposing ('var' と 'varStatus' 属性による設定)
 * </ul>
 *
 * <p>これらの作業へのサポートを提供するために，LoopTagSupport は
 * 反復の更新を実行する制御用変数を含みます．
 * アクセッサは，これらの制御用変数のために提供されています．
 * 変数は変換時(例えば, var, status)に必要とされたり，要求される情報を表すとき
 * 他の変数にとっては, アクセッサはここへ提供されることはできません．
 * サブクラスはそれらのアクセッサがいかに受け取るかの実装によって異なるからです．
 * インスタンスにとっては，あるサブクラスはStringを受け付け，式の評価を行って
 * ある特別な型のオブジェクトへ変換するかもしれません；
 * その他はオブジェクトを直接的に受付けるかもしれません．
 * 静的な他のものは，そのような情報を外部制御から見えるようにしたくはないかもしれません．
 *
 * @author Shawn Bayern
 */

public abstract class LoopTagSupport
    extends TagSupport
    implements LoopTag, IterationTag, TryCatchFinally
{
    //*********************************************************************
    // 'Protected' state 

    /*
     * JavaBean-style properties and other state slaved to them.  These
     * properties can be set directly by accessors; they will not be
     * modified by the LoopTagSupport implementation -- and should
     * not be modified by subclasses outside accessors unless those
     * subclasses are perfectly aware of what they're doing.
     * (An example where such non-accessor modification might be sensible
     * is in the doStartTag() method of an EL-aware subclass.)
     */

    /** indexの開始 (属性 'begin') */
    protected int begin;

    /**
     * indexの終了 (属性 'end').  -1 は内部的に'endの指定がない'ことを表します。
     *  しかしながらJSTLコアタグのためのアクセッサは
     * この値がユーザーによって直接与えられることをゆるさない
     */
    protected int end;

    /** 反復の幅 (属性 'step') */
    protected int step;

    /** 'begin' が指定されていたかを示す２値フラグ */
    protected boolean beginSpecified;

    /** 'end' が指定されていたかを示す２値フラグ */
    protected boolean endSpecified;

    /** 'step' が指定されていたかを示す２値フラグ */
    protected boolean stepSpecified;

    /** 属性表示の制御 */
    protected String itemId, statusId;


    //*********************************************************************
    // 'Private' state (implementation details)

    /*
     * State exclusively internal to the default, reference implementation.
     * (While this state is kept private to ensure consistency, 'status'
     * and 'item' happen to have one-for-one, read-only, accesor methods
     * as part of the LoopTag interface.)
     *
     * 'last' is kept separately for two reasons:  (a) to avoid
     * running a computation every time it's requested, and (b) to
     * let LoopTagStatus.isLast() avoid throwing any exceptions,
     * which would complicate subtag and scripting-variable use.
     *
     * Our 'internal index' begins at 0 and increases by 'step' each
     * round; this is arbitrary, but it seemed a simple way of keeping
     * track of the information we need.  To avoid computing
     * getLoopStatus().getCount() by dividing index / step, we keep
     * a separate 'count' and increment it by 1 each round (as a minor
     * performance improvement).
     */
    private LoopTagStatus status;               // our LoopTagStatus
    private Object item;                        // the current item
    private int index;                          // the current internal index
    private int count;                          // the iteration count
    private boolean last;                       // current round == last one?

    //*********************************************************************
    // Constructor

    /**
     * 新規のLoopTagSupportのコンストラクトします.  TagSupportのために, サブクラスは
     * 他のコンストラクタを提供すべきではありません．そして，サブクラスは
     * スーパークラスのコンストラクタを呼び出すべきです．
     */
    public LoopTagSupport() {
        super();
        init();
    }


    //*********************************************************************
    // Abstract methods

    /**
     * <p>タグが反復する次のオブジェクトを返します。このメソッドは
     * どのようなオブジェクトを反復するのかという基本的な論理を知らせるために、
     * LoopTagSupportの実現サブクラスにおいて提供されなければなりません。
     * </p>
     *
     * <p>このメソッドは一般的に反復によって戻されるのを期待されています。
     * しかし、場合によっては、そうでない場合もあります。
     * 特に、次のオブジェクトの検索によってある例外が投げられる可能性があるのなら、
     * このメソッドは、その例外をJspTagExceptionとしてJSPコンテナへ戻して
     * 伝搬することができます。
     * 独立した反復はこれは不可能です。（これは、なぜLoopTagSupportが単純に
     * そのサブタグから反復を呼ばないのかの説明になります）</p>
     * 
     * @return 反復の次の回で使用されるjava.lang.Object
     * @exception NoSuchElementException
     *            next()が呼ばれたときに、新しい要素が有効でない場合
     * @exception javax.servlet.jsp.JspTagException
     *            このほか、予期しない例外が発生した場合
     */
    protected abstract Object next() throws JspTagException;

    /**
     * <p>反復の対象となる１以上のアイテムが有効になっているかに関する情報を返します。
     * このメソッドは、基本クラスを支援するために提供される反復の論理の手助けとなる
     * ので、LoopTagSupportを実装するサブクラスによって提供されなければなりません。
     * </p>
     *  
     * <p>目的とこのタグに期待されることに関するさらなる情報は
     * <a href="#next()">next</a> を参照してください。</p>
     *  
     * @return 反復を続けるための少なくとも１つ以上のアイテムがあれば、<tt>true</tt>を、それ以外は<tt>false</tt>
     * @exception javax.servlet.JspTagException
     * @see #next
     */
    protected abstract boolean hasNext() throws JspTagException;

    /**
     * <p>単一のタグ起動のための準備をします。特に、
     * サブクラスがhasNext()とnext()を呼び出すための準備をさせます。
     * サブクラスは、prepare()がスーパークラスにおいて、doStartTag()の
     * 呼び出しそれぞれに対して１度呼ばれることを引き受けることができます。
     * </p>
     *
     * @exception javax.servlet.JspTagException
     */
    protected abstract void prepare() throws JspTagException;


    //*********************************************************************
    // Lifecycle management and implementation of iterative behavior

    /**
     * このLoopTagSupportが持つ（継承している）リソースを解放します．
     */
    public void release() {
        super.release();
        init();
    }

    /**
     * 最初のアイテムの処理により反復を開始します．
     */
    public int doStartTag() throws JspException {

        // make sure 'begin' isn't greater than 'end'
        if (end != -1 && begin > end)
	    throw new JspTagException(
		"begin (" + begin + ") > end (" + end + ")");

        // we're beginning a new iteration, so reset our counts (etc.)
        index = 0;
        count = 1;
        last = false;

        // let the subclass conduct any necessary preparation
        prepare();

        // throw away the first 'begin' items (if they exist)
        discardIgnoreSubset(begin);

        // get the item we're interested in
        if (hasNext())
            // index is 0-based, so we don't update it for the first item
            item = next();
        else
            return SKIP_BODY;

        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard(step - 1);

        // prepare to include our body...
        exposeVariables();
        calibrateLast();
        return EVAL_BODY_INCLUDE;
    }

    /**
     * 最後になるまで反復を続けます．-- 最後になるまでとは，(a)まだアイテムがまだあり，
     * かつ (b) ('step'で指定された)'end'に到達していない，という間は，ということです．
     */
    public int doAfterBody() throws JspException {

        // re-sync the index, given our prior behind-the-scenes 'step'
        index += step - 1;

        // increment the count by 1 for each round
        count++;

        // everything's been prepared for us, so just get the next item
        if (hasNext() && !atEnd()) {
            index++;
            item = next();
        } else
            return SKIP_BODY;

        /*
         * now discard anything we have to "step" over.
         * (we do this in advance to support LoopTagStatus.isLast())
         */
        discard(step - 1);

        // prepare to re-iterate...
        exposeVariables();
        calibrateLast();
        return EVAL_BODY_AGAIN;
    }

    /**
     * このLoopTagSupport集合のすべての属性を削除します．
     * 
     * <p> これらの属性は入れ子のスコープのスクリプト変数をサポートするためのものです．
     * したがって，スクリプト変数を放置したままにし属性空間を汚す、ということがないようにします.
     * </p>
     */
    public void doFinally() {
	/*
	 * Make sure to un-expose variables, restoring them to their
	 * prior values, if applicable.
         */
	unExposeVariables();
    }

    /**
     * 与えられたThrowableを再度スローします．
     */
    public void doCatch(Throwable t) throws Throwable {
	throw t;
    }

    //*********************************************************************
    // Accessor methods

    /*
     * Overview:  The getXXX() methods we provide implement the Tag
     * contract.  setXXX() accessors are provided only for those
     * properties (attributes) that must be known at translation time,
     * on the premise that these accessors will vary less than the
     * others in terms of their interface with the page author.
     */

    /*
     * (Purposely inherit JavaDoc and semantics from LoopTag.
     * Subclasses can override this if necessary, but such a need is
     * expected to be rare.)
     */
    public Object getCurrent() {
        return item;
    }

    /*
     * (Purposely inherit JavaDoc and semantics from LoopTag.
     * Subclasses can override this method for more fine-grained control
     * over LoopTagStatus, but an effort has been made to simplify
     * implementation of subclasses that are happy with reasonable default
     * behavior.)
     */
    public LoopTagStatus getLoopStatus() {

        // local implementation with reasonable default behavior
        class Status implements LoopTagStatus {

            /*
             * All our methods are straightforward.  We inherit
             * our JavaDoc from LoopTagSupport; see that class
             * for more information.
             */

            public Object getCurrent() {
                /*
                 * Access the item through getCurrent() instead of just
                 * returning the item our containing class stores.  This
                 * should allow a subclass of LoopTagSupport to override
                 * getCurrent() without having to rewrite getLoopStatus() too.
                 */
                return (LoopTagSupport.this.getCurrent());
            }
            public int getIndex() {
                return (index + begin);       // our 'index' isn't getIndex()
            }
            public int getCount() {
                return (count);
            }
            public boolean isFirst() {
                return (index == 0);          // our 'index' isn't getIndex()
            }
            public boolean isLast() {
                return (last);                // use cached value
            }
            public Integer getBegin() {
                if (beginSpecified)
                    return (new Integer(begin));
                else
                    return null;
            }
            public Integer getEnd() {
                if (endSpecified)
                    return (new Integer(end));
                else
                    return null;
            }
            public Integer getStep() {
                if (stepSpecified)
                    return (new Integer(step));
                else
                    return null;
            }
        }

        /*
         * We just need one per invocation...  Actually, for the current
         * implementation, we just need one per instance, but I'd rather
         * not keep the reference around once release() has been called.
         */
        if (status == null)
            status = new Status();

        return status;
    }

    /*
     * We only support setter methods for attributes that need to be
     * offered as Strings or other literals; other attributes will be
     * handled directly by implementing classes, since there might be
     * both rtexprvalue- and EL-based varieties, which will have
     * different signatures.  (We can't pollute child classes by having
     * base implementations of those setters here; child classes that
     * have attributes with different signatures would end up having
     * two incompatible setters, which is illegal for a JavaBean.
     */

    /**
     * 'var'属性を設定します．
     *
     * @param id 反復の現在のアイテムを保持するスコープありの変数の名前
     */
    public void setVar(String id) {
        this.itemId = id;
    }

    /**
     * 'varStatus'属性を設定します．
     *
     * @param statusId 反復の現在の状態を保持するスコープありの変数の名前
     */
    public void setVarStatus(String statusId) {
        this.statusId = statusId;
    }


    //*********************************************************************
    // Protected utility methods

    /* 
     * These methods validate attributes common to iteration tags.
     * Call them if your own subclassing implementation modifies them
     * -- e.g., if you set them through an expression language.
     */

    /**
     * "begin" 属性が実用的か確認します。
     * もしそうでなかったら伝搬するための例外を投げます。
     */
    protected void validateBegin() throws JspTagException {
        if (begin < 0)
            throw new JspTagException("'begin' < 0");
    }

    /**
     * "end" 属性が実用的か確認します
     * もしそうでなかったら伝搬するための例外を投げます。
     */
    protected void validateEnd() throws JspTagException {
        if (end < 0)
            throw new JspTagException("'end' < 0");
    }

    /**
     * "step" 属性が実用的か確認します
     * もしそうでなかったら伝搬するための例外を投げます。
     */
    protected void validateStep() throws JspTagException {
        if (step < 1)
            throw new JspTagException("'step' <= 0");
    }


    //*********************************************************************
    // Private utility methods

    /** (Re)initializes state (during release() or construction) */
    private void init() {
        // defaults for internal bookkeeping
        index = 0;              // internal index always starts at 0
        count = 1;              // internal count always starts at 1
        status = null;          // we clear status on release()
        item = null;            // item will be retrieved for each round
        last = false;           // last must be set explicitly
        beginSpecified = false; // not specified until it's specified :-)
        endSpecified = false;   // (as above)
        stepSpecified = false;  // (as above)

        // defaults for interface with page author
        begin = 0;              // when not specified, 'begin' is 0 by spec.
        end = -1;               // when not specified, 'end' is not used
        step = 1;               // when not specified, 'step' is 1
        itemId = null;          // when not specified, no variable exported
        statusId = null;        // when not specified, no variable exported
    }

    /** Sets 'last' appropriately. */
    private void calibrateLast() throws JspTagException {
        /*
         * the current round is the last one if (a) there are no remaining
         * elements, or (b) the next one is beyond the 'end'.
         */
        last = !hasNext() || atEnd() ||
            (end != -1 && (begin + index + step > end));
    }

    /**
     * Exposes attributes (formerly scripting variables, but no longer!)
     * if appropriate.  Note that we don't really care, here, whether they're
     * scripting variables or not.
     */
    private void exposeVariables() throws JspTagException {

        /*
         * We need to support null items returned from next(); we
         * do this simply by passing such non-items through to the
         * scoped variable as effectively 'null' (that is, by calling
         * removeAttribute()).
         *
         * Also, just to be defensive, we handle the case of a null
         * 'status' object as well.
         *
         * We call getCurrent() and getLoopStatus() (instead of just using
         * 'item' and 'status') to bridge to subclasses correctly.
         * A subclass can override getCurrent() or getLoopStatus() but still
         * depend on our doStartTag() and doAfterBody(), which call this
         * method (exposeVariables()), to expose 'item' and 'status'
         * correctly.
         */

        if (itemId != null) {
            if (getCurrent() == null)
                pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
            else
                pageContext.setAttribute(itemId, getCurrent());
        }
        if (statusId != null) {
            if (getLoopStatus() == null)
                pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
            else
                pageContext.setAttribute(statusId, getLoopStatus());
        }

    }

    /**
     * Removes page attributes that we have exposed and, if applicable,
     * restores them to their prior values (and scopes).
     */
    private void unExposeVariables() {
        // "nested" variables are now simply removed
	if (itemId != null)
            pageContext.removeAttribute(itemId, PageContext.PAGE_SCOPE);
	if (statusId != null)
	    pageContext.removeAttribute(statusId, PageContext.PAGE_SCOPE);
    }

    /**
     * Cycles through and discards up to 'n' items from the iteration.
     * We only know "up to 'n'", not "exactly n," since we stop cycling
     * if hasNext() returns false or if we hit the 'end' of the iteration.
     * Note: this does not update the iteration index, since this method
     * is intended as a behind-the-scenes operation.  The index must be
     * updated separately.  (I don't really like this, but it's the simplest
     * way to support isLast() without storing two separate inconsistent
     * indices.  We need to (a) make sure hasNext() refers to the next
     * item we actually *want* and (b) make sure the index refers to the
     * item associated with the *current* round, not the next one.
     * C'est la vie.)
     */
    private void discard(int n) throws JspTagException {
        /*
         * copy index so we can restore it, but we need to update it
         * as we work so that atEnd() works
         */
        int oldIndex = index;
        while (n-- > 0 && !atEnd() && hasNext()) {
            index++;
            next();
        }
        index = oldIndex;
    }

    /**
     * Discards items ignoring subsetting rules.  Useful for discarding
     * items from the beginning (i.e., to implement 'begin') where we
     * don't want factor in the 'begin' value already.
     */
    private void discardIgnoreSubset(int n) throws JspTagException {
	while (n-- > 0 && hasNext())
	    next();
    }

    /**
     * Returns true if the iteration has past the 'end' index (with
     * respect to subsetting), false otherwise.  ('end' must be set
     * for atEnd() to return true; if 'end' is not set, atEnd()
     * always returns false.)
     */
    private boolean atEnd() {
        return ((end != -1) && (begin + index >= end));
    }
}
