/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.hayabusa.taglib;

import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)
import static org.opengion.fukurou.util.StringUtil.nval ;

/**
 * case タグは、上位の switch タグの key とマッチした場合に、処理を行います。
 *
 * case タグの以下の属性について、説明します。
 *   match    ： switch_key.match( case_match ) でマッチした場合は、BODY が処理されます。
 *   isDefault： trueに設定すると、どのcase にもマッチしなかった場合に処理されます(初期値:false)
 *   isBreak  ： 通常、最初にマッチした段階で、switch 処理を抜けます(初期値:true)
 *   isNull   ： trueに設定すると、switchのkeyが、null(またはゼロ文字列)の場合、マッチします(初期値:false)
 *
 * @og.formSample
 * ●形式：&lt;og:switch key="･･･" &gt;
 *            &lt;og:case match="A" &gt; ･･･ &lt;/og:case&gt;
 *            &lt;og:case match="B" &gt; ･･･ &lt;/og:case&gt;
 *            &lt;og:case match="C" &gt; ･･･ &lt;/og:case&gt;
 *            &lt;og:case isDefault="true" &gt; ･･･ &lt;/og:case&gt;
 *         &lt;/og:switch&gt;
 * ●body：あり(EVAL_BODY_INCLUDE:BODYをインクルードし、{&#064;XXXX} は解析しません)
 *
 * ●Tag定義：
 *   &lt;og:case
 *       match              【TAG】switch-case のマッチ条件(case_match)を指定します
 *       isDefault          【TAG】どのcase にもマッチしなかった場合に処理する case 文かどうかを指定します(初期値:false)
 *       isBreak            【TAG】マッチした以降に継続処理を行わない(ブレイクする)かどうかを指定(初期値:true)
 *       isNull             【TAG】switchのkeyが、null(またはゼロ文字列)の場合、マッチするかどうか[true/false]を設定します(初期値:false)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:case&gt;
 *
 * ●使用例
 *         &lt;og:switch key="{&#064;PARAM}" &gt;
 *            &lt;og:case match="A" &gt; 処理A &lt;/og:case&gt;
 *            &lt;og:case match="B" &gt; 処理B &lt;/og:case&gt;
 *            &lt;og:case match="C" &gt; 処理C &lt;/og:case&gt;
 *            &lt;og:case isDefault="true" &gt; 処理X &lt;/og:case&gt;
 *         &lt;/og:switch&gt;
 *
 *          ・switch の key に対して、case の match に指定された値が、マッチ(switch_key.match( case_match ))
 *            した場合に、case の BODY 部分が処理されます。
 *            マッチしなければ、BODY部は、スキップされます。
 *          ・isDefault="true" の場合は、どれとも マッチしなかった場合に、実行されます。
 *          ・Javaの switch-case 文は、最初に処理された case 以降を処理します。通常は、break を入れて
 *            後続処理を実行されないようにしています。
 *            この、switch-case タグは、caseタグの isBreak 属性で制御します。初期値が isBreak="true" に、
 *            なっているため、通常は、どれかの case が実行された段階で、switchの処理は、終了されます。
 *            isBreak="false" にすると、switchから抜けずに、継続して case との match を実行します。
 *            この場合、Java等と異なるのは、直後のcase文が実行されるのではなく、あくまで match 作業が
 *            継続されるということです。つまり、複数の case で処理を行いたい場合は、isBreak="false" に
 *            すると同時に、match 条件もそれぞれで、マッチするように設定する必要があります。
 *
 *         &lt;og:switch key="{&#064;PARAM}" &gt;
 *            &lt;og:case match="[1]"   isBreak="false" &gt; 処理A &lt;/og:case&gt;
 *            &lt;og:case match="[12]"  isBreak="false" &gt; 処理B &lt;/og:case&gt;
 *            &lt;og:case match="[123]" isBreak="false" &gt; 処理C &lt;/og:case&gt;
 *            &lt;og:case isNull="true" &gt; 処理X &lt;/og:case&gt;
 *            &lt;og:case isDefault="true" &gt; 処理Y &lt;/og:case&gt;
 *         &lt;/og:switch&gt;
 *
 *          ・上記指定では、isBreak="false" が指定されているため、マッチした後も継続して判定処理が実施されます。
 *          ・上記例で言うと、PARAM が "1" の場合、上記３つともにマッチします。
 *          ・isNull="true" は、switch の key が null の場合に成立します。(null とは、ゼロ文字列も含む)
 *
 * @og.group 画面制御
 * @og.rev 5.2.3.0 (2010/12/01) 新規追加
 *
 * @version  5.2.3.0 (2010/12/01)
 * @author	 Kazuhiko Hasegawa
 * @since    JDK1.6,
 */
public class CaseTag extends CommonTagSupport {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.2.0 (2016/01/29)" ;
	private static final long serialVersionUID = 642020160129L ;

	private String	caseMatch	;			// switch_key.match( case_match ) でマッチした場合は、BODY が処理されます。
	private boolean	isDefault	;			// trueに設定すると、どのcase にもマッチしなかった場合に処理されます(初期値:false)
	private boolean	isBreak		= true ;	// 通常、最初にマッチした段階で、switch 処理を抜けます(初期値:true)
	private boolean	isNull		;			// trueに設定すると、switchのkeyが、null(またはゼロ文字列)の場合、マッチします(初期値:false)

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public CaseTag() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doStartTag() {
		final SwitchTag switchTag = (SwitchTag)findAncestorWithClass( this,SwitchTag.class );
		if( switchTag == null ) {
			final String errMsg = "<b>" + getTagName() + "タグは、switch タグの内部におく必要があります。</b>";
			throw new HybsSystemException( errMsg );
		}

		boolean rtn = false;
		if( switchTag.isMatch() ) {
			final String key = switchTag.getKey();
//			rtn =	( key != null && caseMatch != null && key.matches( caseMatch ) ) ||
//					( isNull && key == null ) || isDefault ;
			rtn =	key != null && caseMatch != null && key.matches( caseMatch )		// 6.9.7.0 (2018/05/14) PMD Useless parentheses.
				||	isNull && key == null
				||	isDefault ;

			if( rtn && isBreak ) { switchTag.setBreak(); }
		}

		if( rtn ) { return EVAL_BODY_INCLUDE ; }
		else      { return SKIP_BODY ;         }
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();		// 4.0.0 (2005/02/28)

		return EVAL_PAGE ;
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 */
	@Override
	protected void release2() {
		super.release2();
		caseMatch	= null ;	// switch_key.match( case_match ) でマッチした場合は、BODY が処理されます。
		isDefault	= false;	// trueに設定すると、どのcase にもマッチしなかった場合に処理されます(初期値:false)
		isBreak		= true ;	// 通常、最初にマッチした段階で、switch 処理を抜けます(初期値:true)
		isNull		= false;	// trueに設定すると、switchのkeyが、null(またはゼロ文字列)の場合、マッチします(初期値:false)
	}

	/**
	 * 【TAG】switch-case のマッチ条件(case_match)を指定します。
	 *
	 * @og.tag
	 * switch_key.match( case_match ) でマッチした場合は、BODY が処理されます。
	 *
	 * @param	mkey マッチ条件
	 */
	public void setMatch( final String mkey ) {
		caseMatch = nval( getRequestParameter( mkey ),caseMatch );
	}

	/**
	 * 【TAG】どのcase にもマッチしなかった場合に処理する case 文かどうかを指定します(初期値:false)。
	 *
	 * @og.tag
	 * trueに設定すると、どのcase にもマッチしなかった場合に処理されます
	 * このタグそのものがなにも出力しません。(つまり条件から消えます。)
	 * BODY 部に記述することが可能です。その場合は、value 属性になにも設定できません。
	 * 初期値は、false：default case 文ではない です。
	 *
	 * @param	flag デフォルト処理 [true:する/false:しない]
	 */
	public void setIsDefault( final String flag ) {
		isDefault = nval( getRequestParameter( flag ),isDefault );
	}

	/**
	 * 【TAG】マッチした以降に継続処理を行わない(ブレイクする)かどうかを指定(初期値:true)。
	 *
	 * @og.tag
	 * true に設定すると、マッチした段階で、ブレイクします。
	 * Javaの switch-case 文は、最初に処理された case 以降を処理します。通常は、break を入れて
	 * 後続処理を実行されないようにしています。
	 * この、switch-case タグは、caseタグの break 属性で制御します。初期値が break="true" に、
	 * なっているため、通常は、どれかの case が実行された段階で、switchの処理は、終了されます。
	 * break="false" にすると、switchから抜けずに、継続して case との match を実行します。
	 * この場合、Java等と異なるのは、直後のcase文が実行されるのではなく、あくまで match 作業が
	 * 継続されるということです。つまり、複数の case で処理を行いたい場合は、break="false" に
	 * すると同時に、match 条件もそれぞれで、マッチするように設定する必要があります。
	 * 初期値は、true：ブレイクする です。
	 *
	 * @param	flag ブレイク可否 [true:する/false:しない(=継続処理する)]
	 */
	public void setIsBreak( final String flag ) {
		isBreak = nval( getRequestParameter( flag ),isBreak );
	}

	/**
	 * 【TAG】switchのkeyが、null(またはゼロ文字列)の場合、マッチするかどうか[true/false]を設定します(初期値:false)。
	 *
	 * @og.tag
	 * trueに設定すると、switchのkeyが、null(またはゼロ文字列)の場合、マッチします。
	 * 初期値のfalse にすると、キーが null でない場合だけ、マッチ処理を実行します。
	 * case の条件判定で使用されます。
	 * 初期値は、 false (null でない) です。
	 *
	 * @param	flag nullマッチ判定 [true:する/false:しない]
	 */
	public void setIsNull( final String flag ) {
		isNull = nval( getRequestParameter( flag ),isNull );
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title( this.getClass().getName() )
				.println( "VERSION"			,VERSION	)
				.println( "caseMatch"		,caseMatch	)
				.println( "isDefault"		,isDefault	)
				.println( "isBreak"			,isBreak	)
				.println( "isNull"			,isNull		)
				.println( "Other..."	,getAttributes().getAttribute() )
				.fixForm().toString() ;
	}
}
