/*
 * 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.fukurou.system.OgBuilder ;				// 6.4.4.1 (2016/03/18)

import org.opengion.fukurou.util.TagBuffer;
import org.opengion.fukurou.util.XHTMLTag;
import org.opengion.fukurou.util.ToString;						// 6.1.1.0 (2015/01/17)
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.db.DBTableModel;
import org.opengion.hayabusa.db.DBTableModelUtil;

import static org.opengion.fukurou.util.StringUtil.nval;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Arrays;										// 6.4.3.4 (2016/03/11)
import java.util.stream.Collectors;								// 6.4.3.4 (2016/03/11)

/** タブ形式のリンクを表示するタグです。
 *
 * このタグ形式の実態は、リンクのリストであるため、実の画面の表示はターゲットを指定して
 * 別フレームで行う必要があります。
 *
 * タブの指定方法については、listType属性の指定により、クエリ発行(DB)により動的に生成する
 * パターンと、タグ指定(TAG)により、静的に生成するパターンがあります。
 * listType属性に何も指定されていない場合は、Body部分に記述された内容により、自動判定されます。
 * ("SELECT"で始まっている場合はDB、それ以外はTAGとして処理)
 *
 * ①listType属性が"DB"の場合
 *  検索された各カラムは、その順番により次の意味を持ちます。
 *  [第1カラム] タブの名前        : リンク時のキー情報、後述のopenTabName属性のキーとしても使用 ※必須
 *  [第2カラム] タブの表示名称    : タブの表示名称 指定がない場合は、第1カラムが表示名称となります。
 *  [第3カラム] タブのリンク先URL : タブのリンク先URL 指定がない場合は、href属性の値が適用されます。
 *  [第4カラム] タブのクラス属性  : 個別のタブに付加されるクラス属性 指定がない場合は、unselClass属性の値が適用されます。
 *  [第5カラム] タブのロールズ    : タブのロールズを指定します。ユーザーロールズと一致しない指定した場合は、タブが表示されなくなります。
 *  [第6カラム] タブの選択可否    : タブの選択可否を'true'or'false'で指定します。falseを指定した場合は、タブが表示されなくなります。
 *                                  (ロールズで選択不可になっている場合は、この値は無視されます)
 *  各カラムの値は[カラム名]=[値]の形で、リンク先のJSPに引数として渡されます。
 *  また、リンク先のJSPについては、href属性で指定します。
 *
 * ②listType属性が"TAG"の場合
 *  tabListタグを記述し、個別にタブを定義します。
 *  制御可能な項目は、①DBの場合と同等です。
 *  タブの名前を定義するname属性は、tabListタグで必ず定義する必要があります。
 *  lbl属性が指定されていない場合は、name属性のラベル名称を取得します。
 *  タブのリンク先JSP及び、クラス属性については、tabListタグで指定がない場合、tabListタグの値が適用されます。
 *
 * [共通設定]
 * 初期設定では、第1番目の"有効な"タブが自動的に開かれます。(="true")
 * 各タブの表示方法で、選択不可能なタブが存在している場合は、それらを読み飛ばした上で、"有効な"タブを
 * 検索します。
 * また、自動で開くタブは、openTabName属性で指定可能であり、これに変数を定義することで、
 * 画面リロード時も、開いていたタブを再度選択された状態で表示することが可能です。
 *
 * 7.4.1.0 (2021/04/23)
 *   openTab = false を指定すると、自動でオープンしません。
 *
 * 選択したタブ及び非選択のタブの枠線や、背景色等を変更する場合は、custom.cssでクラスを定義し、
 * 変更して下さい。
 *
 * タブの表示方向(水平方向 or 垂直方向)については、orientation属性で変更することが可能です。
 * (初期値は、水平方向)
 * 水平方向にした場合は、listCount属性により強制的に一定数のタブを表示する毎に、改行を挿入することができます。
 *
 * このタグを使用する場合は、headタグで必ずuseTabLink="true"を指定してJavaScriptをロードして下さい。
 *
 * 各属性は、{&#064;XXXX} 変数が使用できます。
 * これは、ServletRequest から、XXXX をキーに値を取り出し,この変数に割り当てます。
 * つまり、このXXXXをキーにリクエストすれば、この変数に値をセットすることができます。
 *
 * @og.formSample
 * ●形式：&lt;og:tabLink href="…" … /&gt;
 * ●body：あり(EVAL_BODY_BUFFERED:BODYを評価し、{&#064;XXXX} を解析します)
 *
 * ●Tag定義：
 *   &lt;og:tabLink
 *       listType           【TAG】タブの一覧をどこから取得するかを指定します(初期値:AUTO)
 *       href               【TAG】リンク先のJSPを指定します(初期値:result.jsp)
 *       target             【TAG】リンクのターゲットを指定します(初期値:RESULT)
 *       openTab            【TAG】リンク表示にタブリンクを自動で開くかどうか[true/false]を指定します(初期値:true[=開く])
 *       openTabName        【TAG】最初に開くタブリンクの名前を指定します
 *       constKeys          【TAG】次画面に渡す定数パラメーターのキーを指定します
 *       constVals          【TAG】次画面に渡す定数パラメーターの値を指定します
 *       listCount          【TAG】1行辺りに表示するタブの数を指定します(初期値:10)
 *       selClass           【TAG】選択タブのクラスを指定します(初期値:selTab)
 *       unselClass         【TAG】非選択タブのクラスを指定します(初期値:unselTab)
 *       orientation        【TAG】タブの方向、横型(Horizontal)か縦型(Vertical)を指定します(初期値:横型)
 *       width              【TAG】タブリンクの幅を % 、px 、または "auto" で指定します
 *       height             【TAG】タブの高さを、% 、px 、または "auto" で指定します
 *       onClick            【HTML】JavaScriptのイベント onClick を設定します(初期値：:onClick="changeTab( this, 'selTab' );") 7.4.1.0 (2021/04/23)
 *       caseKey            【TAG】このタグ自体を利用するかどうかの条件キーを指定します(初期値:null) 7.4.1.0 (2021/04/23)
 *       caseVal            【TAG】このタグ自体を利用するかどうかの条件値を指定します(初期値:null) 7.4.1.0 (2021/04/23)
 *       caseNN             【TAG】指定の値が、null/ゼロ文字列 でない場合(Not Null=NN)は、このタグは使用されます(初期値:判定しない) 7.4.1.0 (2021/04/23)
 *       caseNull           【TAG】指定の値が、null/ゼロ文字列 の場合は、このタグは使用されます(初期値:判定しない) 7.4.1.0 (2021/04/23)
 *       caseIf             【TAG】指定の値が、true/TRUE文字列の場合は、このタグは使用されます(初期値:判定しない) 7.4.1.0 (2021/04/23)
 *       debug              【TAG】デバッグ情報を出力するかどうか[true/false]を指定します(初期値:false)
 *   &gt;   ... Body ...
 *   &lt;/og:tabLink&gt;
 *
 * ●使用例
 *   ①DBからタブリストを取得する場合
 *
 *    Body部分に記述されたクエリよりタブ一覧を生成します。
 *
 *      &lt;og:tabLink
 *          listType        = "DB"                      タブの一覧をどこから取得するか
 *          href            = "result.jsp"              リンク先のJSP
 *          target          = "RESULT"                  リンクターゲット
 *          openTab         = "[true/false]"            タブ表示後にタブを自動で開く
 *          openTabName     = "{&#064;PN}               自動で開くタブの名前
 *          constKeys       = "KEY1"                    次画面に固定で渡すキー一覧
 *          constVals       = "{&#064;VAL1}"            次画面に固定で渡す値一覧
 *          listCount       = "10"                      1行辺りに表示するタブの数
 *          selClass        = "selTab"                  選択タブのクラス
 *          unselClass      = "unselTab"                非選択タブのクラス
 *          width           = "100px"                   タブリンクの幅
 *          height          = "50px"                    タブリンクの高さ
 *       &gt;
 *               select PN,HINM,'tabClass','query.jsp','ADMIN','false' from XX01 where PN = '{&#064;PN}' order by PN
 *      &lt;/og:tabLink&gt;
 *
 *   ②tabListタグからタブリストを生成する場合
 *
 *    tabListタグよりタブ一覧を生成します。
 *
 *      &lt;og:tabLink
 *          listType        = "DB"                      タブの一覧をどこから取得するか
 *          href            = "result.jsp"              リンク先のJSP
 *          target          = "RESULT"                  リンクターゲット
 *          openTab         = "[true/false]"            タブ表示後にタブを自動で開く
 *          openTabName     = "{&#064;PN}               自動で開くタブの名前
 *          constKeys       = "KEY1"                    次画面に固定で渡すキー一覧
 *          constVals       = "{&#064;VAL1}"            次画面に固定で渡す値一覧
 *          listCount       = "10"                      1行辺りに表示するタブの数
 *          selClass        = "selTab"                  選択タブのクラス
 *          unselClass      = "unselTab"                非選択タブのクラス
 *          width           = "100px"                   タブリンクの幅
 *          height          = "50px"                    タブリンクの高さ
 *       &gt;
 *          &lt;og:tabList name="TAB1" href="result1.jsp" keys="PN,CDK" vals="ABC,V" /&gt;
 *          &lt;og:tabList name="TAB2" href="result2.jsp" keys="PN,CDK" vals="BCD,W" /&gt;
 *          &lt;og:tabList name="TAB3" href="result3.jsp" keys="PN,CDK" vals="CDE,X" /&gt;
 *      &lt;/og:tabLink&gt;
 *
 * @og.group 画面表示
 *
 * @version  0.9.0	2008/09/26
 * @author	 Nakamura
 * @since	 JDK1.4,
 */
public class TabLinkTag extends CommonTagSupport {
	private static final String VERSION = "7.4.1.0 (2021/04/23)" ;
	private static final long serialVersionUID = 741020210423L ;

	/** リストのulタグのclass属性 */
	private static final String		UL_TAG_START		= "<ul class=\"tabList\">";
	private static final String		UL_TAG_END			= "</ul>";

	/** タブ表示を入れ替えるためのJavaScript関数 */
	private static final String		CHANGE_TAB_SCRIPT	= "changeTab";
	private static final String		INITIAL_TAB_SCRIPT	= "initialTabSelect";

	/** 自動で開くタブに付加されるID */
	private static final String		FIRST_TAB_ID		= "firstTab";

	/** リスト取得タイプのEnum */
	private enum LIST_TYPE { AUTO, DB, TAG };
//	private static enum LIST_TYPE { AUTO, DB, TAG };

	/** 内部変数 */
	private String		query			;
	private transient List<TabData>		tabData		= new ArrayList<>();

	/** タグで設定する属性 */
	private LIST_TYPE	type			= LIST_TYPE.AUTO;
	private String		href			= "result.jsp";
	private String		target			= "RESULT";
	private boolean		openTab			= true;
	private String		openTabName		;
	private String[]	constKeys		;
	private String[]	constVals		;
	private int			listCount		= 10;
	private String		selClass		= "selTab";
	private String		unselClass		= "unselTab";
	private boolean		isHorizontal	= true;
	private String		width			= "auto";
	private String		height			= "auto";
	private String		onClick			= CHANGE_TAB_SCRIPT;		// 7.4.1.0 (2021/04/23)

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

	/**
	 * Taglibの開始タグが見つかったときに処理する doStartTag() を オーバーライドします。
	 *
	 * @og.rev 7.4.1.0 (2021/04/23) caseKey,caseVal,caseNN,caseNull,caseIf 属性を追加
	 *
	 * @return	後続処理の指示( EVAL_BODY_BUFFERED )
	 */
	@Override
	public int doStartTag() {
		// 7.4.1.0 (2021/04/23) caseKey,caseVal,caseNN,caseNull,caseIf 属性を追加
		if( !useTag() ) { return SKIP_BODY ; }

		return EVAL_BODY_BUFFERED ;	// Body を評価する
	}

	/**
	 * Taglibのタグ本体を処理する doAfterBody() を オーバーライドします。
	 *
	 * @og.rev 6.3.1.1 (2015/07/10) BodyString,BodyRawStringは、CommonTagSupport で、trim() します。
	 *
	 * @return	後続処理の指示(SKIP_BODY)
	 */
	@Override
	public int doAfterBody() {
		query = getBodyString();
		return SKIP_BODY ;
	}

	/**
	 * Taglibの終了タグが見つかったときに処理する doEndTag() を オーバーライドします。
	 *
	 * @og.rev 4.3.5.0 (2008/02/01) 処理及び内部構造を大幅に見直し
	 * @og.rev 7.4.1.0 (2021/04/23) caseKey,caseVal,caseNN,caseNull,caseIf 属性を追加
	 *
	 * @return	後続処理の指示
	 */
	@Override
	public int doEndTag() {
		debugPrint();

		// 7.4.1.0 (2021/04/23) caseKey,caseVal,caseNN,caseNull,caseIf 属性を追加
		if( !useTag() ) { return EVAL_PAGE ; }

//		final int rtnCode = EVAL_PAGE;		// 7.4.1.0 (2021/04/23) 書き換えてないので、直接指定します。

		// 種別の自動判定処理
		if( type == LIST_TYPE.AUTO ) {
			if( query == null || query.isEmpty() ) {
				type = LIST_TYPE.TAG;
			}
			else {
				if( query.toUpperCase( Locale.JAPAN ).indexOf( "SELECT" ) >= 0 ) {
					type = LIST_TYPE.DB;
				}
				else {
					type = LIST_TYPE.TAG;
				}
			}
		}

		if( type == LIST_TYPE.DB ) {
			makeTabsFromQuery();
		}
		else if( type == LIST_TYPE.TAG ) {
			makeTabsFromTag();
		}

		// リンク一覧が何も設定されていない場合は、処理しない
		if( ! tabData.isEmpty() ) {
			makeTag();
		}

//		return rtnCode ;
		return EVAL_PAGE ;		// 7.4.1.0 (2021/04/23)
	}

	/**
	 * タグリブオブジェクトをリリースします。
	 * キャッシュされて再利用されるので、フィールドの初期設定を行います。
	 *
	 * @og.rev 7.4.1.0 (2021/04/23) タブをクリックした際のJavaScriptを外部から設定できるようにします。
	 */
	@Override
	protected void release2() {
		super.release2();
		query			= null;
		tabData			= new ArrayList<>();
		type			= LIST_TYPE.AUTO;
		href			= "result.jsp";
		target			= "RESULT";
		openTab			= true;
		openTabName		= null;
		constKeys		= null;
		constVals		= null;
		listCount		= 10;
		selClass		= "selTab";
		unselClass		= "unselTab";
		isHorizontal	= true;
		width			= "auto";
		height			= "auto";
		onClick			= CHANGE_TAB_SCRIPT;		// 7.4.1.0 (2021/04/23)
	}

	/**
	 * DBからタブリンクの一覧を作成します。
	 * DBTableModelが作成されない(行数が0の場合)は、リンク一覧は生成されません。
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) ３項演算子を || or &amp;&amp; で簡素化できる(PMD)。
	 */
	private void makeTabsFromQuery() {
		final DBTableModel table = DBTableModelUtil.makeDBTable( query, new String[0], getResource(), getApplicationInfo() );
		if( table == null || table.getRowCount() == 0 ) {
			return;
		}

		// 6.3.9.1 (2015/11/27) 初期値設定の簡素化
		final boolean isSetLabel	= table.getColumnCount() > 1;
		final boolean isSetHref		= table.getColumnCount() > 2;
		final boolean isSetClazz	= table.getColumnCount() > 3;
		final boolean isSetRoles	= table.getColumnCount() > 4;
		final boolean isSetVisible	= table.getColumnCount() > 5;

		final int rowCount = table.getRowCount();
		final String key   = table.getColumnName( 0 );
		// 6.3.9.1 (2015/11/27) visible=true以外は処理しないので、先に求める。
		for( int row=0; row<rowCount; row++ ) {
			final boolean visible = ( !isSetRoles   || getUser().isAccess( table.getValue( row, 4 ) ) ) &&
									( !isSetVisible || Boolean.valueOf(    table.getValue( row, 5 ) ) );

			if( visible ) {
				final String value	= table.getValue( row, 0 );
				final String label	= ( isSetLabel ? nval( table.getValue( row, 1 ), value ) : value );
				String newHref	= ( isSetHref  ? nval( table.getValue( row, 2 ), href ) : href );
				// 第1カラムのカラム名とその値はリンクの引数に含める
				newHref	= XHTMLTag.addUrlEncode( newHref, XHTMLTag.urlEncode( key, value ) );

				final String clazz	= ( isSetClazz ? nval( table.getValue( row, 3 ), unselClass ) : unselClass );

				tabData.add( new TabData( newHref, value, label, clazz, visible ) );
			}
		}
	}

	/**
	 * タブリストからタブリンクの一覧を作成します。
	 * (予めaddTagメソッドにより、リンク一覧が登録されているため、ここでは何も処理しません)
	 *
	 * @see #addTag( String, String, String, String, boolean, String[], String[] )
	 */
	private void makeTabsFromTag() {
		// 何もありません。(PMD エラー回避)
	}

	/**
	 * 子タグであるタブリストタグからタブ情報をセットします。
	 *
	 * @param hr 画面URL
	 * @param name タブの名前
	 * @param label タブの表示名称
	 * @param clz 非選択状態のタブに付加するclass名
	 * @param visible タブが選択可能 [true:可能/false:不可能]
	 * @param keys リンク先のJSPに渡すキー一覧
	 * @param vals リンク先のJSPに渡す値一覧
	 */
	protected void addTag( final String hr, final String name, final String label, final String clz
							,final boolean visible, final String[] keys, final String[] vals ) {
		String newHref = nval( hr, href );
		if( keys != null && keys.length > 0 ) {
			newHref = XHTMLTag.addUrlEncode( newHref, XHTMLTag.urlEncode( keys, vals ) );
		}

		if( visible ) { // visible=falseの場合は表示しない
			tabData.add( new TabData( newHref, name, nval( label, getLabel( name ) ), nval( clz, unselClass ), visible ) );
		}
	}

	/**
	 * リンク一覧からHTMLタグを作成します。
	 *
	 * @og.rev 5.0.2.0 (2009/11/01) openTab属性がfalseの場合でも、openTabNameに指定されたタブに色付けする。
	 * @og.rev 5.1.4.0 (2010/03/01) FF対応&FirstTabのID付加に関するバグを修正
	 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
	 * @og.rev 7.4.1.0 (2021/04/23) openTab=false の場合は、自動でオープンしません。
	 */
	private void makeTag() {
		final OgBuilder buf = new OgBuilder().appendCR();

		boolean isExistFirst = false;
		for( int idx=0; idx<tabData.size(); idx++ ) {
			final TabData tab = tabData.get( idx );

			if( idx % listCount == 0 ) {
				buf.appendIfCR( idx > 0 , UL_TAG_END )				// if
					.appendCR( UL_TAG_START );
			}

			// openTabNameが定義されていない場合は、1番目の有効なタブを開く
			// 5.1.4.0 (2010/03/01) バグ修正
			if( !isExistFirst && tab.visible
				&& ( openTabName == null || openTabName.isEmpty() || openTabName.equals( tab.name ) ) ) {
				// 7.4.1.0 (2021/04/23) 選択タブのクラスをセットします。
				final TabData tab2 = tab.copyTab( selClass );

//				buf.append( tab.makeLiTag( idx, true ) );
				buf.append( tab2.makeLiTag( idx, true ) );
				isExistFirst = true;
			}
			else {
				buf.append( tab.makeLiTag( idx, false ) );
			}
		}
		// 7.4.1.0 (2021/04/23) openTab=false の場合は、自動でオープンしません。
		buf.appendCR( UL_TAG_END )
//			.appendIf( openTab || openTabName != null && openTabName.length() > 0	// 6.9.7.0 (2018/05/14) PMD Useless parentheses.
			.appendIf( openTab && openTabName != null && openTabName.length() > 0	// 6.9.7.0 (2018/05/14) PMD Useless parentheses.
					// 8.1.0.0 (2021/12/28) HTML5 準拠に見直し(<script> type属性削除)
//					, "<script type=\"text/javascript\">addEvent(window,\"load\", function() { "
					, "<script>addEvent(window,\"load\", function() { "
					, INITIAL_TAB_SCRIPT
					, "(" , String.valueOf( openTab ) , "); } );</script>" );

		jspPrint( buf.toString() );
	}

	/**
	 * Tabデータ を管理している 内部クラス
	 *
	 * タブの情報を管理するための簡易的な、内部クラスです。
	 */
	private final class TabData {
		// 引数として初期設定される変数
		private final String href;
		private final String name;
		private final String label;
		private final String clazz;
		// 現状の実装では、visible=falseは渡ってきませんが、将来的にdisableの状態で
		// 表示したい場合等に対応するため残しておきます。
		private final boolean visible;

		/**
		 * コンストラクタ
		 *
		 * @param hr 画面URL
		 * @param nm タブの名前
		 * @param lbl タブの表示名称
		 * @param clz 非選択状態のタブに付加するclass名
		 * @param vsb タブが選択可能 [true:可能/false:不可能]
		 */
		public TabData( final String hr, final String nm, final String lbl, final String clz, final boolean vsb ) {
			href	= hr;
			name	= nm;
			label	= lbl;
			clazz	= clz;
			visible	= vsb;
		}

		/**
		 * class名違いの新しいTabDataを作成します。
		 *
		 * 通常、選択状態のｸﾗｽ名を付ける場合に、使用します。
		 *
		 * @param clz タブに付加するclass名
		 * @return class名違いの新しいTabData
		 */
		public TabData copyTab( final String clz ) {
			return new TabData( href,name,label,clz,visible );
		}

		/**
		 * liタグの部分の文字列を生成します。
		 *
		 * @og.rev 6.4.4.1 (2016/03/18) StringBuilderの代わりに、OgBuilderを使用する。
		 *
		 * @param idx		生成したタブのインデックス番号
		 * @param isFirst	始めの有効なタブかどうか[true:始め/false:それ以外]
		 * @return liタグ文字列
		 * @og.rtnNotNull
		 */
		private String makeLiTag( final int idx, final boolean isFirst ) {
			return new OgBuilder()
				.append( "<li class=\""	, clazz , "\""
						, " style=\""
						, " width: "	, width	 , ";"
						, " height: "	, height , ";" )
				.appendIf( isHorizontal	, " float: left;" )
				.appendIf( isHorizontal && ( idx % listCount == 0 )	// if-if なので、少し無駄かも
						, " clear: left; margin-left: "
						, String.valueOf( (idx/listCount) * 10 )
						, "px;" )
				.appendCR( " \">" , makeLinkTag( isFirst ) , "</li>" )
				.toString();
		}

		/**
		 * aタグの部分の文字列を生成します。
		 * タブが選択不可能な状態の場合は、タブの表示文字列をそのまま返します。
		 *
		 * @og.rev 4.3.6.4 戻るボタンがでない問題への対応
		 * @og.rev 7.4.1.0 (2021/04/23) タブをクリックした際のJavaScriptを外部から設定できるようにします。
		 *
		 * @param	isFirst	始めの有効なタブかどうか[true:始め/false:それ以外]
		 * @return	liタグ文字列
		 * @og.rtnNotNull
		 */
		private String makeLinkTag( final boolean isFirst ) {
			String newHref = XHTMLTag.addUrlEncode( href, XHTMLTag.urlEncode( constKeys, constVals ) );
			// 4.3.6.4 (2009/05/01)
			// タブ画面から遷移した時に、タブの読込により、画面IDが消えてしまい
			// 戻るボタンがでない不具合への対応
			newHref = XHTMLTag.addUrlEncode( newHref, "GAMENID=" + getGUIInfoAttri( "KEY" ) );

			// 7.4.1.0 (2021/04/23) タブをクリックした際のJavaScriptを外部から設定できるようにします。
			if( CHANGE_TAB_SCRIPT.equals( onClick ) ) {			// つまり、デフォルトのまま
				onClick = CHANGE_TAB_SCRIPT + "( this, \"" + selClass + "\" );" ;
			}

			// 6.1.1.0 (2015/01/17) TagBufferの連結記述
			return new TagBuffer( "a" )
					.add( "href"	, newHref )
					.add( "name"	, name )
					.add( "target"	, target )
//					.add( "onClick"	, CHANGE_TAB_SCRIPT + "( this, \"" + selClass + "\" );" )
					.add( "onClick"	, onClick		, onClick != null )			// 7.4.1.0 (2021/04/23) null 以外は追加する。
					.add( "id"		, FIRST_TAB_ID	, isFirst )
					.addBody( label )
					.makeTag();
		}
	}

	/**
	 * 【TAG】タブの一覧をどこから取得するか[AUTO/DB/TAG]を指定します(初期値:AUTO)。
	 *
	 * @og.tag
	 * タブの一覧をどこから取得するかを指定します。
	 * 現状の実装では、クエリを発行して一覧を生成する「DB」と、子タグである
	 * tabListタグを列挙してタブを定義する「TAG」が実装されています。
	 *
	 * また、「AUTO」と指定した場合は、Body部分の内容に応じて自動的に判定されます。
	 * 初期値は、｢AUTO」です。
	 *
	 * @og.rev 6.4.3.4 (2016/03/11) CSV形式の文字連結を、stream 経由で行います。
	 *
	 * @param	tp 取得方法 [AUTO/DB/TAG]
	 */
	public void setListType( final String tp ) {
		final String typeStr = nval( getRequestParameter( tp ), null );
		try {
			type = LIST_TYPE.valueOf( typeStr );
		}
		catch( final IllegalArgumentException ex ) {
			final String errMsg = Arrays.stream( LIST_TYPE.values() )
									.map( obj -> obj.name() )
									.collect( Collectors.joining( "," , "listType は " , " から選んでください。" ) );	// 連結文字 , 最初 , 最後
			throw new HybsSystemException( errMsg, ex );
		}
	}

	/**
	 * 【TAG】リンク先のJSPを指定します(初期値:result.jsp)。
	 *
	 * @og.tag
	 * リンク先のJSPを指定します。
	 * このタブリンクは、あくまで「タブの形をしたリンク」なので、
	 * target属性と合わせてセットする必要があります。
	 * 初期値は、｢result.jsp」です。
	 *
	 * @param	hr リンク先のJSP
	 */
	public void setHref( final String hr ) {
		href = nval( getRequestParameter( hr ), href );
	}

	/**
	 * 【TAG】リンクのターゲットを指定します(初期値:RESULT)。
	 *
	 * @og.tag
	 * リンクのターゲットを指定します。
	 * このタブリンクは、あくまで「タブの形をしたリンク」なので、
	 * target属性を設定し、別のフレームに実画面を表示するようにします。
	 * 初期値は、｢RESULT」です。
	 *
	 * @param	tgt リンクターゲット
	 */
	public void setTarget( final String tgt ) {
		target = nval( getRequestParameter( tgt ), target );
	}

	/**
	 * 【TAG】リンク表示にタブリンクを自動で開くかどうか[true/false]を指定します(初期値:true[=開く])。
	 *
	 * @og.tag
	 * リンク表示にタブリンクを自動で開くかを指定します。
	 * openTabName属性が指定されていない場合、自動で開くタブは
	 * 「1番目に表示されたタブリンク」です。
	 * 指定されている場合は、その名前を持つ「1番目」のタブが自動で開かれます。
	 * タブが選択不可能な状態の場合は、「1番目」の条件から除外されます。
	 * 初期値は、「true(開く)」です。
	 *
	 * 7.4.1.0 (2021/04/23)
	 *   openTab = false を指定すると、自動でオープンしません。
	 *
	 * @param	flag 自動タブオープン [true:自動で開く/false:開かない]
	 */
	public void setOpenTab( final String flag ) {
		openTab = nval( getRequestParameter( flag ), openTab );
	}

	/**
	 * 【TAG】最初に開くタブリンクの名前を指定します。
	 *
	 * @og.tag
	 * 最初に開くタブリンクのキーを指定します。
	 *
	 * @param	name 初期表示タブ名前
	 */
	public void setOpenTabName( final String name ) {
		openTabName = nval( getRequestParameter( name ), openTabName );
	}

	/**
	 * 【TAG】次画面に渡す定数パラメーターのキーを指定します。
	 *
	 * @og.tag
	 * 次画面に渡す定数パラメーターのキーを指定します。
	 * キーはCSV形式で複数指定が可能です。
	 * パラメーターの値は、constVals属性の数と一致している必要があります。
	 *
	 * @param	keys 定数キー (CSV形式)
	 * @see		#setConstVals( String )
	 */
	public void setConstKeys( final String keys ) {
		constKeys = getCSVParameter( keys );
	}

	/**
	 * 【TAG】次画面に渡す定数パラメーターの値を指定します。
	 *
	 * @og.tag
	 * 次画面に渡す定数パラメーターの値を指定します。
	 * 値はCSV形式で複数指定が可能です。
	 * パラメーターの値は、constKeys属性の数と一致している必要があります。
	 *
	 * @param	vals 定数値 (CSV形式)
	 * @see		#setConstKeys( String )
	 */
	public void setConstVals( final String vals ) {
		constVals = getCSVParameter( vals );
	}

	/**
	 * 【TAG】1行辺りに表示するタブの数を指定します(初期値:10)。
	 *
	 * @og.tag
	 * 1行辺りに表示するタブの数を指定します。
	 * 1行辺りのタブの数がこの設定を超えると、自動的に折り返します。
	 * また、折り返し毎に、左に10pxのマージンを設けます。
	 * 初期値は、10です。
	 * この属性は、orientationがHorizontal(水平方向)の場合のみ有効です。
	 *
	 * @param	cnt 1行タブ数
	 */
	public void setListCount( final String cnt ) {
		listCount = nval( getRequestParameter( cnt ), listCount );
	}

	/**
	 * 【TAG】選択タブのクラスを指定します(初期値:selTab)。
	 *
	 * @og.tag
	 * タブが選択されている状態にある場合の、タブ部分のクラス名を指定します。
	 * このクラス名を変更する場合は、そのクラスをcustom/custom.css等で再定義して下さい。
	 * 初期値は、selTabです。
	 *
	 * @param	cls 選択タブのクラス名
	 */
	public void setSelClass( final String cls ) {
		selClass = nval( getRequestParameter( cls ), selClass );
	}

	/**
	 * 【TAG】非選択タブのクラスを指定します(初期値:unselTab)。
	 *
	 * @og.tag
	 * タブが選択されていない状態にある場合の、タブ部分のクラス名を指定します。
	 * このクラス名を変更する場合は、そのクラスをcustom/custom.css等で再定義して下さい。
	 * 初期値は、unselTabです。
	 *
	 * @param	cls 選択タブのクラス名
	 */
	public void setUnselClass( final String cls ) {
		unselClass = nval( getRequestParameter( cls ), unselClass );
	}

	/**
	 * 【TAG】タブの方向[Horizontal(or H):横型/Vertical(or V):縦型]を指定します(初期値:Horizontal:横型)。
	 *
	 * @og.tag
	 * タブは、上にタブが並ぶ横型と左にタブが並ぶ縦型があります。
	 * この属性では、横型は、Horizontal 、縦型は、Vertical を指定します。
	 * 指定は、文字列の最初の一文字を見ているだけですので、HかVでも構いません。
	 *
	 * 縦型(Vertical)にした場合、各タブ要素は、フレームサイズの幅に合わせて
	 * 最大で表示されます。幅を固定する場合は、width属性を指定して下さい。
	 *
	 * 初期値は、横型(Horizontal) です。
	 *
	 * @param	ori タブの方向 [Horizontal(or H):横型/Vertical(or V):縦型]
	 */
	public void setOrientation( final String ori ) {
		final String ori2 = nval( getRequestParameter( ori ),null );
		if( ori2 != null && ori2.length() > 0 ) {
			final char ch = ori2.toUpperCase(Locale.JAPAN).charAt(0);
			if( ch == 'H' ) { isHorizontal = true; }
			else if( ch == 'V' ) { isHorizontal = false; }
			else {
				final String errMsg = "orientation の指定は、H(orizontal) または、V(ertical) です。"
							+ " orientation=" + ori2 ;			// 5.1.8.0 (2010/07/01) errMsg 修正
				throw new HybsSystemException( errMsg );
			}
		}
	}

	/**
	 * 【TAG】タブリンクの幅を % 、px 、または "auto" で指定します(初期値:auto)。
	 *
	 * @og.tag
	 * 初期値は、"auto"(自動設定) です。
	 * autoの場合、横型表示では、文字の幅に合わせて自動的に調整され、
	 * 縦型表示の場合は、フレームサイズに合わせて拡大して表示されます。
	 *
	 * @param	wh	幅 (% 、px 、または "auto" )
	 */
	public void setWidth( final String wh ) {
		width = nval( getRequestParameter( wh ),width );
	}

	/**
	 * 【TAG】タブの高さを、% 、px 、または "auto" で指定します(初期値:auto)。
	 *
	 * @og.tag
	 * タブの高さを、% 、px 、または "auto" で指定します
	 * 初期値は、"auto"(自動設定) です。
	 *
	 * @param	ht	高さ (% 、px 、または "auto" )
	 */
	public void setHeight( final String ht ) {
		height = nval( getRequestParameter( ht ),height );
	}

	/**
	 * 【HTML】JavaScriptのイベント onClick を設定します(初期値：:onClick="changeTab( this, 'selTab' );")。
	 *
	 * @og.tag
	 * onClick をセットします。
	 * 初期値は、選択タブをクリックしたタブの色を新しく選択されたタブの色と入れ替えます。
	 * ゼロ文字列を指定すると、onClick を何も指定しないことになります。
	 * 初期値：:onClick="changeTab( this, 'selTab' );"
	 *
	 * @og.rev 7.4.1.0 (2021/04/23) タブをクリックした際のJavaScriptを外部から設定できるようにします。
	 *
	 * @param	scrpt	onClickイベント
	 */
	public void setOnClick( final String scrpt ) {
		// ゼロ文字列を指定すると、null になります。
		onClick = nval( getRequestParameter( scrpt ),null );
	}

	/**
	 * シリアライズ用のカスタムシリアライズ読み込みメソッド
	 *
	 * ここでは、transient 宣言された内部変数の内、初期化が必要なフィールドのみ設定します。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) tabData の初期化処理 追加
	 * @serialData 一部のオブジェクトは、シリアライズされません。
	 *
	 * @param	strm	ObjectInputStreamオブジェクト
	 * @see #release2()
	 * @throws IOException	シリアライズに関する入出力エラーが発生した場合
	 * @throws ClassNotFoundException	クラスを見つけることができなかった場合
	 */
	private void readObject( final ObjectInputStream strm ) throws IOException, ClassNotFoundException {
		strm.defaultReadObject();
		tabData		= new ArrayList<>();
	}

	/**
	 * このオブジェクトの文字列表現を返します。
	 * 基本的にデバッグ目的に使用します。
	 *
	 * @return このクラスの文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {
		return ToString.title(this.getClass().getName() )
		.println( "VERSION"       , VERSION )
		.println( "listType"      , type.toString() )
		.println( "href"          , href )
		.println( "target"        , target )
		.println( "openTab"       , openTab )
		.println( "openTabName"   , openTabName )
		.println( "constKeys"     , constKeys )
		.println( "constVals"     , constVals )
		.println( "listCount"     , listCount )
		.println( "selClass"      , selClass )
		.println( "unselClass"    , unselClass )
		.println( "isHorizontal"  , isHorizontal )
		.println( "width"         , width )
		.println( "height"        , height )
		.println( "Other...", getAttributes().getAttribute() ).fixForm().toString();
	}
}
