/*
 * 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.resource;

import java.util.Arrays;
import java.util.HashMap;
import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.LinkedHashSet;										// 8.0.0.0 (2021/10/01)

import java.util.stream.Stream;										// 6.4.1.1 (2016/01/16)
import java.util.stream.Collectors;									// 6.4.1.1 (2016/01/16)

import org.opengion.fukurou.util.ErrMsg;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.db.DBUtil;								// 7.2.9.2 (2020/10/30)
import org.opengion.fukurou.db.ApplicationInfo;						// 7.2.9.2 (2020/10/30)
import org.opengion.hayabusa.common.HybsSystem;						// 7.2.9.2 (2020/10/30)
import org.opengion.hayabusa.common.HybsSystemException;			// 6.4.3.3 (2016/03/04)
import org.opengion.hayabusa.db.DBColumn;
import org.opengion.hayabusa.db.DBColumnConfig;
import static org.opengion.fukurou.system.HybsConst.BUFFER_LARGE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * java.util.ResourceBundle クラスを複数管理するリソースクラスです。
 *
 * ResourceManager は、
 *      LabelResource.properties   ラベルリソース(テーブル定義やカラム名などの画面に表示するリソース)
 *      CodeResource.properties    コードリソース(選択データなどプルダウンメニューで選択するリソース)
 *      MessageResource.properties メッセージリソース(エラーコードやメッセージなどを表示するリソース)
 *
 * の３つのプロパティーファイルを内部に持っており,それぞれのメソッドにより，
 * リソースの返す値を決めています。
 *
 * ResourceManagerは,単独でも生成できますが，各ユーザー毎に作成するよりも
 * ResourceFactory#newInstance( lang )メソッドより生成した方が,プーリングされるので
 * 効率的です。
 *
 * リソース作成時に指定するロケールは,ISO 言語コード(ISO-639 で定義される 2 桁の小文字)
 * <a href ="http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt">
 * http://www.ics.uci.edu/pub/ietf/http/related/iso639.txt</a>を使用して下さい。
 * ただし，内部的に Locale を構築していますが,その正しさは,チェックされていませんので,
 * 指定するロケールに応じた properties ファイルを用意しておいて下さい。
 *
 * 日本語の場合は, 言語コードは "jp" なので，
 *      LabelResource_jp.properties   ラベルリソース(日本語)
 *      CodeResource_jp.properties    コードリソース(日本語)
 *      MessageResource_jp.properties メッセージリソース(日本語)
 *
 * を用意して下さい。
 *
 * CodeResource については、リソースファイルから CodeSelectionオブジェクトを
 * 作成して利用します。この、CodeSelectionオブジェクトの作成方法として、
 * ３通り考えられます。
 * １つ目は、毎回 要求が発生する毎に CodeSelection を作成し、プールしていきます。こうすることで、
 * 初めて使用されたときだけオブジェクト化されますので、メモリの節約が可能です。ただし、
 * プールにヒットしなかった場合は、やはりリソースから検索しますので、元々ヒットしない
 * キーに対しては、毎回リソースを検索するため、非効率です。
 * ２つめは、元々ヒットしないキーに対して、NullCodeSelectionオブジェクトを登録しておくことで、
 * プールにため込んで行くと言う方法です。この場合は、シングルトーンにしてメモリを節約しますが、
 * それでもプール自体の容量は、確保しておく必要があります。
 * ３つめは、この ResourceManager がインスタンス化されるときに、すべての CodeSelectionオブジェクトを
 * あらかじめ プールしておく方法です。使わない CodeSelection もインスタンス化する変わりに、
 * キャッシュにヒットしない場合は、即 CodeSelection が存在しないと判断できるため、
 * もっともパフォーマンスが高くなります。
 * 本 ResourceManager の実装は、３つめの、あらかじめ、すべてをキャッシュしておく方法を
 * 採用しています。
 *
 * @og.group リソース管理
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class ResourceManager {
	// 7.2.9.2 (2020/10/30) ベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)の取得
	private static final String QUERY = "select PARAM from GE12"
										+ " where SYSTEM_ID=? and FGJ='1' and PARAM_ID='RESOURCE_BASE_SYSTEM_ID'";

	// 7.2.9.2 (2020/10/30) リソースの接続先を、取得します。
	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );

	private final ColumnDataLoader	columnLoader ;
	private final CodeDataLoader	codeLoader;
	private final LabelDataLoader	labelLoader;
	private final GUIDataLoader		guiLoader;

	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private final ConcurrentMap<String,DBColumn> columnPool = new ConcurrentHashMap<>( BUFFER_LARGE );
	private final String	lang ;

	/**
	 * コンストラクター
	 * システムＩＤと言語コードを指定して,生成します。
	 *
	 * @og.rev 7.2.9.2 (2020/10/30) ベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)の取得
	 * @og.rev 8.0.0.0 (2021/10/01) RESOURCE_BASE_SYSTEM_ID は、CSV形式で複数指定できる(先頭優先)。
	 *
	 * @param	systemId システムＩＤ
	 * @param	lang 言語コード
	 * @param	initLoad リソースデータの先読み可否(true:先読みする)
	 */
	public ResourceManager( final String systemId,final String lang,final boolean initLoad ) {
		this.lang = lang;

		// 8.0.0.0 (2021/10/01)
		final Set<String> sysSet = new LinkedHashSet<>();			// 順序をキープし、重複を排除する。
		sysSet.add( systemId );		// 通常システムＩＤは、一番最初

		// 7.2.9.2 (2020/10/30)  ベースとなるSYSTEM_ID(RESOURCE_BASE_SYSTEM_ID)の取得
		final String[] args = new String[] { systemId };
		final String[][] vals = DBUtil.dbExecute( QUERY,args,(ApplicationInfo)null,DBID );
//		final String baseSys = vals != null && vals.length > 0 && vals[0].length > 0 && vals[0][0].length() > 0 ?
//									StringUtil.nval( vals[0][0] , "**" ) : "**" ;
		if( vals != null && vals.length > 0 && vals[0].length > 0 && vals[0][0].length() > 0 ) {
			for( final String sysId : vals[0][0].split( "," ) ) {	// AA,BB,CC 形式を分割
				final String sys = sysId.trim();
				if( !sys.isEmpty() ) { sysSet.add( sys ); }			// 空文字列は登録しない。
			}
		}
		sysSet.add( "**" );		// "**" は、一番最後に追加する。

		final String[] sysAry = sysSet.toArray( new String[sysSet.size()] );

//		columnLoader	= new ColumnDataLoader(	systemId,baseSys,initLoad );
//		labelLoader		= new LabelDataLoader(	systemId,baseSys,lang,initLoad );
//		codeLoader		= new CodeDataLoader(	systemId,baseSys,initLoad,labelLoader );
//		guiLoader		= new GUIDataLoader(	systemId,baseSys );

		columnLoader	= new ColumnDataLoader(	sysAry,initLoad );
		labelLoader		= new LabelDataLoader(	sysAry,lang,initLoad );
		codeLoader		= new CodeDataLoader(	sysAry,initLoad,labelLoader );
		guiLoader		= new GUIDataLoader(	sysAry );
	}

	/**
	 * 設定されている言語を返します。
	 *
	 * @return	言語
	 */
	public String getLang() {
		return lang;
	}

	/**
	 * DBColumn オブジェクトを取得します。
	 * 作成したDBColumnオブジェクトは，内部にプールしておき，同じオブジェクト要求が
	 * あったときは，プールのオブジェクトを利用して,DBColumnを返します。
	 *
	 * @og.rev 3.4.0.0 (2003/09/01) ラベルカラム、コードカラム、表示パラメータ、編集パラメータ、文字パラメータの追加。
	 * @og.rev 3.5.6.4 (2004/07/16) 追加パラメータ取り込み時に、"_" は、null 扱いとする。
	 * @og.rev 3.6.0.7 (2004/11/06) DBColumn の official属性追加
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
	 *
	 * @param	key	カラムID(not null)
	 *
	 * @return	DBColumnオブジェクト
	 */
	public DBColumn getDBColumn( final String key ) {
		// 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
		if( key == null ) {
			final String errMsg = "カラムIDに、NULL は指定できません。";
			throw new HybsSystemException( errMsg );
		}

		DBColumn clm = columnPool.get( key );
		if( clm == null ) {
			final ColumnData clmDt = columnLoader.getColumnData( key );
			if( clmDt != null ) {
				final String label_clm = clmDt.getLabelColumn();
				final String code_clm  = clmDt.getCodeColumn();

				clm = new DBColumn(
							lang,
							clmDt,
							labelLoader.getLabelData( label_clm ),
							codeLoader.getCodeData( code_clm ) );

				columnPool.put( key,clm );
			}
		}
		return clm;
	}

	/**
	 * DBColumn オブジェクトを作成します。
	 * 内部にプールに存在すればそれを、なければ新規に作成します。
	 * それでも存在しない場合は、DBColumnConfig より、ラベルと言語を指定して
	 * 新規に作成します。
	 *
	 * @param	key		カラムID(not null)
	 *
	 * @og.rev 7.1.0.0 (2020/01/27) LabelDataを直接呼び出します。
	 *
	 * @return	DBColumnオブジェクト
	 * @see		#getDBColumn( String )
	 */
	public DBColumn makeDBColumn( final String key ) {
		DBColumn dbColumn = getDBColumn( key );
		if( dbColumn == null ) {
			final DBColumnConfig config = new DBColumnConfig( key );
			config.setLabelData( labelLoader.getLabelData( key ) );		// 7.1.0.0 (2020/01/27)
			config.setLang( lang );
			dbColumn = new DBColumn( config );
		}
		return dbColumn;
	}

	/**
	 * DBColumn オブジェクトを作成します。
	 * 内部にプールに存在すればそれを、なければ新規に作成します。
	 * それでも存在しない場合は、DBColumnConfig より、ラベルと言語を指定して
	 * 新規に作成します。
	 * lbl引数が、nullか、ゼロ文字列の場合は、#makeDBColumn(String) と同じです。
	 *
	 * @og.rev 6.9.1.0 (2018/02/26) unionLbls追加
	 * @og.rev 7.1.0.0 (2020/01/27) LabelDataを直接呼び出します。
	 *
	 * @param	key	カラムID(not null)
	 * @param	lbl	ラベル(nullか、ゼロ文字列の場合は、設定しません)
	 *
	 * @return	DBColumnオブジェクト
	 * @see		#getDBColumn( String )
	 */
	public DBColumn makeDBColumn( final String key , final String lbl ) {
		if( lbl == null || lbl.isEmpty() ) { return makeDBColumn( key ); }

		final DBColumn dbColumn = getDBColumn( key );
		final DBColumnConfig config ;

		if( dbColumn == null ) {
			config = new DBColumnConfig( key );
			config.setLang( lang );
		}
		else {
			config = dbColumn.getConfig();
		}

		config.setLabelData( labelLoader.getLabelData( lbl ) );		// 7.1.0.0 (2020/01/27)

		return new DBColumn( config );
	}

	/**
	 * メッセージリソースから,キーで指定されたメッセージに,
	 * 引数で指定された変数値をセットしたメッセージを返します。
	 *
	 * このメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * メッセージがリソースに存在しない場合は,キーを返します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) オラクルとWindowsとの間の "～"の文字化け対策
	 * @og.rev 4.0.0.0 (2007/10/17) メッセージリソース統合に伴いラベルローダーを使用する
	 * @og.rev 4.0.0.0 (2007/10/18) 名称変更 getMessage ⇒ getLabel
	 * @og.rev 5.1.1.0 (2009/12/01) #XXXXの変換で、カラム名が複数指定されている場合の対応
	 * @og.rev 6.1.0.0 (2014/12/26) LabelData が存在しなかった場合の処理
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 * @og.rev 6.6.0.0 (2016/12/01) 引数の配列を可変長配列に変更します。
	 * @og.rev 7.0.7.0 (2019/12/13) #getLabel( String ) と統合します。
	 *
	 * @param	key キー
	 * @param	args メッセージの配列
	 *
	 * @return	メッセージ(無ければ キー)
	 */
	public String getLabel( final String key,final String... args ) {			// 6.6.0.0 (2016/12/01)
		final LabelData lblData = labelLoader.getLabelData( key );

		final String msglbl ;

		if( args == null || args.length == 0 ){
			msglbl = lblData.getLabel();
		}
		else {
			// 6.1.0.0 (2014/12/26) メソッド化
			final String[] msgArgs = makeLabelArray( args );	// 6.1.0.0 (2014/12/26) メソッド化

			// 6.1.0.0 (2014/12/26) LabelData が存在しなかった場合の処理
			// 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
			msglbl = lblData.getMessage( msgArgs );
		}

		return msglbl == null ? key : msglbl ;		// なければ key を返す
	}

	/**
	 * ラベルリソースから,ラベルを返します。
	 * ただし、キーに、「スペース＋%＋記号」を判定、処理する機能を用意します。
	 *
	 * 記号は、Label,Short,Tips,Description,RawShortLabel,CodeData の頭文字一つ目です。
	 * ('L','S','T','D','R','C') となります。
	 * 'L' は通常のﾗﾍﾞﾙと同じ。'C' は、'S'(Short)と同じになります。
	 *
	 * 「スペース＋%＋記号」が無ければ、通常のラベルと同じ処理を行います。
	 * つまり、このメソッドは、getLabel(String) より、ほんの少し処理時間を稼ぐために用意しました。
	 * なので、一応、内部からしか呼ばない事とし、private にしておきます。
	 *
	 * @og.rev 6.3.8.4 (2015/10/09) XXXX %S などの対応。getLabel(String) は汎用過ぎるので、少し分ける。
	 * @og.rev 6.3.9.0 (2015/11/06) switch 文の2つの case のために同じコードを使用している(findbugs)
	 * @og.rev 7.2.9.0 (2020/10/12) getRawShortLabelで、null時は key を返します。
	 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:switch 文の2つの case のために同じコードを使用しているメソッド
	 *
	 * @param	key ラベルキー
	 *
	 * @return	リソースに応じたラベル文字列(無ければ ラベルキー)
	 */
	private String getExtLabel( final String key ) {
		final String val ;

		final int ad = key.indexOf( " %" );
		if( ad < 0 ) {
			val = getLabel( key );
		}
		else if( ad+2 <= key.length() ) {		// " %" の後ろの文字が存在しない場合
			val = getLabel( key.substring( 0,ad ) );
		}
		else {
			final String tmpKey = key.substring( 0,ad );
			// 6.3.9.0 (2015/11/06) switch 文の2つの case のために同じコードを使用している(findbugs)
			switch( key.charAt( ad+2 ) ) {
				case 'S':
				case 'C': val = getShortLabel(    tmpKey );	break;			// 6.3.9.0 (2015/11/06) findbugs対応
				case 'T': val = getLongLabel(     tmpKey );	break;
				case 'D': val = getDescription(   tmpKey );	break;
				case 'R': val = getRawShortLabel( tmpKey,true );	break;	// 7.2.9.0 (2020/10/12)
				default : val = getLabel(         tmpKey );	break;
			}
		}
		return val;
	}

	/**
	 * ラベルリソースの引数で、#XXXXの変換で、カラム名が複数指定されている場合の対応を行います。
	 *
	 * 一つの引数に、#CLM,LANG,KBSAKU 等の項目名が複数指定指定された場合に、
	 * それぞれを解析してラベルに戻します。
	 * 該当するラベルが存在しない場合は、そのままの値を返します。
	 * 返される文字列配列は、元の引数とは異なる新しい文字列配列で返されます。
	 *
	 * @og.rev 6.1.0.0 (2014/12/26) #XXXXの変換で、カラム名が複数指定されている場合の対応
	 * @og.rev 6.3.8.4 (2015/10/09) #XXXX %S などの対応。getLabel(String) は汎用過ぎるので、少し分ける。
	 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. Java8 ラムダ式を使用して、大幅書き直し。
	 * @og.rev 7.0.4.2 (2019/06/17) # のみの場合は、特別に、# を返すように対応。
	 *
	 * @param	args メッセージの配列
	 *
	 * @return	ラベル置換後の新しい文字列配列
	 */
	private String[] makeLabelArray( final String[] args ) {
		final int size = args.length;
		final String[] msgArgs = new String[size];

		for( int i=0; i<size; i++ ) {
			final String arg = args[i] ;
			if( StringUtil.startsChar( arg , '#' ) && arg.length() > 1 ) {					// 7.0.4.2 (2019/06/17)
				// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid instantiating new objects inside loops
				msgArgs[i] = Stream.of( StringUtil.csv2Array( arg.substring( 1 ) ) )
								.map( lbl -> getExtLabel( lbl ) )
								.collect( Collectors.joining( "," ) );
			}
			else {
				msgArgs[i] = arg ;
			}
		}
		return msgArgs ;
	}

	/**
	 * ラベルリソースから,ラベル(短)を返します。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 4.3.3.0 (2008/10/01) 新規作成
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 *
	 * @param	key ラベルキー
	 *
	 * @return	リソースに応じたラベル文字列(無ければ ラベルキー)
	 */
	public String getShortLabel( final String key ) {
		final LabelData lblData = labelLoader.getLabelData( key );
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		// 反転注意
		final String rtn = lblData.getShortLabel();

		return rtn == null ? key : rtn ;		// なければ key を返す
	}

	/**
	 * ラベルリソースから,ラベル(長)を返します。
	 * 概要説明が存在する場合は、ツールチップに概要説明が
	 * 表示されます。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 6.3.8.4 (2015/10/09) #XXXX %S などの対応。getLabel(String) は汎用過ぎるので、少し分ける。
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 *
	 * @param	key ラベルキー
	 *
	 * @return	リソースに応じたラベル(長)文字列(無ければ ラベルキー)
	 */
	public String getLongLabel( final String key ) {
		final LabelData lblData = labelLoader.getLabelData( key );
		// 6.4.1.1 (2016/01/16) PMD refactoring. A method should have only one exit point, and that should be the last statement in the method
		// 反転注意
		final String rtn = lblData.getLongLabel();

		return rtn == null ? key : rtn ;		// なければ key を返す
	}

	/**
	 * ラベルリソースから,ラベル(長)ををそのままの形で返します。
	 * （discription等を付けない)
	 * 表示されます。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 7.2.9.0 (2020/10/12) 新規追加
	 *
	 * @param	key ラベルキー
	 *
	 * @return	リソースに応じたラベル(長)そのままの文字列(無ければ ラベルキー)
	 * @og.rtnNotNull
	 */
	public String getRawLongLabel( final String key ) {
		final LabelData lblData = labelLoader.getLabelData( key );

		return lblData.getRawLongLabel();
	}

	/**
	 * ラベルオブジェクトの名称(短)をspanタグを付けない状態で返します。
	 * SNAMEが未設定の場合は、LNAME が返されます。
	 * 引数の言語コードに応じたリソースが登録されていない場合は,
	 * 引数のラベルキーそのまま返します。
	 *
	 * @og.rev 6.3.8.4 (2015/10/09) #XXXX %S などの対応。getLabel(String) は汎用過ぎるので、少し分ける。
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 * @og.rev 7.0.7.0 (2019/12/13) args パラメータ配列(可変長引数)を追加します。
	 * @og.rev 7.2.9.0 (2020/10/12) args パラメータ配列(可変長引数)廃止(旧ﾒｿｯﾄﾞ復活)
	 *
	 * @param	key ラベルキー
	 * @param	useKey null時にキーを返す場合は、true , 空文字を返す場合は、false
	 *
	 * @return	リソースに応じたラベル(短)文字列(無ければ ラベルキー)
	 */
	public String getRawShortLabel( final String key , final boolean useKey ) {
		final LabelData lblData = labelLoader.getLabelData( key );

		return lblData.getRawShortLabel( useKey ? key : "" );
	}

	/**
	 * ラベルリソースから,概要説明を返します。
	 * {0},{1}...の置換えを行います。
	 * キーのデータが存在しない場合はnullを返します。
	 * ただし、パラメータのデータがあれば、それを返します。
	 *
	 * @og.rev 4.3.7.6 (2009/07/15) 新規作成
	 * @og.rev 6.1.0.0 (2014/12/26) #XXXXの変換で、カラム名が複数指定されている場合の対応
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 * @og.rev 7.0.7.0 (2019/12/13) #getDescription( String ) と統合します。
	 *
	 * @param	key ラベルキー
	 * @param	args パラメータ配列(可変長引数)
	 *
	 * @return	リソースに応じた概要説明(無ければ null)
	 */
	public String getDescription( final String key,final String... args ) {			// 6.6.0.0 (2016/12/01)
		final LabelData lblData = labelLoader.getLabelData( key );

		final String msgdesc ;
		if( args == null || args.length == 0 ){
			msgdesc = lblData.getDescription();
		}
		else {
			// 6.1.0.0 (2014/12/26) getLabel( String ,String[] ) と同様に複数ラベル対応
			final String[] msgArgs = makeLabelArray( args );

			// 6.1.0.0 (2014/12/26) LabelData が存在しなかった場合の処理
			msgdesc = lblData.getDescription( msgArgs );
		}

		return msgdesc == null ? key : msgdesc ;		// なければ key を返す
	}

	/**
	 * メッセージリソースから,ErrMsgオブジェクトで指定されたメッセージを返します。
	 *
	 * このエラーメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * エラーメッセージがリソースに存在しない場合は,エラーコードを返します。
	 *
	 * @og.rev 4.0.0.0 (2004/12/31) 新規追加
	 * @og.rev 4.0.0.0 (2007/10/18) メッセージリソースとの統合化
	 *
	 * @param	errMsgObj ErrMsgオブジェクト
	 *
	 * @return	エラーメッセージ(無ければ ErrMsgオブジェクトの toString() )
	 */
	public String getLabel( final ErrMsg errMsgObj ) {
		return getLabel( errMsgObj.getId(),errMsgObj.getArgs() );
	}

	/**
	 * メッセージリソースから,ErrMsgオブジェクトで指定されたショートメッセージを返します。
	 *
	 * このエラーメッセージは,リソースで選ばれたロケール毎のメッセージに，
	 * MessageFormat#format でフォーマットする事により,作成されます。
	 * エラーメッセージがリソースに存在しない場合は,エラーコードを返します。
	 *
	 * @og.rev 7.0.7.0 (2019/12/13) 新規追加
	 * @og.rev 7.2.9.0 (2020/10/12) 引数を分解して、直接処理します。…ついでにメソッド名変更
	 *
	 * @param	errMsgObj ErrMsgオブジェクト
	 *
	 * @return	エラーメッセージ(無ければ ErrMsgオブジェクトの toString() )
	 */
	public String getShortErrorMsg( final ErrMsg errMsgObj ) {
		final String   key  = errMsgObj.getId();
		final String[] args = errMsgObj.getArgs();

		final LabelData lblData = labelLoader.getLabelData( key );

		final String msglbl ;
		if( args == null || args.length == 0 ){
			msglbl = lblData.getRawShortLabel();
		}
		else {
			// 6.1.0.0 (2014/12/26) メソッド化
			final String[] msgArgs = makeLabelArray( args );	// 6.1.0.0 (2014/12/26) メソッド化

			// 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
			msglbl = lblData.getShortMessage( msgArgs );
		}

		return msglbl == null ? key : msglbl ;		// なければ key を返す
	}

	/**
	 * ErrMsgオブジェクトの内容を元に、ラベルリソースから,概要説明を返します。
	 * キーのデータが存在しない場合はnullを返します。
	 *
	 * @og.rev 4.3.7.6 (2009/07/15) 新規作成
	 *
	 * @param	errMsgObj ErrMsgオブジェクト
	 *
	 * @return	エラーメッセージ(キーが無ければnull)
	 */
	public String getDescription( final ErrMsg errMsgObj ) {
		return getDescription( errMsgObj.getId(),errMsgObj.getArgs() );
	}

	/**
	 * ラベルキーに対応する、LabelDataオブジェクトを返します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) 新規作成
	 * @og.rev 6.3.9.0 (2015/11/06) labelLoader.getLabelDataは、nullを返しません。
	 *
	 * @param	key ラベルキー
	 *
	 * @return	LabelDataオブジェクト
	 * @og.rtnNotNull
	 */
	public LabelData getLabelData( final String key ) {
		return labelLoader.getLabelData( key );
	}

	/**
	 * コードキーに対応する、CodeDataオブジェクトを返します。
	 *
	 * @param	key コードキー
	 *
	 * @return	CodeDataオブジェクト(無ければ null)
	 */
	public CodeData getCodeData( final String key ) {
		return codeLoader.getCodeData( key );
	}

	/**
	 * コードリソースから,コード文字列を返します。
	 * 引数にQUERYを渡すことで、DBから、動的にコードリソースを作成できます。
	 *
	 * @og.rev 5.4.2.2 (2011/12/14) 新規追加。
	 *
	 * @param	key コードキー
	 * @param	query 検索SQL(引数に、? を一つ持つ)
	 *
	 * @return	コードデータオブジェクト(無ければ null)
	 */
	public CodeData getCodeData( final String key,final String query ) {
		return codeLoader.getCodeData( key,query );
	}

	/**
	 * ログインユーザーで使用する画面オブジェクトを、UserInfoにセットします。
	 * 各、UserInfo は、自分自身が使用する 画面オブジェクトのみを管理することで、
	 * 画面アクセス有無を、すばやく検索することが可能になります。
	 *
	 * @og.rev 3.1.0.1 (2003/03/26) GUIInfo のキー順サポートの為に、引数追加。
	 * @og.rev 4.0.0.0 (2005/01/31) 使用画面のMap を UserInfo にセットします。
	 * @og.rev 4.3.0.0 (2008/07/04) ロールモードマルチ対応
	 * @og.rev 5.2.0.0 (2010/09/01) アクセス禁止アドレスによる不正アクセス防止機能追加
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、removeAll メソッドに置き換えます。
	 * @og.rev 6.4.4.2 (2016/04/01) guiMap.values() では、GUIInfo だが、remove するのは、gui.getAddress() の値。
	 * @og.rev 7.1.0.0 (2020/01/27) LabelDataを直接呼び出します。
	 *
	 * @param	user	指定のユーザーロールに対応する画面だけをMapにセットする。
	 */
	public void makeGUIInfos( final UserInfo user ) {
		final GUIData[] guiDatas = guiLoader.getAllData();

		// guikey に対してユニークになるように Map に追加します。後登録が優先されます。
		final Map<String,GUIInfo> guiMap = new HashMap<>();
		final Set<String> forbidAddrSet  = new HashSet<>();
		int size = guiDatas.length;
		for( int i=0; i<size; i++ ) {
			final GUIData gui = guiDatas[i];
			final byte bitMode = user.getAccessBitMode( gui.getRoleMode() );
			if(  bitMode > 0 ) {
				final String	  guikey	= gui.getGuiKey();
				final LabelData labelData	= labelLoader.getLabelData( gui.getLabelClm() );	// 7.1.0.0 (2020/01/27)
				guiMap.put( guikey,new GUIInfo( gui,labelData,bitMode ) );
			}
			// 5.2.0.0 (2010/09/01) アクセス禁止アドレスによる不正アクセス防止機能追加
			else {
				final String addr = gui.getAddress();
				if( addr.indexOf( '/' ) < 0 ) {
					forbidAddrSet.add( addr );
				}
			}
		}

		// もし、禁止リストの中に画面ID違いで許可リストと同じアドレスが
		// 含まれている場合は、禁止リスト中から該当のアドレスを削除する。
		// 6.4.3.4 (2016/03/11) forループを、removeAll メソッドに置き換えます。
		// 6.4.4.2 (2016/04/01) guiMap.values() では、GUIInfo だが、remove するのは、gui.getAddress() の値。
		guiMap.forEach( (id,gui) -> forbidAddrSet.remove( gui.getAddress() ) );

		// GUIInfo をその順番(SEQNO順)でソートし直します。SYSTEM_ID や、KBSAKU の影響を排除します。
		final GUIInfo[] guiInfos = guiMap.values().toArray( new GUIInfo[ guiMap.size() ] ) ;
		Arrays.sort( guiInfos );
		final Map<String,GUIInfo> sortMap = new LinkedHashMap<>();
		size = guiInfos.length;
		for( int i=0; i<size; i++ ) {
			final GUIInfo guiInfo = guiInfos[i];
			final String guikey	= guiInfo.getKey();
			sortMap.put( guikey,guiInfo );
		}

		user.setGUIMap( sortMap, forbidAddrSet );
	}

	/**
	 * 指定されたクエリを発行し、ラベルマップを作成します。
	 *
	 * @og.rev 4.3.4.0 (2008/12/01) 新規作成
	 * @og.rev 6.4.0.5 (2016/01/09) useLabelMap="true" 時のSQL文の実行は、dbid を使用して行う。
	 *
	 * @param	query	ラベルマップを作成するクエリ
	 * @param	dbid	接続先ID
	 *
	 * @return	ラベルマップ
	 * @see org.opengion.hayabusa.resource.LabelDataLoader#getLabelMap( String,String )
	 */
	public Map<String, LabelData> getLabelMap( final String query , final String dbid ) {
		return labelLoader.getLabelMap( query , dbid );
	}

	/**
	 * リソースマネージャーをキーに基づいて部分クリアします。
	 * ここでは、部分クリアなため、GUIData に関しては、処理されません。
	 * また、存在しないキーを指定されたリソースは、何も処理されません。
	 *
	 * @og.rev 5.4.3.4 (2012/01/12) labelPool の削除追加
	 * @og.rev 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限のチェック追加
	 * @og.rev 6.9.0.1 (2018/02/05) どのシステムIDのﾘｿｰｽがクリアされたかを表示します。
	 *
	 * @param   key         カラムのキー
	 */
	public void clear( final String key ) {
		// 6.4.3.3 (2016/03/04) ConcurrentHashMap の not null制限
		if( key != null ) {
			columnLoader.clear( key );
			codeLoader.clear( key );
			labelLoader.clear( key );
			columnPool.remove( key );
		}
	}

	/**
	 * GUI情報をクリアします。
	 * ここでは、関連するラベル、コードリソースの部分クリアも行います。
	 * GUI情報は、シーケンスに管理しているため、この処理１回ごとに、
	 * GUIData を全件再読み込みを行いますので、ご注意ください。
	 *
	 */
	public void guiClear() {
		final GUIData[] gui = guiLoader.getAllData();

		for( int i=0; i<gui.length; i++ ) {
			final String key = gui[i].getGuiKey();
			labelLoader.clear( key );
		}
		codeLoader.clear( "CLASSIFY" );
		guiLoader.clear();
	}

	/**
	 * リソースマネージャーをクリア(初期化)します。
	 *
	 * @og.rev 5.4.3.4 (2012/01/12) labelPool の削除追加
	 *
	 */
	public void clear() {
		columnLoader.clear();
		codeLoader.clear();
		labelLoader.clear();
		guiLoader.clear();
		columnPool.clear();
	}
}
