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

import org.opengion.fukurou.system.HybsConst ;			// 6.1.0.0 (2014/12/26)
import org.opengion.fukurou.util.ErrorMessage;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.model.NativeType;

/**
 * 一般的な半角文字列を扱う為の、カラム属性を定義します。
 *
 * 半角文字列とは、「 c &lt; 0x20 || c &gt; 0x7e 以外」でのみ
 * 構成された文字列のことです。
 *
 * タイプチェックとして、以下の条件を判定します。
 * ・文字列長は、Byte換算での文字数との比較
 * ・半角文字列チェック「 c &lt; 0x20 || c &gt; 0x7e 以外」エラー
 * ・文字パラメータの 正規表現チェック
 * ・クロスサイトスクリプティングチェック
 *
 * 8.0.0.1 (2021/10/08)
 * そして、要素に対して class 属性も設定できます。
 * 但し、class 属性が設定されている場合は、マッチチェックを実施しない
 *
 * @og.group データ属性
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public abstract class AbstractDBType implements DBType {
	private final String defValue ;		// データのデフォルト値

	/** システムの改行コードを設定します。*/
	protected static final String CR		 = HybsConst.CR;			// 6.1.0.0 (2014/12/26) refactoring
	/** StringBilderなどの初期値を設定します。	{@value} */
	protected static final int BUFFER_MIDDLE = HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) type 廃止
	 */
	public AbstractDBType() {
		this( "" );		// データのデフォルト値
	}

	/**
	 * コンストラクター
	 *
	 * 各サブクラスのタイプ値とデフォルト値を設定して、オブジェクトを構築します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31) type 廃止
	 *
	 * @param  defValue データのデフォルト値
	 */
	public AbstractDBType( final String defValue ) {
		this.defValue = defValue;
	}

	/**
	 * NATIVEの型の識別コードを返します。
	 *
	 * @og.rev 3.5.4.7 (2004/02/06) 新規作成
	 * @og.rev 4.1.1.2 (2008/02/28) Enum型(fukurou.model.NativeType)に変更
	 *
	 * @return  NATIVEの型の識別コード(DBType で規定)
	 * @og.rtnNotNull
	 * @see org.opengion.fukurou.model.NativeType
	 */
	public NativeType getNativeType() {
		return NativeType.STRING;
	}

	/**
	 * 半角スペースで固定長(半角換算の数)に変換した文字列を返します。
	 *
	 * 半角スペース埋めは、文字が半角、全角混在でもかまいません。
	 * なお、エラーチェックは行われません。
	 * 実行前に、必ず valueCheck( String value ,int len ) を行う必要があります。
	 *
	 * @og.rev 3.5.4.5 (2004/01/23) エンコード指定に変更します。
	 *
	 * @param   value    ＦＩＬＬ埋めする文字列
	 * @param   sizeX    整数部分の文字列の長さ
	 * @param   sizeY    小数部分の文字列の長さ
	 * @param   encode   固定長で変換する文字エンコード
	 *
	 * @return  ＦＩＬＬ埋めした新しい文字列
	 * @og.rtnNotNull
	 */
	public String valueFill( final String value ,final int sizeX ,final int sizeY,final String encode ) {
		final int len = (sizeY == 0) ? sizeX : sizeX + sizeY + 1;

		return StringUtil.stringFill( value,len,encode );
	}

	/**
	 * そのDBTypeの,デフォルトの値(物理的初期設定値)を返します。
	 *
	 * 一般に、文字列の場合は,ゼロストリング""  数字の場合は "0" です。
	 *
	 * @return  物理的初期設定値
	 */
	public String getDefault() {
		return defValue;
	}

	/**
	 * String引数の文字列を＋１した文字列を返します。
	 *
	 * これは、英字の場合(A,B,C など)は、B,C,D のように,最終桁の文字コードを
	 * ＋１ します。
	 * 文字列が数字タイプの場合は, 数字に変換して、+1 します。
	 * 最終桁が、"9","z","Z" および、その全角文字の場合、"0","a","Z" および、その全角文字に
	 * 変換後、ひとつ上の桁で、同様の ＋１ 操作を行います。
	 * 最も上位の桁が、これらの繰り上がり桁の場合は、すべての桁が初期化された状態に戻ります。
	 * 例：123 ⇒ 124 , ABC ⇒ ABD , 789 ⇒ 790 , XYZ ⇒ XXZ ,
	 *     ABC123 ⇒ ABC124 , AB99 ⇒ AC00 , 12ZZ99 ⇒ 13AA00 , ZZZZ ⇒ AAAA
	 * 引数が null の場合と、ゼロ文字列("")の場合は,物理的初期設定値(String getDefault())
	 * の値を返します。
	 *
	 * @og.rev 4.0.0.0 (2005/01/31)  Ａｄｄの方法を変更(汎用的なＡｄｄ)
	 *
	 * @param   value  String引数
	 *
	 * @return  引数の文字列を＋１した文字列。
	 */
	public String valueAdd( final String value ) {
		if( value == null || value.isEmpty() ) { return getDefault(); }

		char[] chs = value.toCharArray() ;

		for( int i=chs.length-1; i>=0; i-- ) {
			boolean over = true;
			switch( chs[i] ) {
				case '9'  : chs[i] = '0' ; break;
				case 'z'  : chs[i] = 'a' ; break;
				case 'Z'  : chs[i] = 'A' ; break;
				case '９' : chs[i] = '０'; break;
				case 'ｚ' : chs[i] = 'ａ'; break;
				case 'Ｚ' : chs[i] = 'Ａ'; break;
				default   : chs[i]++; over=false; break;
			}
			if( !over ) { break; }		// キャリーオーバーしていなければ、終了
		}

		return new String( chs );
	}

	/**
	 * String引数の文字列に、第２引数に指定の文字列(数字、日付等)を加算して返します。
	 *
	 * これは、valueAdd( String ) と本質的には同じ動きをしますが、任意の文字列を加算する
	 * ため、主として、数字系や日付系の DBType にのみ実装します。
	 * 実装がない場合は、UnsupportedOperationException を throw します。
	 *
	 * 第２引数 が、null の場合は、＋１ する valueAdd( String )が呼ばれます。
	 * これは、将来的には、valueAdd( String ) を無くすことを意味します。
	 *
	 * @og.rev 5.6.0.3 (2012/01/24) ADD に、引数の値を加算する機能を追加します。
	 *
	 * @param   value  String引数
	 * @param   add    加算する文字列(null の場合は、従来と同じ、+1 します。)
	 *
	 * @return  引数の文字列第２引数に指定の文字列(数字、日付等)を加算した文字列。
	 * @throws UnsupportedOperationException 実装が存在しない場合
	 */
	public String valueAdd( final String value,final String add ) {
		if( add == null || add.isEmpty()  ) { return valueAdd( value ); }

		final String errMsg = "このクラスでは、引数付の任意の加算は実装されていません。"
							+ getClass().getName() + " Action=[ADD]"
							+ " oldValue=[" + value + "] newValue=[" + add + "]" ;
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * エディターで編集されたデータを登録する場合に、データそのものを変換して、実登録データを作成します。
	 *
	 * 例えば,大文字のみのフィールドなら、大文字化します。
	 * 実登録データの作成は、DBType オブジェクトを利用しますので,
	 * これと CellEditor とがアンマッチの場合は、うまくデータ変換
	 * されない可能性がありますので、注意願います。
	 *
	 * @og.rev 3.3.3.0 (2003/07/09) 前後のスペースを取り除いておく。
	 * @og.rev 3.3.3.1 (2003/07/18) 後ろスペースを取り除く。(StringUtil#rTrim)
	 *
	 * @param	value	(一般に編集データとして登録されたデータ)
	 *
	 * @return  修正後の文字列(一般にデータベースに登録するデータ)
	 */
	public String valueSet( final String value ) {
		return StringUtil.rTrim( value );
	}

	/**
	 * action で指定されたコマンドを実行して、値の変換を行います。
	 *
	 * oldValue(旧データ)は、元のDBTableModelに設定されていた値です。通常は、
	 * この値を使用してカラム毎に変換を行います。newValue(新データ)は、引数で
	 * 指定された新しい値です。この値には、パラメータを指定して変換方法を
	 * 制御することも可能です。
	 * 指定のアクションがカラムで処理できない場合は、エラーになります。
	 *
	 * @param   action アクションコマンド
	 * @param   oldValue 入力データ(旧データ)
	 * @param   newValue 入力データ(新データ)
	 *
	 * @return	実行後のデータ
	 */
	public String valueAction( final String action,final String oldValue,final String newValue ) {
		final String errMsg = "このクラスでは、このアクションは実装されていません。"
							+ getClass().getName() + " Action=[" + action + "]"
							+ " oldValue=[" + oldValue + "] newValue=[" + newValue + "]" ;
		throw new UnsupportedOperationException( errMsg );
	}

	/**
	 * データが登録可能かどうか[true/false]をチェックします。
	 *
	 * データがエラーの場合は、そのエラー内容を返します。
	 *
	 * @og.rev 2.1.1.1 (2002/11/15) HTMLタグチェックのメソッドの共有化。
	 * @og.rev 3.0.1.3 (2003/03/11) DBTypeCheckUtilクラスを利用するように修正
	 * @og.rev 3.6.0.0 (2004/09/22) dbType パラメータを引数に追加
	 * @og.rev 5.2.2.0 (2010/11/01) 厳密にチェック(isStrict=true)するフラグを追加
	 * @og.rev 8.0.0.1 (2021/10/08) dbType パラメータのclass属性対応
	 *
	 * @param   key   キー
	 * @param   value 値
	 * @param   sizeX 整数部分の文字列の長さ
	 * @param   sizeY 小数部分の文字列の長さ
	 * @param   typeParam  dbType パラメータ
	 * @param   isStrict   厳密にチェックするかどうか [true:する/false:標準的]
	 *
	 * @return  エラー内容
	 */
	public ErrorMessage valueCheck( final String key ,final String value ,
									final int sizeX ,final int sizeY ,final String typeParam ,final boolean isStrict) {

		ErrorMessage msg = new ErrorMessage();
		if( value == null || value.isEmpty() ) { return msg; }

		final int len = (sizeY == 0) ? sizeX : sizeX + sizeY + 1;
		if( len < value.length() ) {
			// 文字列の長さが指定の長さよりも長いです。
			msg.addMessage( 0,ErrorMessage.NG,"ERR0006",key,value,
									String.valueOf( value.length() ),String.valueOf( len ) );
		}

		final StringBuilder val = new StringBuilder( BUFFER_MIDDLE );
		boolean isError = false;
		for( int i=0; i<value.length(); i++ ) {
			final char ch = value.charAt( i );
			if( ch < 0x20 || ch > 0x7e ) {
				val.append( "<span class=\"NG\">" ).append( ch ).append( "</span>" );
				isError = true;
			}
			else {
				val.append( ch );
			}
		}
		if( isError ) {
			// 指定の文字以外の文字が使われています。
			msg.addMessage( 0,ErrorMessage.NG,"ERR0009", key,val.toString() );
		}

		// 3.6.0.0 (2004/09/22) dbType パラメータを使用したマッチチェック
		if( typeParam != null ) {
			// class属性はマッチチェックを実施しない 8.0.0.1 (2021/10/08)
			String newTypePram = typeParam;
			final int st = typeParam.indexOf( "class=" );
			if( st >= 0 ) {
				final int ed = newTypePram.indexOf( ';' , st );
				// 文字パラメータの文字列から class属性の文字列を消す
				if( ed >= 0 ) {
					// [0-9]|class=sel2sel;[A-z] の場合、[0-9]|[A-z] を取得
					newTypePram = newTypePram.replaceAll( newTypePram.substring( st , ed + 1 ) , "" );
				}
				else {
					// [0-9A-z]class=sel2sel の場合、[0-9A-z] を取得
					newTypePram = newTypePram.substring( 0 , st );
				}
			}

//			final String check = DBTypeCheckUtil.matcheCheck( value,typeParam );	// 8.0.0.1 (2021/10/08)
			final String check = DBTypeCheckUtil.matcheCheck( value,newTypePram );
			if( check != null ) {
				// 指定の文字以外の文字が使われています。
				msg.addMessage( 0,ErrorMessage.NG,"ERR0009", key,check );
			}
		}

		// クロスサイトスクリプティング対策：'<', '>' は登録させない。
		msg = xssCheck( key ,value, msg );
		return msg;
	}

	/**
	 * HTMLタグかどうかをチェックします。
	 *
	 * クロスサイトスクリプティング対策として、'&lt;', '&gt;' は登録させない。
	 *
	 * @og.rev 2.1.1.1 (2002/11/15) HTMLタグチェックのメソッドの共有化。
	 * @og.rev 6.2.0.0 (2015/02/27) ERR0010 の引数が、変更されているので、修正します。
	 *
	 * @param   key		タグのキー
	 * @param   value	対象の値
	 * @param   msg		ErrorMessageオブジェクト
	 *
	 * @return  エラー内容(エラーを追加した、ErrorMessageオブジェクト)
	 */
	protected ErrorMessage xssCheck( final String key ,final String value, final ErrorMessage msg ) {
		final StringBuilder val = new StringBuilder( BUFFER_MIDDLE );
		boolean isError = false;
		for( int i=0; i<value.length(); i++ ) {
			final char ch = value.charAt( i );
			if( ch == '<' || ch == '>' ) {
				val.append( "<span class=\"NG\">" ).append( ch ).append( "</span>" );
				isError = true;
			}
			else {
				val.append( ch );
			}
		}
		if( isError ) {
			// 6.2.0.0 (2015/02/27) ERR0010:HTMLタグは登録できません。key={0} value={1}({2}) char={3}
			// {2} が何を想定していたのか不明ですが、エラー発生元のクラス名を設定しておきます。
			final String clsName = this.getClass().getSimpleName();

			// HTMLタグは登録できません。
			msg.addMessage( 0,ErrorMessage.NG,"ERR0010", key,val.toString(),clsName,"<,>" );
		}
		return msg;
	}
}
