/*
 * 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.sql.SQLException;
import java.util.List;
import java.util.Set;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Map;
import java.util.LinkedHashMap;										// 6.4.3.1 (2016/02/12) refactoring
import java.util.HashMap;
//import java.util.concurrent.ConcurrentMap;						// 6.4.3.3 (2016/03/04) 7.4.4.0 (2021/06/30) Delete
//import java.util.concurrent.ConcurrentHashMap;					// 6.4.3.1 (2016/02/12) refactoring 7.4.4.0 (2021/06/30) Delete
import java.util.Collections;										// 6.4.3.1 (2016/02/12) refactoring

import org.opengion.fukurou.db.DBSimpleTable;
import org.opengion.fukurou.db.DBUtil;
import org.opengion.fukurou.db.ApplicationInfo;
import org.opengion.fukurou.util.Cleanable;
import org.opengion.fukurou.util.HybsEntry;
import org.opengion.fukurou.system.DateSet;							// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.system.LogWriter;
import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.common.UserSummary;
import org.opengion.hayabusa.db.DBEditConfig;
import org.opengion.hayabusa.db.DBEditConfigManager;
import static org.opengion.fukurou.system.HybsConst.CR ;			// 6.1.0.0 (2014/12/26)
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * ユーザー情報の取得の為のインターフェースを実装したクラスです。
 *
 * ログイン時のパスワードのチェックや,国名の識別ID，ポータルページのURLなど
 * 個人情報を管理させます。
 * 特に,画面アクセス時の権限やメールの送信，各画面にユーザー情報を表示したり,
 * エラー時のログファイル，テンポラリディレクトリなども管理します。
 *
 * {&#064;USER.XXXX} で、XXXX 部に、UserInfo オブジェクトで定義されている
 * 属性情報を取り出すことが出来ます。
 *
 * 以下の値は UserInfo オブジェクトの項目から取得します。
 * ・JNAME      ユーザー日本語名称
 * ・ID         ユーザーＩＤ
 * ・INFO       ユーザー情報(ユーザーID：日本語名称)
 * ・LANG       言語
 * ・ROLES      ロール
 * ・IPADDRESS  IPアドレス
 * ・LOGINTIME  ログイン時刻
 * ・LASTACCESS 最終アクセス画面ID
 *
 * 以下の値はあらかじめ、動的に作成されます。
 * ・YMD       ８byte の今日のシステム日付
 * ・YMDH    １４byte の今日のシステム日時
 *
 * それ以外は,外部より設定された値です。
 * 従来は、USER.IDNO はUSER.ID が５Byte以上の時のみ先頭１文字を除いたユーザーＩＤとして
 * オブジェクト項目からの取得でしたが、現在は初期値として設定してあります。
 * 外部より再設定可能になっています。
 *
 * @og.group リソース管理
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class UserInfo implements UserSummary , Cleanable {
	private static final long serialVersionUID = 568120130913L ;	// 5.6.8.1 (2013/09/13)

	// ユーザーリソースのキー指定読み込みのクエリー
	// 7.4.4.0 (2021/06/30) Delete
	//private static final String QUERY_PARAM = HybsSystem.sys( "USER_PARAMETER_SQL" );

	/** 5.6.8.1 (2013/09/13) 最終リクエスト情報のユーザー永続化情報(GE20)へのセーブに使用するキーの接頭語 */
	private static final String LAST_REQUEST_DATA_SUFIX = "LAST_REQUEST_" ;

	// ｱｸｾｽ統計ﾃｰﾌﾞﾙ(GE15)への接続先を、リソースの接続先より取得します。
	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );

	//private static final String YOYAKU = "|JNAME|ID|INFO|LANG|ROLES|IPADDRESS|LOGINTIME|LASTACCESS|YMD|YMDH|" ;
	private static final String YOYAKU = "|JNAME|ID|INFO|LANG|ROLES|IPADDRESS|LOGINTIME|LASTACCESS|YMD|YMDH|LASTGAMENNM" ; // 4.4.0.1 (2009/08/08)

	private final boolean useAccessTable = HybsSystem.sysBool( "USE_ACCESS_TOKEI_TABLE" );

	private final String	userID	;
	private       String	lang 	;	// 5.1.4.0 (2010/03/01) lang を書き換え可能とする。
	private final String	jname	;
	private final String	roles	;
	//private final String	droles	; // 4.4.0.0 (2009/08/02) データロール対応 7.4.4.0 (2021/06/30) Delete
	private final String	ipAddress	;
	private final long		loginTime	;
	/** 6.4.3.1 (2016/02/12) val=null を登録しているため、単純に、ConcurrentHashMap に置き換えできない。  */
	private final Map<String,String>	attriMap  ;
	private final RoleMode	roleMode ;		// ロールズとモードを管理するオブジェクト
	//private final DataRole	dataRole ;		// データロールを管理するオブジェクト 7.4.4.0 (2021/06/30) Delete

	private final int		hcode		;				// 6.4.1.1 (2016/01/16) PMD refactoring. Field hashcode has the same name as a method
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
	private final Map<String,GUIInfo>	guiMap = Collections.synchronizedMap( new LinkedHashMap<>() );				// 6.4.3.1 (2016/02/12)
	private 	  long		usedTime 	;				// 4.0.0 (2005/01/31)
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	//private final ConcurrentMap<String,String>	paramMap = new ConcurrentHashMap<>();							// 6.4.3.3 (2016/03/04) 7.4.4.0 (2021/06/30) Delete
	private final Object	guiLock		= new Object();
	private final String	systemId	;
	/** 6.4.3.1 (2016/02/12) 作成元のMapを、変更不可能なビューを返すようにするため、 ConcurrentHashMap に置き換え不要。  */
	private Map<String,FavoriteGUIData> favoriteGuiMap	;	// 4.1.1.0 (2008/01/22)
	private Set<String> forbidAddrSet 	;				// 5.2.0.0 (2010/09/01)
	private final DBEditConfigManager editMgr ;								// 6.0.2.2 (2014/10/03)

	/** 6.4.3.1 (2016/02/12) val=null を登録しているため、単純に、ConcurrentHashMap に置き換えできない。  */
	private final Map<String,String>	lastRequestMap = new HashMap<>();	// 5.6.8.1 (2013/09/13)

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	public static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	private final ApplicationInfo appInfo;

	// ユーザー永続化情報(GE20)テーブル 読み込み用SQL
	// 4.3.4.0 (2008/12/01) ROLE='*'も検索できるようにする
	// 5.3.6.0 (2011/06/01) USERID='*'も検索できるようにする
	private static final String QUERY_GE20	= "select PARAM_ID,PARAM from GE20"
											+	" where SYSTEM_ID = ? and USERID in ( ?, '*' )"
											+	" and ROLES in ( ?, '*' ) and FGJ = '1'"
											+	" order by USERID,ROLES";

	// 4.3.4.0 (2008/12/01) ユーザー永続化情報(GE20)にセーブ時に存在チェックを行うためのSQL
	// 4.3.4.4 (2009/01/01) private static を付加
	private static final String QUERY_GE20_KEY	= "select KBSET from GE20"
											+	" where SYSTEM_ID = ? and USERID = ?"
											+	" and ROLES = ? and PARAM_ID = ? and FGJ = '1'";

	// 5.2.3.0 (2010/12/01) アクセス履歴管理
	private GUIInfo lastGuiInfo	;

	// saveGUIAccessInfo() メソッドでしか使用しない、定数宣言
	private static final int C_SYSTEM_ID		= 0 ;
	private static final int C_USERID			= 1 ;
	private static final int C_USERADRS			= 2 ;
	private static final int C_HOSTADRS			= 3 ;
	private static final int C_GUIKEY			= 4 ;
	private static final int C_DYLOGIN			= 5 ;
	private static final int C_DYLOGOUT			= 6 ;
	private static final int C_USED_TIME		= 7 ;
	private static final int C_CNT_ACCESS		= 8 ;
	private static final int C_CNT_ERROR		= 9 ;
	private static final int C_CNT_READ			= 10 ;
	private static final int C_CNT_WRITE		= 11 ;
	private static final int C_TM_TOTAL_QUERY	= 12 ;
	private static final int C_TM_MAX_QUERY		= 13 ;
	private static final int C_MAX_QUERY		= 14 ;
	private static final int C_FGJ				= 15 ;
	private static final int C_DYSET			= 16;
	private static final int C_DYUPD			= 17;
	private static final int C_USRSET			= 18;
	private static final int C_USRUPD			= 19;
	private static final int C_PGUPD			= 20;

	// ユーザー永続化情報(GE20)設定でしか使用しない変数の宣言
	private static final int C_GE20_SYSTEM_ID	= 0;
	private static final int C_GE20_USERID		= 1;
	private static final int C_GE20_ROLES		= 2;
	private static final int C_GE20_PARAM_ID	= 3;
	private static final int C_GE20_PARAM		= 4;
	private static final int C_GE20_KBSET		= 5;
	private static final int C_GE20_FGJ			= 6;
	private static final int C_GE20_DYSET		= 7;
	private static final int C_GE20_DYUPD		= 8;
	private static final int C_GE20_USRSET		= 9;
	private static final int C_GE20_USRUPD		= 10;
	private static final int C_GE20_PGUPD		= 11;

	private static final int GE20_KBSET_READONLY	= 1;
	private static final int GE20_KBSET_WRITABLE	= 2;

	// ロールは全て*で登録する。アプリケーションから動的に登録される値を、
	// ロール単位設定しても、ロール変更時に整合性が合わない可能性大なので、
	// UserInfoで設定する場合は、全てのロールで有効とする。
	private static final String GE20_ROLES = "*";

	/**
	 * コンストラクター
	 *
	 * @og.rev 3.0.0.1 (2003/02/14) ユーザー毎のエンコード指定方法を廃止します。
	 * @og.rev 3.1.3.0 (2003/04/10) ユーザー情報から、エンコード情報を削除する。
	 * @og.rev 3.4.0.3 (2003/09/10) "root","manager","admin" のロールを、すべて root 権限を与える。
	 * @og.rev 3.8.5.3 (2006/06/30) USE_USER_IDNO_C_SAPLESS を判定条件に加える。
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.3.0.0 (2008/07/04) ロールモードマルチ対応
	 * @og.rev 4.4.0.0 (2009/08/02) データロール対応
	 * @og.rev 5.3.6.0 (2011/06/01) ユーザー永続化情報(GE20)の読み込みをUserInfoFactoryから移動
	 * @og.rev 6.0.2.2 (2014/10/03) DBEditConfigManager をここで作成する。
	 * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(DataRole.java廃止、GE16廃止)
	 *
	 * @param	userID		ユーザー
	 * @param	lang		言語
	 * @param	jname		日本語名称
	 * @param	roles		ロール
	 * @param	systemId	システムID
	 * @param	ipAddress	IPアドレス
	 * @param   appInfo		アプリ情報オブジェクト
	 */
	public UserInfo( final String userID		,
					 final String lang			,
					 final String jname			,
					 final String roles			,
	//				 final String droles		, // 4.4.0.0 (2009/08/02) 7.4.4.0 (2021/06/30) Delete
					 final String systemId		,
					 final String ipAddress		,
					 final ApplicationInfo appInfo  ) {
		this.userID	 	= userID	;
		this.lang	 	= lang		;
		this.jname	 	= jname		;
		this.roles		= roles		;
	//	this.droles		= droles	; // 4.4.0.0 (2009/08/02) 7.4.4.0 (2021/06/30) Delete
		this.systemId	= systemId	;
		this.roleMode	= RoleMode.newInstance( roles );	// 4.3.0.0 (2008/07/04) ロールモード
	//	this.dataRole	= DataRole.newInstance( droles, systemId, appInfo ); // 4.4.0.0 (2009/08/02) 7.4.4.0 (2021/06/30) Delete
		this.ipAddress	= ipAddress	;
		this.appInfo	= appInfo	;
		loginTime		= System.currentTimeMillis();
		usedTime		= loginTime;
		attriMap		= new HashMap<>();

		// 3.5.6.0 (2004/06/18) hashCode を計算しておきます。
		hcode = (int)(loginTime^(loginTime>>>32)) ;

	 	// 3.8.1.2 (2005/12/19) USER.IDNO をAttributeにセットする。
	 	// 3.8.5.3 (2006/06/30) USE_USER_IDNO_C_SAPLESS を判定条件に加える。
		final boolean IDNO_C_SAPLESS = HybsSystem.sysBool( "USE_USER_IDNO_C_SAPLESS" );		// 6.4.2.1 (2016/02/05) PMD refactoring. Useless parentheses.
		final String idno = userID.length() > 5 && IDNO_C_SAPLESS ? userID.substring(1) : userID ;
		attriMap.put( "IDNO",idno ) ;				// コンストラクタ内なので、同期処理は入れていません。

		// ユーザーパラメータなどの初期設定を行います。
		// 7.4.4.0 (2021/06/30) Delete
	//	initLoad() ;

		// 5.3.6.0 (2011/06/01) ユーザー永続化情報(GE20)からDBに保存されたUserInfo情報を読み出します。
		dbLoad();

		// 5.3.6.0 (2011/06/01) Edit情報の一覧を作成します。
		editMgr = new DBEditConfigManager( attriMap );			// 6.0.2.2 (2014/10/03) 作成方法変更
	}

	/**
	 * ユーザーパラメータを取得します。
	 * ユーザーパラメーターは、通常、GE16 テーブルより取得します。
	// * 取得するSQL文は、SystemData の USER_PARAMETER_SQL に記述しておきます。
	 * ユーザーパラメータに、値が存在しない場合は、システムリソースより
	 * 取得します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(GE16廃止)
	 *
	 * @param	key	パラメータキー
	 *
	 * @return	パラメータ値(ユーザーパラメータになければ、システムリソースより取得
	 */
	public String getParameter( final String key ) {
		String val = null;
		if( key != null ) {
			// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	//			val = paramMap.get( key );										// 7.4.4.0 (2021/06/30) Delete
			if( val == null ) { val = HybsSystem.sys( key ); }
		}
		return val;
	}

	/**
	 * ユーザーログイン時刻を取得します。
	 *
	 * @return	ログイン時刻
	 */
	@Override	// UserSummary
	public long getLoginTime() {
		return loginTime;
	}

	/**
	 * ユーザーのログインIPアドレスを取得します。
	 *
	 * @return	IPアドレス
	 *
	 */
	@Override	// UserSummary
	public String getIPAddress() {
		return ipAddress;
	}

	/**
	 * ユーザーを取得します。
	 *
	 * @return	ユーザー
	 *
	 */
	@Override	// UserSummary
	public String getUserID() {
		return userID;
	}

	/**
	 * ユーザー情報ロケール(言語)を取得します。
	 *
	 * @return	ロケール(言語)
	 */
	@Override	// UserSummary
	public String getLang() {
		return lang ;
	}

	/**
	 * ユーザー情報ロケール(言語)をセットします。
	 *
	 * @og.rev 5.1.4.0 (2010/03/01) lang を書き換え可能とする。
	 *
	 * @param newLang	ロケール(言語)
	 */
	public void setLang( final String newLang ) {
		lang = newLang ;
	}

	/**
	 * ユーザー情報 名称(日本語)を取得します。
	 *
	 * @return	名称(日本語)
	 */
	@Override	// UserSummary
	public String getJname() {
		return jname ;
	}

	/**
	 * ユーザー情報 ロール(役割)を取得します。
	 *
	 * @return	ロール(役割)
	 */
	@Override	// UserSummary
	public String getRoles() {
		return roles ;
	}

	/**
	 * ロールモード情報を取得します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/04) 新規追加
	 *
	 * @return	ロールモード情報
	 */
	public RoleMode getRoleMode() {
		return roleMode ;
	}

	/**
	 * オブジェクトの識別子として，ユーザー情報を返します。
	 *
	 * @return  ユーザー情報
	 * @og.rtnNotNull
	 */
	@Override	// UserSummary
	public String getInfo() {
		final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE )
			.append( userID ).append( " ： " )
			.append( jname  ).append( CR );
		return rtn.toString();
	}

//	/**
//	 * DBEditConfigManagerオブジェクトを返します。
//	 *
//	 * ここでは、内部オブジェクトそのものを返します。
//	 *
//	 * @og.rev 6.4.5.0 (2016/04/08) 新規追加。UserInfo オブジェクトから、出来るだけEditConfig関係の処理を除外します。
//	 * @og.rev 6.9.2.1 (2018/03/12) DBEditConfigManagerを直接取り出す処理を、廃止します。
//	 *
//	 * @return  DBEditConfigManagerオブジェクト
//	 */
//	public DBEditConfigManager getEditConfigManager() {
//		return editMgr;
//	}

	/**
	 * UserInfoの属性文字列を登録します。
	 *
	 * 予約文字(JNAME,ID,INFO,LANG,ROLES,IPADDRESS,LOGINTIME,LASTACCESS,YMD,YMDH,LASTGAMENNM)を
	 * 登録しようとした場合は、エラーにします。
	 *
	 * ※ 属性文字列キーが不正の場合、HybsSystemException が、throw されます。
	 *
	 * @param	key  	キー
	 * @param	value	値
	 * @param	save 	ユーザー永続化情報(GE20)に情報を保存するか
	 */
	public void setAttribute( final String key,final String value, final boolean save ) {
		setAttribute( key, value, save, false );
	}

	/**
	 * UserInfoの属性文字列を登録します。
	 *
	 * 予約文字(JNAME,ID,INFO,LANG,ROLES,IPADDRESS,LOGINTIME,LASTACCESS,YMD,YMDH,LASTGAMENNM)を
	 * 登録しようとした場合は、エラーにします。
	 *
	 * ※ 属性文字列キーが不正の場合、HybsSystemException が、throw されます。
	 *
	 * @og.rev 3.5.6.0 (2004/06/18) synchronized をattriMap に行います。
	 * @og.rev 4.3.4.0 (2008/12/01) ユーザー永続化情報(GE20)へ登録するかのフラグを追加
	 * @og.rev 5.3.6.0 (2011/06/01) ユーザー永続化情報(GE20)へ登録時に全ユーザー公開するかのフラグを追加
	 * @og.rev 5.6.8.1 (2013/09/13) 値が null、またはゼロ文字列の場合、処理しません。
	 * @og.rev 5.7.2.2 (2014/01/24) 値が null、またはゼロ文字列の場合でも処理します。(5.6.8.1 以前に戻します)
	 *
	 * @param	key  	キー
	 * @param	value	値
	 * @param	save 	ユーザー永続化情報(GE20)に情報を保存するか
	 * @param	common 	ユーザー永続化情報(GE20)に保存した情報を全ユーザー公開するか
	 */
	private void setAttribute( final String key,final String value, final boolean save, final boolean common ) {
		if( key != null && YOYAKU.indexOf( "|" + key + "|" ) < 0 ) {
			// 5.6.8.1 (2013/09/13) 値が null、またはゼロ文字列の場合、処理しません。
			// 5.7.2.2 (2014/01/24) 値が null、またはゼロ文字列の場合でも処理します。(5.6.8.1 以前に戻します)
			synchronized( attriMap ) {
				attriMap.put( key,value ) ;
			}

			// 4.3.4.0 (2008/12/01) 追加
			if( save ) {
				savePermanently( key ,value, common );
			}
		}
		else {
			final String errMsg = "属性文字列キーが不正です。 key=[" + key + "]"
						+ CR
						+ "null または予約語(" + YOYAKU + ") は指定できません。";
			throw new HybsSystemException( errMsg );
		}
	}

	/**
	 * UserInfoの属性文字列を取得します。
	 *
	 * 以下の値は UserInfo オブジェクトの項目から取得します。
	 * <pre>
	 * ・JNAME      ユーザー日本語名称
	 * ・ID         ユーザーＩＤ
	 * ・IDNO       (初期値)USER.ID が５Byte以上の時のみ先頭１文字を除いたユーザーＩＤ
	 * ・INFO       ユーザー情報(ユーザーID：日本語名称)
	 * ・LANG       言語
	 * ・ROLES      ロール
	 * ・IPADDRESS  IPアドレス
	 * ・LOGINTIME  ログイン時刻
	 * ・LASTACCESS 最終アクセス画面ID
	 * ・LASTGAMENNM 最終アクセス画面名
	 *
	 * 以下の値はあらかじめ、動的に作成されます。
	 * ・YMD       ８byte の今日のシステム日付
	 * ・YMDH    １４byte の今日のシステム日時
	 * </pre>
	 *
	 * それ以外は,外部より設定された値です。
	 *
	 * @og.rev 2.1.0.2 (2002/11/07) USER.IDNO の返す値をUSER.ID が５Byte以上の時のみ、
	 * 先頭1文字を除いた値を返す様に変更。それ以外は、USER.IDを返す。
	 *
	 * @og.rev 2.2.0.0 (2002/12/17) 中国語(国際化)対応  ENCODE 追加
	 * @og.rev 3.1.3.0 (2003/04/10) ユーザー情報から、エンコード情報を削除する。
	 * @og.rev 3.5.4.2 (2003/12/15) ENAME,MAILTO、MAILUSERID、MAILPASSWD、GROUP、PROJECTを削除する。
	 * @og.rev 3.5.6.0 (2004/06/18) synchronized をattriMap に行います。
	 * @og.rev 3.6.0.0 (2004/09/17) PASSWD を削除する。
	 * @og.rev 3.8.1.2 (2005/12/19) USER.IDNO を削除する。(外部設定可能にするため)
	 * @og.rev 3.8.7.0 (2006/12/15) ApplicationInfoオブジェクトから最終アクセス画面を取得
	 * @og.rev 4.4.0.0 (2009/08/02) データロール属性対応
	 * @og.rev 4.4.0.1 (2009/08/08) LASTGAMENNM追加
	 * @og.rev 6.4.1.1 (2016/01/16) PMD refactoring. Position literals first in String comparisons for EqualsIgnoreCase.
	 * @og.rev 6.4.2.0 (2016/01/29) DateSet.getDate( String ) を利用するように修正します。
	 * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(DataRole.java廃止)
	 *
	 * @param	key  	キー
	 *
	 * @return	UserInfoの属性文字列
	 */
	@Override	// UserSummary
	public String getAttribute( final String key ) {
		final String rtn ;

		if( key == null ) { rtn = null; }
		else {
			if(      "JNAME"		.equalsIgnoreCase( key ) ) { rtn = jname; }
			else if( "ID"			.equalsIgnoreCase( key ) ) { rtn = userID; }
			else if( "INFO"			.equalsIgnoreCase( key ) ) { rtn = getInfo(); }
			else if( "LANG"			.equalsIgnoreCase( key ) ) { rtn = lang; }
			else if( "ROLE"			.equalsIgnoreCase( key ) ) { rtn = roles; }
			else if( "ROLES"		.equalsIgnoreCase( key ) ) { rtn = roles; }
	//		else if( "DROLES"		.equalsIgnoreCase( key ) ) { rtn = droles; } // 4.4.0.0 (2009/08/02) 7.4.4.0 (2021/06/30) Delete
			else if( "IPADDRESS"	.equalsIgnoreCase( key ) ) { rtn = ipAddress; }
			else if( "LOGINTIME"	.equalsIgnoreCase( key ) ) { rtn = HybsSystem.getDate( loginTime ); }
			else if( "YMD"			.equalsIgnoreCase( key ) ) { rtn = DateSet.getDate( "yyyyMMdd" ); }					// 6.4.2.0 (2016/01/29)
			else if( "YMDH"			.equalsIgnoreCase( key ) ) { rtn = DateSet.getDate( "yyyyMMddHHmmss" ); }			// 6.4.2.0 (2016/01/29)
			else if( "LASTACCESS"	.equalsIgnoreCase( key ) ) { rtn = getGamenId(); }
			else if( "LASTGAMENNM"	.equalsIgnoreCase( key ) ) { rtn = getGamenName(); }
			else {
				synchronized( attriMap ) {
					rtn = attriMap.get( key ) ;
				}
			}
		}

		return rtn ;
	}

	/**
	 * ApplicationInfo から、画面ID を取得します。
	 * ApplicationInfoが null の場合は、null を返します。
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) 新規追加
	 *
	 * @return	画面ID(ApplicationInfo#getGamenId())
	 */
	private String getGamenId() {
		return appInfo == null ? null : appInfo.getGamenId();
	}

	/**
	 * GUIInfo から、画面名 を取得します。
	 * GUIInfo がない場合、または、GUIInfo の取り出しも元の、画面ID がない、
	 * さらに、画面ID の取出し元の ApplicationInfoが null の場合は、null を返します。
	 *
	 * @og.rev 6.4.1.1 (2016/01/16) 新規追加
	 *
	 * @return	画面名(GUIInfo#getName())
	 */
	private String getGamenName() {
		final GUIInfo guiInfo = getGUIInfo( getGamenId() );		// 引数 null 対応済み
		return guiInfo == null ? null : guiInfo.getName();
	}

	/**
	 * UserInfoの属性文字列の内部情報を返します。
	 * この内部情報の中には、UserInfo 自身の管理情報も含めます。
	 * 独自管理キーは、JNAME,ID,IDNO,INFO,LANG,ROLES,IPADDRESS,LOGINTIME,LASTACCESS,LASTGAMENNM です。
	 *
	 * それ以外は,外部より設定された値です。
	 *
	 * @og.rev 4.0.0.0 (2004/12/31) 新規作成
	 * @og.rev 4.4.0.1 (2009/08/08) LASTGAMENNM追加
 	 *
	 * @return 属性文字列のHybsEntryオブジェクト配列
	 * @og.rtnNotNull
	 */
	public HybsEntry[] getEntrys() {
		final List<HybsEntry> list = new ArrayList<>();

		list.add( new HybsEntry( "JNAME"		, getAttribute( "JNAME"		) ,"ユーザー日本語名称" ) );
		list.add( new HybsEntry( "ID"			, getAttribute( "ID"		) ,"ユーザーＩＤ" ) );
		list.add( new HybsEntry( "IDNO" 		, getAttribute( "IDNO"		) ,"USER.ID が５Byte以上の時のみ先頭１文字を除いたユーザーＩＤ" ) );
		list.add( new HybsEntry( "INFO" 		, getAttribute( "INFO"		) ,"ユーザー情報(ユーザーID：日本語名称)" ) );
		list.add( new HybsEntry( "LANG" 		, getAttribute( "LANG"		) ,"言語" ) );
		list.add( new HybsEntry( "ROLES"		, getAttribute( "ROLES"		) ,"ロールズ" ) );
		list.add( new HybsEntry( "IPADDRESS"	, getAttribute( "IPADDRESS" ) ,"IPアドレス" ) );
		list.add( new HybsEntry( "LOGINTIME"	, getAttribute( "LOGINTIME" ) ,"ログイン時刻" ) );
		list.add( new HybsEntry( "LASTACCESS"	, getAttribute( "LASTACCESS" ) ,"最終アクセス画面ID" ) );
		list.add( new HybsEntry( "LASTGAMENNM" 	, getAttribute( "LASTGAMENNM") ,"最終アクセス画面名" ) ); // 4.4.0.1 (2009/08/08)
		list.add( new HybsEntry( "YMD"			, getAttribute( "YMD"		) ,"　８byte の今日のシステム日付" ) );
		list.add( new HybsEntry( "YMDH" 		, getAttribute( "YMDH"		) ,"１４byte の今日のシステム日時" ) );

		synchronized( attriMap ) {
			final String[] keys = attriMap.keySet().toArray( new String[attriMap.size()] );
			Arrays.sort( keys );
			for( int i=0; i<keys.length; i++ ) {
				list.add( new HybsEntry( keys[i],getAttribute( keys[i] ) ) );
			}
		}
		return list.toArray( new HybsEntry[list.size()] );
	}

	/**
	 * UserInfoの属性文字列を削除します。
	 *
	 * @param	key  	キー
	 * @param	save 	ユーザー永続化情報(GE20)から情報を削除するか
	 */
	public void removeAttribute( final String key, final boolean save ) {
		removeAttribute( key, save, false );
	}

	/**
	 * UserInfoの属性文字列を削除します。
	 *
	 * @og.rev 3.5.6.0 (2004/06/18) synchronized をattriMap に行います。
	 * @og.rev 5.3.6.0 (2011/06/01) ユーザー永続化情報(GE20)から削除するかのフラグを追加
	 *
	 * @param	key  	キー
	 * @param	save 	ユーザー永続化情報(GE20)から情報を削除するか
	 * @param	common 	ユーザー永続化情報(GE20)から情報削除時、全ユーザー公開情報を削除するか
	 */
	private void removeAttribute( final String key, final boolean save, final boolean common ) {
		synchronized( attriMap ) {
			attriMap.remove( key ) ;
		}

		if( save ) {
			deletePermanently( key, common );
		}
	}

	/**
	 * ユーザー個別の画面オブジェクトのマップをセットします。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 4.1.1.0 (2008/01/29) 画面の格上げとお気に入りマップ作成はクラスUserAccessTableに依頼
	 * @og.rev 5.2.0.0 (2010/09/01) アクセス禁止アドレスによる不正アクセス防止機能追加
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います
	 *
	 * @param	newGuiMap			画面オブジェクトのマップ
	 * @param	newForbidAddrSet	アクセス禁止アドレスセット
	 */
	public void setGUIMap( final Map<String,GUIInfo> newGuiMap, final Set<String> newForbidAddrSet ) {
		if( newGuiMap != null ) {
			synchronized( guiLock ) {
				guiMap.clear();							// 6.4.3.1 (2016/02/12)
				guiMap.putAll( newGuiMap );				// 6.4.3.1 (2016/02/12)
				forbidAddrSet  = newForbidAddrSet;
				favoriteGuiMap = UserAccessTable.makeAccessDB( guiMap,systemId,userID,lang );
			}
		}
	}

	/**
	 * ユーザー個別の画面オブジェクトを取得します。
	 * アクセスできない画面IDが指定されたときは、null が返ります。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 5.2.0.0 (2010/09/01) guiMap の null 判定を追加
	 * @og.rev 6.4.1.1 (2016/01/16) 引数の gamenId が、null の場合は、null を返すように変更。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @param	gamenId	画面ID
	 *
	 * @return	画面オブジェクト
	 */
	public GUIInfo getGUIInfo( final String gamenId ) {
		return gamenId == null ? null : guiMap.get( gamenId );
	}

	/**
	 * ユーザー個別の画面オブジェクトのマップを取得します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 5.2.0.0 (2010/09/01) guiMap の null 判定を追加
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 *
	 * @return	画面オブジェクトの配列
	 */
	public GUIInfo[] getGUIInfos() {
		return guiMap.values().toArray( new GUIInfo[guiMap.size()] ) ;
	}

	/**
	 * アクセスが許可されているアドレスかどうかをチェックします。
	 *
	 * チェックの方法は、ブラックリスト方式です。
	 * 画面リソースに登録された一覧の内、そのユーザーが許可されていないアドレスに
	 * 対してのみfalseが返ります。
	 * 画面リソースに登録されていないアドレスや、アドレスにURLの区切り文字(/)が
	 * 含まれている場合はチェックされません。(trueが返ります)
	 *
	 * @og.rev 5.2.0.0 (2010/09/01) 新規追加
	 *
	 * @param addr チェック対象のアドレス
	 *
	 * @return アクセスが許可されているアドレスかどうか
	 */
	public boolean isValidAddr( final String addr ) {
		synchronized( guiLock ) {
			return forbidAddrSet == null || !forbidAddrSet.contains( addr ) ;
		}
	}

	/**
	 * ユーザー個別のお気に入り画面オブジェクトのマップを取得します。
	 *
	 * @og.rev 4.1.1.0 (2008/01/31) 新規追加
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.unmodifiableMap( Map&lt;? extends K,? extends V&gt; ) で作成された変更不可のMapを返します。
	 *
	 * @return	お気に入り画面オブジェクトのマップ
	 */
	public Map<String,FavoriteGUIData> getFavoriteMap() {
		return favoriteGuiMap;
	}

	/**
	 * 画面オブジェクトのマップがセット済みかどうかを取得します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 6.4.3.1 (2016/02/12) isInfoSet 廃止。
	 *
	 * @return 画面オブジェクトのマップがセット済みかどうか(true:セット済み / false:未セット)
	 */
	public boolean isGUIInfoSet() {
		return !guiMap.isEmpty();			// 6.4.3.1 (2016/02/12)
	}

	/**
	 * 指定のユーザーロールに対する最終的なアクセス条件を取得します。
	 * アクセス条件は、複数あるユーザーロールの中で、最大のアクセス条件を算出します。
	 * 例えば、AAA(-r)|BBB(-w)|CCC(mr) の３つのロール/モードが設定されている場合、
	 * ユーザーが、AAA だけの場合は、-r ですが、AAA|BBB を持っている場合は、-w になります。
	 * さらに、BBB|CCC と持っている場合は、(-w:書き込み許可)と(mr:メニューから読取許可)の
	 * 権限により、mw:メニューからの書き込み許可が与えられます。
	 * モード指定がある場合は、AND演算になります。
	 * 例えば、AAA(-r)|BBB(-w)|CCC(mr) と BBB|CCC(-r) の場合、(-r)+(-w)+(mr)*(-r)=-w に
	 * なります。ロールは、OR ですが、モードは、同一ロールでのAND になります。
	 * 実際には、メニュー表示の可否は、ポップアップ系によく用いられますので、上記のような
	 * 許可が実際にあるかどうかは不明ですが、すべてのモードのOR条件での結合になります。
	 *
	 * @og.rev 4.3.0.0 (2008/07/04) ロールモードマルチ対応
	 *
	 * @param	other	ロールモード
	 *
	 * @return アクセスビット
	 */
	public byte getAccessBitMode( final RoleMode other ) {
		return roleMode.getAccessBitMode( other );
	}

	/**
	 * このユーザーの権限で、指定のロールが許可されているかどうかを判定します。
	 *
	 * @og.rev 4.3.0.0 (2008/07/04) ロールモードマルチ対応
	 * @og.rev 4.3.0.1 (2008/08/11) ロールチェック時の引数間違い、是正
	 * @og.rev 6.3.6.1 (2015/08/28) '_' を、null と同等に扱います(すべて許可と同じ)
	 *
	 * @param	role	チェックを行うロール
	 *
	 * @return	アクセスできる(true)/出来ない(false)
	 */
	public boolean isAccess( final String role ) {
		// 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
		return role == null || role.isEmpty() || role.charAt(0) == '_'
				|| roleMode.getAccessBitMode( RoleMode.newInstance( role ) ) > 0 ;		// 4.3.0.1 (2008/08/11)
	}

	/**
	 * 初期化(クリア)します(org.opengion.fukurou.util.Cleanable の実装)。
	 * 画面オブジェクトのマップをクリアし、セット済みフラグを未セットに設定します。
	 * システムリソースの USE_ACCESS_TOKEI_TABLE が true の場合は、
	 * 画面アクセス状況を、ｱｸｾｽ統計ﾃｰﾌﾞﾙ(GE15)に書き込みます。
	// * ユーザー単位のパラメータは、システムリソースの USER_PARAMETER_SQL で
	// * 定義された値を検索して、取り込みます。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 5.6.8.1 (2013/09/13) lastRequestMap を ユーザー永続化情報(GE20) に書き込みます。
	 * @og.rev 6.4.3.1 (2016/02/12) isInfoSet 廃止。
	 * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(GE16廃止)
	 */
	public void clear() {
		if( useAccessTable && isGUIInfoSet() ) { saveGUIAccessInfo(); }		// 6.4.3.1 (2016/02/12) isInfoSet 廃止
	//	initLoad() ;															// 7.4.4.0 (2021/06/30) Delete

		saveLastRequestValues();	// 5.6.8.1 (2013/09/13) lastRequestMap を ユーザー永続化情報(GE20) に書き込みます。
	}

	// /**
	// * ユーザーパラメータを取得します。
	// * 画面オブジェクトのマップをクリアし、セット済みフラグを未セットに設定します。
	// * ユーザー単位のパラメータは、システムリソースの USER_PARAMETER_SQL で
	// * 定義された値を検索して、取り込みます。
	// *
	// * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	// * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	// * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(GE16廃止)
	// */
	// private void initLoad() {
	// 	// ユーザーパラメータの取得
	// 	if( QUERY_PARAM != null && QUERY_PARAM.length() > 0 ) {
	// 		final String[] args = new String[] { systemId,userID };
	// 		// 画面ID,操作,プログラムID
	// 		if( appInfo != null ) {
	// 			// 画面ID,操作,プログラムID
	// 			appInfo.setModuleInfo( "UserInfo",null,"initLoad" );
	// 		}
	// 		final String[][] vals = DBUtil.dbExecute( QUERY_PARAM,args,appInfo,DBID );

	// 		// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	// 			paramMap.clear();
	// 			for( int i=0; i<vals.length; i++ ) {
	// 				final String key = vals[i][0];
	// 				final String val = vals[i][1];
	// 				// 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
	// 				if( key != null && val != null && !val.isEmpty() ) {		// 元の条件 val.isEmpty() も加味しておきます。
	// 					paramMap.put( key,val );
	// 				}
	// 			}
	// 	}
	// }

	/**
	 * ユーザー永続化情報(GE20)からDBに保存されたUserInfoのパラメーターを取得します。
	 *
	 * ここでは、キーの先頭が、LAST_REQUEST_DATA_SUFIX(="LAST_REQUEST_")文字列が、
	 * 付いている物だけ lastRequestMap マップに設定します。(分けて管理します)
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 5.6.8.1 (2013/09/13) setAttribute メソッドではなく、直接 Mapに登録します。
	 * @og.rev 5.7.2.2 (2014/01/24) 値が null、またはゼロ文字列の場合でも処理します。(5.6.8.1 以前に戻します)
	 *
	 * ※ コンストラクタのみで呼ばれるため、synchronized は入れていません。
	 * @see		#LAST_REQUEST_DATA_SUFIX
	 */
	private void dbLoad() {
		// ユーザー永続化情報(GE20)テーブル読み込み
		final String[] argsGe20 = new String[] { systemId,userID,roles };
		final String[][] valsGe20 = DBUtil.dbExecute( QUERY_GE20,argsGe20,appInfo,DBID );

		for( int i=0; i<valsGe20.length; i++ ) {
			// 4.3.4.0 (2008/12/01) ユーザー永続化情報(GE20)から読み込んでいるので、当然保存しない
			final String key = valsGe20[i][0];
			final String val = valsGe20[i][1];
			if( key != null && key.length() > 0 ) {
				if( key.startsWith( LAST_REQUEST_DATA_SUFIX ) ) {
					// val が null かどうかは問わない
					lastRequestMap.put( key.substring( LAST_REQUEST_DATA_SUFIX.length() ) , val );
				}
				else {
	 				// 5.7.2.2 (2014/01/24) 値が null、またはゼロ文字列の場合でも処理します。(5.6.8.1 以前に戻します)
					attriMap.put( key,val ) ;
				}
			}
		}
	}

	/**
	 * 画面ID、編集名より編集設定オブジェクトを返します。
	 * また、ここで指定された編集名がこの画面での選択済み編集として登録されます。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 *
	 * @return 編集設定オブジェクト
	 */
	public DBEditConfig getEditConfig( final String guikey, final String editName ) {
		if( editName != null ) {
			final String selEditName = getAttribute( "EDIT_SELECTED_" + guikey );
			if( !editName.equals( selEditName ) ) {
				setAttribute( "EDIT_SELECTED_" + guikey, editName, true );
			}
		}
	//	else {
	//		setSelectedEdit( guikey, null );
	//	}
		return editMgr.getEditConfig( guikey, editName );
	}

	/**
	 * 編集設定オブジェクトの配列を返します。
	 *
	 * DBEditConfigManagerを直接返していましたが、それを廃止します。
	 *
	 * @og.rev 6.9.2.1 (2018/03/12) DBEditConfigManagerを直接取り出す処理を、廃止します。
	 *
	 * @param guikey 画面ID
	 * @return  編集設定オブジェクトの配列
	 */
	public DBEditConfig[] getEditConfigs( final String guikey ) {
		return editMgr.getEditConfigs( guikey );
	}

	/**
	 * 指定の画面ID、編集名で編集設定オブジェクトを追加します。
	 * 既に登録されている場合は、既存の編集情報を更新します。
	 *
	 * ※ 廃止予定ですが、対応が間に合わないため、暫定的に残しています。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
	 * @og.rev 6.0.2.2 (2014/10/03) getEditKeys(String,String) を、DBEditConfig ⇒ DBEditConfigManager 変更
	 * @og.rev 6.3.9.1 (2015/11/27) getEditKeys(String,String) は、DBEditConfigManager ⇒ DBEditConfig へ移動。
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。廃止
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 * @param config 編集設定オブジェクト
	 */
	public void addEditConfig( final String guikey, final String editName, final DBEditConfig config ) {
		if( config != null ) {
			editMgr.addEditConfig( guikey, editName, config );

			final boolean isCommon = config.isCommon();
			config.forEach( guikey
						, (key,val) -> {
								if( val != null && val.length() > 0 ) {
									setAttribute( key, val, true, isCommon );
								}
								else {
									removeAttribute( key, true, isCommon );
								}
							} );
		}
	}

	/**
	 * 指定の画面ID、編集名の編集設定を削除します。
	 *
	 * ※ 廃止予定ですが、対応が間に合わないため、暫定的に残しています。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
	 * @og.rev 6.0.2.2 (2014/10/03) getEditKeys( String, String ) を、DBEditConfig ⇒ DBEditConfigManager 変更
	 * @og.rev 6.3.9.1 (2015/11/27) getEditKeys(String,String) は、DBEditConfigManager ⇒ DBEditConfig へ移動。
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。廃止
	 *
	 * @param guikey 画面ID
	 * @param editName 編集名
	 */
	public void deleteEditConfig( final String guikey, final String editName ) {
		final DBEditConfig config = editMgr.deleteEditConfig( guikey, editName );
		if( config != null ) {
			final boolean isCommon = config.isCommon();
	//		final String[] editKeys = DBEditConfig.getEditKeys( guikey, editName );			// 6.3.9.1 (2015/11/27) メソッド移動
	//		// 編集設定が存在しない場合エラー。
	//		if( !isExistValue( editKeys[0], ( isCommon ? "*" : userID ), "*" ) ) {
	//			final String errMsg = "編集設定が存在しません。";
	//			throw new HybsSystemException( errMsg );	// 5.7.1.2 (2013/12/20) msg ⇒ errMsg 変更
	//		}

			config.forEach( guikey , (key,val) -> removeAttribute( key, true, isCommon ) );

	//		for( int i=0; i<editKeys.length; i++ ) {
	//			removeAttribute( editKeys[i], true, isCommon );
	//		}
			removeAttribute( "EDIT_SELECTED_"      + guikey, true, isCommon );		// 6.0.2.2 (2014/10/03) 初期値用のキーも削除
			removeAttribute( "EDIT_NAME_SELECTED_" + guikey, true, isCommon );		// 6.0.2.2 (2014/10/03) 昔のキーは強制削除
		}

	//	if( editName != null ) {
	//		String selEditName = getSelectedEdit( guikey );
	//		if( !editName.equals( selEditName ) ) {
	//			setSelectedEdit( guikey, null );
	//		}
	//	}
	}

	/**
	 * 指定の画面IDに対して選択済みの編集名を返します。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 6.0.2.2 (2014/10/03) EDIT_NAME_SELECTED_ を、EDIT_SELECTED_ に変更
	 * @og.rev 6.4.5.0 (2016/04/08) UserInfo のEditConfig関連機能を、DBEditConfigManagerに移植します。廃止
	 *
	 * @param guikey 画面ID
	 *
	 * @return 選択済み編集名
	 */
	public String getSelectedEdit( final String guikey ) {
		return getAttribute( "EDIT_SELECTED_" + guikey );
	}

	/**
	 * 最後に使用されたリクエスト変数の値を、Mapを読み取って登録します。
	 *
	 * 読み取り対象は、先に lastRequestMap に登録済みのキーだけです。
	 * そのため、{&#064;LAST.XXXX} で値を要求されたときに、キーが
	 * 登録されていない場合は、キーだけ(値 nullで)登録しておきます。
	 *
	 * @og.rev 5.6.8.1 (2013/09/13) 新規追加
	 *
	 * @param reqMap リクエスト変数のMap
	 */
	public void setLastRequestMap( final Map<String,String[]> reqMap ) {
		if( reqMap != null ) {
			synchronized( lastRequestMap ) {
				for( final String key : lastRequestMap.keySet() ) {
					if( key != null ) {									// 6.4.3.3 (2016/03/04)
						final String[] vals = reqMap.get( key );
						if( vals != null ) {
							String val = null;
							for( int i=0; i<vals.length; i++ ) {
								val = vals[i];
								if( ! "0".equals( val ) ) { break; }	// チェックボックス対応
							}
							lastRequestMap.put( key, val );				// val は、null もあり得る。
						}
					}
				}
			}
		}
	}

	/**
	 * 最後に使用されたリクエスト変数の値を、設定します。
	 *
	 * この処理は、{&#064;LAST.XXXX} は、リクエスト値があれば、それが優先的に
	 * 使われます。
	 *
	 * @og.rev 5.6.8.1 (2013/09/13) 新規追加
	 *
	 * @param  key リクエストキー
	 * @param  val 設定値
	 */
	public void setLastRequestValue( final String key,final String val ) {
		if( key != null && key.length() > 0) {
			synchronized( lastRequestMap ) {
				lastRequestMap.put( key, val );
			}
		}
	}

	/**
	 * 最後に使用されたリクエスト変数の値を、取得します。
	 *
	 * 画面で簡素に使用できるように、少し特殊な処理を行います。
	 * query 画面で {&#064;LAST.XXXX} を呼ぶと、lastRequestMap にキーがなければ、
	 * キーだけ先に追加します。あれば、値を取得するだけです。
	 * そして、result画面で command="NEW" の場合のみ、リクエスト情報のMapから、
	 * lastRequestMap に持っているキーで(NULLでない場合は)上書きセットします。
	 * キャッシュ量を減らすことと、処理の対象キーを減らす意味を持っています。
	 *
	 * @og.rev 5.6.8.1 (2013/09/13) 新規追加
	 *
	 * @param  key リクエストキー
	 * @return 設定値
	 */
	public String getLastRequestValue( final String key ) {
		String rtn = null;
		if( key != null && key.length() > 0) {
			synchronized( lastRequestMap ) {
				if( lastRequestMap.containsKey( key ) ) {	// キーを持っているかどうかを判定
					rtn = lastRequestMap.get( key );
				}
				else {
					lastRequestMap.put( key, null );		// キーだけ登録しておく。
				}
			}
		}
		return rtn ;
	}

	/**
	 * lastRequestMap を ユーザー永続化情報(GE20) に書き込みます。
	 *
	 * clear() 処理が実行された場合に、まとめて ユーザー永続化情報(GE20) に書き込みます。
	 * タイミング的には、saveGUIAccessInfo() メソッドと同じですが、saveGUIAccessInfo() は、
	 * 書き込む条件( useAccessTable && isInfoSet ) があります。
	 * セーブする時には、他の属性と区別するため、接頭語 LAST_REQUEST_DATA_SUFIX(="LAST_REQUEST_") を
	 * キーに付けて渡します。
	 *
	 * 読み取りは、dbLoad() で、attriMap と同じタイミングで、コンストラクタで、行います。
	 *
	 * @og.rev 5.6.8.1 (2013/09/13) 新規追加
	 * @og.rev 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
	 * @og.rev 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
	 *
	 * @see		#clear()
	 * @see		#dbLoad()
	 */
	private void saveLastRequestValues() {
		int cnt = 0;
		synchronized( lastRequestMap ) {
			// 6.3.9.0 (2015/11/06) Map.keySet() ではなく、Map.entrySet() を使う様に変更。
			// 6.4.3.4 (2016/03/11) forループを、forEach メソッドに置き換えます。
			lastRequestMap.forEach( (key,val) -> savePermanently( LAST_REQUEST_DATA_SUFIX + key , val , false ) );

			cnt = lastRequestMap.size();
		}
		System.out.println( "  [" + userID + "] 最終ﾘｸｴｽﾄ情報({@LAST.XXXX})を、(GE20)に、[" + cnt + "]件、登録しました。" );
	}

	/**
	 * アクセスログ取得の為,ApplicationInfoオブジェクトを返します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) 新規追加
	 *
	 * @param	gamenId	実行中の画面ID
	 * @param	prgId	実行中のプログラムID
	 *
	 * @return	ApplicationInfoオブジェクト
	 */
	public ApplicationInfo getApplicationInfo( final String gamenId,final String prgId ) {
		if( appInfo != null ) {
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( gamenId,null,prgId );
		}
		return appInfo;
	}

	/**
	 * 自然比較メソッド
	 * インタフェース Comparable の 実装です。
	 * ユーザーの順序は、ユーザーID そのものの順序であらわされます。
	 * 同一ユーザーの場合は，ログインタイムの順番になります。
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) UserSummary の Comparable を型設定
	 *
	 * @param	object	比較対象のObject
	 *
	 * @return	このオブジェクトが指定されたオブジェクトより小さい場合は負の整数、等しい場合はゼロ、大きい場合は正の整数
	 */
	@Override	// Comparable
	public int compareTo( final UserSummary object ) {
		int test1 = userID.compareTo( object.getUserID() );
		if( test1 == 0 ) {
			test1 = (int)( loginTime - object.getLoginTime() ) ;
		}
		return test1;
	}

	/**
	 * このオブジェクトと他のオブジェクトが等しいかどうかを示します。
	 * インタフェース Comparable の 実装に関連して、再定義しています。
	 * ユーザーは、ユーザーIDが等しく、かつ ログイン時刻が同一の場合に、
	 * 等しいと判断されます。
	 *
	 * @param   object 比較対象の参照オブジェクト
	 *
	 * @return	引数に指定されたオブジェクトとこのオブジェクトが等しい場合は true、そうでない場合は false
	 */
	@Override	// Object
	public boolean equals( final Object object ) {
		// 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
		return object instanceof UserInfo
					&& userID.equals( ((UserInfo)object).getUserID()  )
					&& loginTime == ( ((UserInfo)object).getLoginTime() );
	}

	/**
	 * オブジェクトのハッシュコード値を返します。
	 * このメソッドは、java.util.Hashtable によって提供されるような
	 * ハッシュテーブルで使用するために用意されています。
	 * equals( Object ) メソッドをオーバーライトした場合は、hashCode() メソッドも
	 * 必ず 記述する必要があります。
	 * ここでは、ログイン時刻(long 値)の上位 32 ビットと下位 32 ビットの排他的論理和
	 * を求めています。
	 * (int)(this.longValue()^(this.longValue()&gt;&gt;&gt;32))
	 *
	 * ※ hashCode の 同一オブジェクトには同一ハッシュコードという規則と
	 *    発生頻度,ランダム性を考慮すれば、ログイン時刻そのもの(long)の
	 *    ハッシュコードでも運用上は全く問題ないと考えられます。
	 *
	 * @og.rev 3.5.6.0 (2004/06/18) 新規追加
	 *
	 * @return  このオブジェクトのハッシュコード値
	 *
	 */
	@Override	// Object
	public int hashCode() { return hcode ; }

	/**
	 * オブジェクトの識別子として，詳細なユーザー情報を返します。
	 *
	 * @return  詳細なユーザー情報
	 * @og.rtnNotNull
	 */
	@Override	// Object
	public String toString() {
		final StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE )
			.append( "userID   :" ).append( userID	 ).append( CR )
			.append( "lang     :" ).append( lang	 ).append( CR )
			.append( "jname    :" ).append( jname	 ).append( CR )
			.append( "roles    :" ).append( roles	 ).append( CR )
			.append( "IPAddress:" ).append( ipAddress ).append( CR )
			.append( "loginTime:" ).append( loginTime ).append( CR );
		return rtn.toString();
	}

	/**
	 * ユーザー個別の画面オブジェクトの明細情報をｱｸｾｽ統計ﾃｰﾌﾞﾙ(GE15)に登録します。
	 *
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.0.0 (2005/01/31) 新規追加
	 * @og.rev 4.0.0.0 (2007/10/05) SQLServer 互換性の為、SUBSTRB を廃止します。
	 * @og.rev 4.1.1.0 (2008/01/30) ユーザーアクセス画面管理テーブルに画面の最終アクセス時間を更新
	 * @og.rev 5.0.2.0 (2009/11/01) 作成・更新日付がセットされていないバグを修正
	 * @og.rev 5.2.3.0 (2010/12/01) 画面アクセスの履歴(順番)を管理する機能を追加
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.2.0 (2016/01/29) DateSet.getDate( String ) を利用するように修正します。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 */
	private void saveGUIAccessInfo() {
	//	if( !useAccessTable || !isInfoSet ) { return ; }

		final GUIInfo[] infos = getGUIInfos() ;
		guiMap.clear();							// 6.4.3.1 (2016/02/12) getGUIInfos() してから、クリアします。

		final long crntTime = System.currentTimeMillis();

		final String[] names = new String[] { "SYSTEM_ID","USERID","USERADRS","HOSTADRS","GUIKEY","DYLOGIN","DYLOGOUT",
										"USED_TIME","CNT_ACCESS","CNT_ERROR","CNT_READ","CNT_WRITE",
										"TM_TOTAL_QUERY","TM_MAX_QUERY","MAX_QUERY","FGJ","DYSET","DYUPD","USRSET","USRUPD","PGUPD" };
		String[] values = new String[names.length];

		values[C_SYSTEM_ID		] = HybsSystem.sys( "SYSTEM_ID" );
		values[C_USERID			] = userID;
		values[C_USERADRS		] = ipAddress;
		values[C_HOSTADRS		] = HybsSystem.sys( "HOST_ADRS" );
		values[C_GUIKEY			] = "";
		values[C_DYLOGIN		] = DateSet.getDate( loginTime,"yyyyMMddHHmmss" );				// 6.4.2.0 (2016/01/29)
		values[C_DYLOGOUT 		] = DateSet.getDate( "yyyyMMddHHmmss" );						// 6.4.2.0 (2016/01/29)
		values[C_USED_TIME		] = String.valueOf( Math.round( (crntTime-usedTime) / 1000.0d ) );	// 秒に変換
		values[C_CNT_ACCESS		] = "0";
		values[C_CNT_ERROR		] = "0";
		values[C_CNT_READ 		] = "0";
		values[C_CNT_WRITE		] = "0";
		values[C_TM_TOTAL_QUERY	] = "0";
		values[C_TM_MAX_QUERY	] = "0";
		values[C_MAX_QUERY		] = "";
		values[C_FGJ			] = "1";
		values[C_DYSET			] = DateSet.getDate( "yyyyMMddHHmmss" );						// 6.4.2.0 (2016/01/29)
		values[C_DYUPD			] = DateSet.getDate( "yyyyMMddHHmmss" );						// 6.4.2.0 (2016/01/29)
		values[C_USRSET			] = "userInfo";
		values[C_USRUPD			] = "userInfo";
		values[C_PGUPD			] = "userInfo";

		usedTime = crntTime ;

		final DBSimpleTable dbTable = new DBSimpleTable( names );
		// 画面ID,操作,プログラムID
		getApplicationInfo( "UserInfo","saveGUI" );
		dbTable.setApplicationInfo( appInfo );	// 3.8.7.0 (2006/12/15)
		dbTable.setConnectionID( DBID );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
		dbTable.setTable( "GE15" );
		// 4.0.0.0 (2007/10/05) SQLServer 互換性の為、CLOB化します。
	//	dbTable.addConstrain( names[C_MAX_QUERY],"SUBSTRB(?,1,4000)" );

		boolean okFlag = false;
		try {
			dbTable.startInsert();

			// UserInfo に関する情報の登録
			dbTable.execute( values );

			// GUIInfo に関する情報の登録
			if( infos != null ) {
				values[C_USED_TIME] = "0";	// USED_TIME をクリアしておきます。
				final String logoutTime = DateSet.getDate( "yyyyMMddHHmmss" );					// 6.4.2.0 (2016/01/29)
				for( int i=0; i<infos.length; i++ ) {
					final GUIAccessCount access = infos[i].getGUIAccessCount();
					final int cnt = access.getAccessCount();
					if( cnt > 0 ) {
						values[C_GUIKEY			] = access.getKey();
						values[C_CNT_ACCESS		] = String.valueOf( cnt );
						values[C_CNT_ERROR		] = String.valueOf( access.getErrorCount() );
						values[C_CNT_READ 		] = String.valueOf( access.getReadCount() );
						values[C_CNT_WRITE		] = String.valueOf( access.getWriteCount() );
						values[C_TM_TOTAL_QUERY	] = String.valueOf( access.getQueryTime() );
						values[C_TM_MAX_QUERY	] = String.valueOf( access.getMaxQueryTime() );
						values[C_MAX_QUERY		] = access.getMaxQuery();
	//					dbTable.addValues( values );
						dbTable.execute( values );
						// 4.1.1.0(2008/01/28)画面アクセス時間の更新
						// 5.2.3.0 (2010/12/01) 画面アクセスの履歴(順番)を管理する機能を追加
						final String keys = infos[i].getNextGuiKeys();
						UserAccessTable.updateLastAccessTime( systemId,userID,access.getKey(),logoutTime,keys );
					}
				}
			}
			okFlag = true;
		}
		catch( final SQLException ex) {
			LogWriter.log( "  [" + userID + "] ｱｸｾｽ統計ﾃｰﾌﾞﾙ(GE15)登録時にエラーが発生しました" );
			LogWriter.log( ex.getMessage() );
		}
		finally {
			final int cnt = dbTable.close( okFlag );
			System.out.println( "  [" + userID + "] ｱｸｾｽ統計ﾃｰﾌﾞﾙ(GE15)に、[" + cnt + "]件、追加しました。" );
		}
	}

	/**
	 * userInfoにセットされた値/キーをDBに登録します。
	 * 既にキーが存在している場合は、既存データを更新し、なければ追加します。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 全ユーザー情報として保存できるように対応
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 6.4.2.0 (2016/01/29) DateSet.getDate( String ) を利用するように修正します。
	 * @og.rev 6.4.5.0 (2016/04/08) DBEditConfigManager で使えるように、private → public にします(ちょっと不安)。
	 *
	 * @param key キー
	 * @param value 値
	 * @param isCommon ユーザーID='*'(全ユーザー公開)として登録するかどうか
	 */
	public void savePermanently( final String key, final String value, final boolean isCommon ) {

		// 追加変更時に共通でセットされる値を設定
		final String[] names = new String[] { "SYSTEM_ID","USERID","ROLES","PARAM_ID","PARAM","KBSET"
										,"FGJ","DYSET","DYUPD","USRSET","USRUPD","PGUPD" };
		String[] values = new String[names.length];
		values[C_GE20_SYSTEM_ID	] = HybsSystem.sys( "SYSTEM_ID" );
		values[C_GE20_USERID	] = ( isCommon ? "*" : userID );
		values[C_GE20_ROLES		] = GE20_ROLES;
		values[C_GE20_PARAM_ID	] = key;
		values[C_GE20_PARAM		] = value;
		values[C_GE20_KBSET		] = String.valueOf( GE20_KBSET_WRITABLE );
		values[C_GE20_FGJ		] = "1";
		values[C_GE20_DYSET		] = DateSet.getDate( "yyyyMMddHHmmss" );				// 6.4.2.0 (2016/01/29)
		values[C_GE20_DYUPD		] = DateSet.getDate( "yyyyMMddHHmmss" );				// 6.4.2.0 (2016/01/29)
		values[C_GE20_USRSET 	] = userID;
		values[C_GE20_USRUPD	] = userID;
		values[C_GE20_PGUPD	] = "UserInfo";

		// 画面ID,操作,プログラムID
		getApplicationInfo( "UserInfo","registValueToDB" );

		final DBSimpleTable dbTable = new DBSimpleTable( names );
		dbTable.setApplicationInfo( appInfo );	// 3.8.7.0 (2006/12/15)
		dbTable.setConnectionID( DBID );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
		dbTable.setTable( "GE20" );

		boolean okFlag = false;
		try {
			if( isExistValue( key, ( isCommon ? "*" : userID ), GE20_ROLES ) ) {
				final String where = "SYSTEM_ID = [SYSTEM_ID] and USERID = [USERID] and ROLES = [ROLES] and PARAM_ID = [PARAM_ID] and FGJ='1'";
				dbTable.setWhere( where );
				dbTable.startUpdate();
			}
			else {
				dbTable.startInsert();
			}
			dbTable.execute( values );
			okFlag = true;
		}
		catch( final SQLException ex ) {
			throw new HybsSystemException( "ユーザー永続化情報(GE20)設定時にエラーが発生しました", ex );
		}
		finally {
			dbTable.close( okFlag );
		}
	}

	/**
	 * userInfoから削除された値/キーをDBからも削除します。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 新規追加
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 *
	 * @param key キー
	 * @param isCommon ユーザーID='*'(全ユーザー公開)から削除するかどうか
	 */
	private void deletePermanently( final String key, final boolean isCommon ) {

		// 追加変更時に共通でセットされる値を設定
		final String[] names = new String[] { "SYSTEM_ID","USERID","ROLES","PARAM_ID" };
		String[] values = new String[names.length];
		values[C_GE20_SYSTEM_ID	] = HybsSystem.sys( "SYSTEM_ID" );
		values[C_GE20_USERID	] = ( isCommon ? "*" : userID );
		values[C_GE20_ROLES		] = GE20_ROLES;
		values[C_GE20_PARAM_ID	] = key;

		// 画面ID,操作,プログラムID
		getApplicationInfo( "UserInfo","deleteValueFromDB" );

		final DBSimpleTable dbTable = new DBSimpleTable( names );
		dbTable.setApplicationInfo( appInfo );
		dbTable.setConnectionID( DBID );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応
		dbTable.setTable( "GE20" );

		boolean okFlag = false;
		try {
			final String where = "SYSTEM_ID = [SYSTEM_ID] and USERID = [USERID] and ROLES = [ROLES] and PARAM_ID = [PARAM_ID] and FGJ='1'";
			dbTable.setWhere( where );
			dbTable.startDelete();
			dbTable.execute( values );
			okFlag = true;
		}
		catch( final SQLException ex ) {
			throw new HybsSystemException( "ユーザー永続化情報(GE20)削除時にエラーが発生しました", ex );
		}
		finally {
			dbTable.close( okFlag );
		}
	}

	/**
	 * ユーザー永続化情報(GE20)に該当のキーが存在するかをチェックします。
	 *
	 * @og.rev 5.3.6.0 (2011/06/01) 全ユーザー情報として保存できるように対応
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 *
	 * @param key キー
	 * @param userid ユーザーID
	 * @param roles ロール
	 *
	 * @return true:存在している/false:存在していない
	 */
	private boolean isExistValue( final String key, final String userid, final String roles ) {
		final String[] args = { HybsSystem.sys( "SYSTEM_ID" ), userid, roles, key };

		// 画面ID,操作,プログラムID
		getApplicationInfo( "UserInfo","isExistValue" );

		final String[][] rtn = DBUtil.dbExecute( QUERY_GE20_KEY, args, appInfo, DBID );	// 5.5.5.1 (2012/08/07)
		if( rtn == null || rtn.length == 0 ) {
			return false;
		}
		else if( rtn[0].length > 0 ) {
			if( String.valueOf( GE20_KBSET_READONLY ).equals( rtn[0][0] ) ) {
				throw new HybsSystemException( "読み取り専用情報のため、書き込みできません" );
			}
			else {
				return true;
			}
		}
		else {
			throw new HybsSystemException( "ユーザー永続化情報(GE20)検索時にエラーが発生しました。" );
		}
	}

	// /**
	// * 指定されたカラムキーに対応するデータの条件式を返します。
	// *
	// * @og.rev 4.4.0.0 (2009/08/02) 新規追加
	// * @og.rev 7.4.4.0 (2021/06/30) openGionV8事前準備(DataRole.java廃止)
	// *
	// * @param clm カラム名
	// *
	// * @return データの条件式
	// */
	// 7.4.4.0 (2021/06/30) Delete
	// public String getDataCondition( final String clm ) {
	// 	return dataRole.getCondition( clm );
	// }

	/**
	 * このユーザーでアクセスされた画面オブジェクトを設定します。
	 *
	 * これは、画面アクセスの履歴(順番)を管理する機能に使います。
	 *
	 * @og.rev 5.2.3.0 (2010/12/01) 新規追加
	 *
	 * @param guiInfo 画面オブジェクト
	 */
	public void setAccessGui( final GUIInfo guiInfo ) {
		if( lastGuiInfo != null && guiInfo != null ) {
			lastGuiInfo.setNextGuiKey( guiInfo.getKey() );
		}
		lastGuiInfo = guiInfo ;		// 最後にアクセスした GUIInfo を設定
	}
}
