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

import java.awt.Color;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.StringTokenizer;
import java.nio.charset.Charset;		// 5.5.2.6 (2012/05/25)


/**
 * StringUtil.java は、共通的に使用される String関連メソッドを集約した、クラスです。
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class StringUtil {

	/** バッファの初期容量を通常より多い目に設定します。(200)  */
	private static final int BUFFER_MIDDLE = 200;

	/** システム依存の改行記号をセットします。	*/
	private static final String CR = System.getProperty("line.separator");

	/**
	 * プラットフォーム依存のデフォルトの Charset です。
	 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
	 *
	 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
	 */
	public static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;

	/**
	 *	デフォルトコンストラクターをprivateにして、
	 *	オブジェクトの生成をさせないようにする。
	 *
	 */
	private StringUtil() {}

	/**
	 * UTF-8 で、URLエンコードを行います。
	 * このメソッドは、JDK1.4 以上でないと使用できません。
	 *
	 * @param	value エンコードする文字列
	 *
	 * @return	 指定の文字コードでＵＲＬエンコードされた文字列
	 */
	public static String urlEncode( final String value ) {
		if( value == null ) { return ""; }

		try {
			return URLEncoder.encode( value,"UTF-8" );
		}
		catch( UnsupportedEncodingException ex ) {
			String errMsg = "UnsupportedEncodingException [UTF-8]" + CR
						+ ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		catch( RuntimeException ex2 ) {		// 3.6.0.0 (2004/09/17)
			String errMsg = "予期せぬエラー value=[" + value + "] , encode=[UTF-8]" + CR
						+ ex2.getMessage();
			throw new RuntimeException( errMsg,ex2 );
		}
	}

	/**
	 * UTF-8 でURLエンコードされた文字列をデコードします。
	 * このメソッドは、JDK1.4 以上でないと使用できません。
	 *
	 * @og.rev 5.4.5.0 追加
	 * @param	value デコードする文字列
	 *
	 * @return	 デコードされた文字列
	 */
	public static String urlDecode( final String value ) {
		try {
			return URLDecoder.decode( value,"UTF-8" );
		}
		catch( UnsupportedEncodingException ex ) {
			String errMsg = "UnsupportedEncodingException [UTF-8]" + CR
						+ ex.getMessage() ;
			throw new RuntimeException( errMsg,ex );
		}
		catch( RuntimeException ex2 ) {		// 3.6.0.0 (2004/09/17)
			String errMsg = "予期せぬエラー value=[" + value + "] , encode=[UTF-8]" + CR
						+ ex2.getMessage();
			throw new RuntimeException( errMsg,ex2 );
		}
	}

	/**
	 * 文字列の後ろのスペースを削除します。
	 * String クラスの trim()メソッドは、文字列の両方のスペースを削除しますが、
	 * この rTrim( String ) は、後ろの半角スペースのみ、詰めます。
	 * 注意：'\u0020' (スペース文字) より小さい文字を切り取ります。
	 *
	 * @param	str 元の文字列
	 *
	 * @return	後ろの半角スペースを詰めた、新しい文字列
	 */
	public static String rTrim( final String str ) {
		if( str == null )  { return null; }
		int count = str.length();

		int len = count;

		while ((0 < len) && (str.charAt(len-1) <= ' ')) {
			len--;
		}
		return (len < count) ? str.substring(0, len) : str;
	}

	/**
	 * 文字列の後ろから、" .0" の文字を削除した数字型文字列を返します。
	 * 数字型文字列は、入力文字列の後ろの スペース、小数点、ゼロを削除します。
	 * また、先頭が、"." で始まる場合は、"0" を追加します。
	 * 例: "123.00" ⇒ "123" , ".123" ⇒ "0.123"
	 *
	 * @og.rev 3.8.8.1 (2007/01/10) 新規作成
	 *
	 * @param	str 元の文字列
	 *
	 * @return	数字文字列化された、新しい文字列
	 */
	public static String toNumber( final String str ) {
		if( str == null )  { return null; }

		String rtn = str.trim() ;

		int adrs = rtn.indexOf( '.' );
		int count = rtn.length();
		int len = count;

		if( adrs >= 0 ) {
			while ((adrs < len) && ( ".0".indexOf( rtn.charAt(len-1) ) >= 0 )) {
				len--;
			}
		}

		if( len < count ) { rtn = rtn.substring(0, len); }
		if( adrs == 0 ) { rtn = "0" + rtn; }

		return rtn ;
	}

	/**
	 * 文字列の前方のゼロ(０)を削除します。
	 * 先頭の０を削除するまえに、trim して、スペースを削除しておきます。
	 *
	 * @og.rev 3.5.4.5 (2004/01/23) 新規追加
	 *
	 * @param	in 元の文字列
	 *
	 * @return	前方のゼロ(０)を削除した、新しい文字列
	 */
	public static String lTrim0( final String in ) {
		if( in == null )  { return null; }
		String str = in.trim();
		int count = str.length();

		int len = 0;

		while ((count > len) && (str.charAt(len) == '0')) {
			len++;
		}

		if( len == 0 ) { return str; }				// 先頭がゼロでない。
		else if( len == count ) { return "0"; }		// すべてがゼロ
		else if( str.charAt(len) == '.' ) { return "0" + str.substring(len); }
		else { return str.substring(len); }
	}

	/**
	 * 文字列配列の各要素の後ろのスペースを削除します。
	 * 個々の配列要素に対して、rTrim( String str ) を適用します。
	 * 元の文字列配列に直接作用するのではなく、新しい文字列配列に
	 * 結果をコピーして返します。
	 * ただし、元の文字列配列が、null か、length == 0 の場合は、
	 * 元の文字列配列(アドレス)を返します。
	 * 注意：'\u0020' (スペース文字) より小さい文字を切り取ります。
	 *
	 * @param	str 元の文字列
	 *
	 * @return	後ろの半角スペースを詰めた、新しい文字列
	 */
	public static String[] rTrims( final String[] str ) {
		if( str == null || str.length == 0 ) { return str; }

		String[] rtn = new String[ str.length ];
		for( int i=0; i<str.length; i++ ) {
			rtn[i] = rTrim( str[i] );
		}
		return rtn ;
	}

	/**
	 * 文字列の前後のダブルクオートを取り外します。
	 * 前後にダブルクオートが入っていなければ、そのままの文字列を返します。
	 * 前後に入っていない(片方のみなど)場合も、そのままの文字列を返します。
	 *
	 * @param	str 元の文字列
	 *
	 * @return	ダブルクオートを取り外した新しい文字列
	 */
	public static String csvOutQuote( final String str ) {
		if( str == null )  { return null; }
		int end = str.length();

		if( end < 2 || str.charAt(0) != '"' || str.charAt( end-1 ) != '"' ) {
			return str;
		}

		return str.substring( 1,end-1 ) ;
	}

	/**
	 * 内部で使われる byte[] から String 生成 メソッド
	 *
	 * @param	byteValue	 変換するバイト列
	 * @param	start		 変換開始アドレス
	 * @param	length		 変換バイト数
	 * @param	encode		 変換する文字エンコード
	 *
	 * @return	変換後文字列
	 */
	public static String makeString( final byte[] byteValue, final int start, final int length,final String encode ) {

		if( encode.startsWith( "Unicode" ) ) {
			String errMsg = "Unicode文字列は、変換できません。[" + encode + "]"  + CR;
			throw new RuntimeException( errMsg );
		}

		String rtn = null;
		if( byteValue != null ) {
			try {
				// encode コードで変換されている byte[] を、String に変換。
				rtn = new String( byteValue,start,length,encode );
			} catch( UnsupportedEncodingException ex ) {	  // 変換コードが存在しないエラー
				String errMsg = "文字変換コードが存在しません。[" + encode + "]" + CR
							+ ex.getMessage() ;
				throw new RuntimeException( errMsg,ex );
			}
		}
		return rtn;
	}

	/**
	 * 指定の文字列をバイトコードに変換します。
	 * 引数の文字列が null の場合は、return は、byte[0] を返します。
	 *
	 * @param	value	 変換するストリング値
	 * @param	encode	 変換する文字エンコード
	 *
	 * @return	変換後文字列
	 */
	public static byte[] makeByte( final String value,final String encode ) {
		byte[] rtnByte = new byte[0];
		if( value != null ) {
			try {
				rtnByte = value.getBytes( encode );		// byte[] に encode コードで変換。
			} catch( UnsupportedEncodingException ex ) {	  // 変換コードが存在しないエラー
				String errMsg = "文字変換コードが存在しません。[" + encode + "]" + CR
							+ ex.getMessage();
				throw new RuntimeException( errMsg,ex );
			}
		}
		return rtnByte;
	}

	/**
	 * 半角スペースで固定長(半角換算の数)に変換した文字列を返します。
	 * 半角スペース埋めは、文字が半角、全角混在でもかまいません。
	 * 内部にセットした文字列は、変化しません。
	 *
	 * @param	str 	 Fill埋めする文字列
	 * @param	su_fill  Fill埋めする文字列の長さ。(半角換算の数)
	 *
	 * @return	Fill埋めした新しいStringを返す。
	 */
	public static String stringXFill( final String str,final int su_fill ) {
		char[] charValue ;

		if( str == null ) { charValue = new char[0]; }
		else              { charValue = str.toCharArray(); }
		int len = charValue.length;

		if( su_fill < len ) {
			String errMsg = "元の文字数がフォームより長いです。(数字が壊れます。)"
					+ "su_fill[" + su_fill + "], len[" + len + "]" + CR
					+ "input=[" + str + "]" + CR;
			throw new RuntimeException( errMsg );
		}

		char[] charbuf = new char[ su_fill ];			// 移す char 配列を新規作成
		Arrays.fill( charbuf,' ' );
		System.arraycopy( charValue,0,charbuf,0,len );

		return new String( charbuf );		 // コピーした配列全てを文字列に変換
	}

	/**
	 * 半角スペースで固定長(半角換算の数)に変換した文字列を返します。
	 * 半角スペース埋めは、文字が半角、全角混在でもかまいません。
	 * 内部にセットした文字列は、変化しません。
	 *
	 * @param	str 	 Fill埋めする文字列
	 * @param	su_fill  Fill埋めする文字列の長さ。(半角換算の数)
	 * @param	encode   Fill埋めする文字列の文字エンコード
	 *
	 * @return	Fill埋めした新しいStringを返す。
	 */
	public static String stringFill( final String str,final int su_fill,final String encode ) {
		if( su_fill < 0 ) {
			String errMsg = "指定文字数が負です。[" + su_fill + "]";
			throw new RuntimeException( errMsg );
		}

		byte[] byteValue = makeByte( str,encode );
		int len = byteValue.length;

		// 内部文字列が指定長より長い場合
		if( len >= su_fill ) {
			return makeString( byteValue,0,su_fill,encode );
		}
		else {
			byte[] space = makeByte( " ",encode );
			int spaceLen = space.length ;
			if( spaceLen == 4 ) {	// encode が、UnicodeLittle の場合の特殊処理
				space[0] = space[2];
				space[1] = space[3];
				spaceLen = 2;
			}
			byte[] bytebuf = new byte[ su_fill ];
			for( int i=0; i<len; i++ ) { bytebuf[i] = byteValue[i]; }

			int k = 0;
			for( int j=len; j<su_fill; j++ ) {		// 余った部分は、スペース埋め
				if( k >= spaceLen ) { k = 0; }
				bytebuf[j] = space[k++];
			}
			return makeString( bytebuf,0,su_fill,encode ); 	// 新たに、すべての長さの部分文字列を作成する。
		}
	}

	/**
	 * 整数のフォーム( 12 で、整数部 １２桁を表す)に合った新しい文字列を作り、それを返します。
	 * 実行できるのは、整数の String に対してのみです。
	 * 内部にセットした文字列は、変化しません。
	 *
	 *		String str = StringUtil.intFill( "123",10 );
	 *
	 *		実行結果："0000000123"
	 *
	 * @param	str	整数の String
	 * @param	su_fill	フォームを表す数字 ( 12 で、整数部 １２桁を表す)
	 *
	 * @return	整数のフォームに合った文字列
	 */
	public static String intFill( final String str,final int su_fill ) {
		if( su_fill < 0 ) {
			String errMsg = "指定文字数が負です。[" + su_fill + "]";
			throw new RuntimeException( errMsg );
		}

		char[] charbuf = new char[ su_fill ];			// 移す char 配列を新規作成
		Arrays.fill( charbuf,'0' );

		if( str == null ) { return new String( charbuf ); }

		char[] charValue = str.toCharArray();
		int len = charValue.length;

		if( su_fill < len ) {
			String errMsg = "元の文字数がフォームより長いです。(数字が壊れます。) su_fill[" + su_fill + "], len[" + len + "]";
			throw new RuntimeException( errMsg );
		}

		System.arraycopy( charValue,0,charbuf,su_fill-len,len );

		return new String( charbuf );		 // コピーした配列全てを文字列に変換
	}

	/**
	 * 全角スペースで固定長(半角換算の数)に変換した文字列を返します。
	 *
	 * @param	str      Fill埋めする文字列
	 * @param	su_fill  Fill埋めする文字列の長さ。(半角換算の数)
	 * @param	encode   Fill埋めする文字列の文字エンコード
	 *
	 * @return	全角スペースでFill埋めした新しいStringを返す。
	 */
	public static String stringKFill( final String str,final int su_fill,final String encode ) {
		if( su_fill < 0 ) {
			String errMsg = "指定文字数が負です。[" + su_fill + "]";
			throw new RuntimeException( errMsg );
		}

		byte[] byteValue = makeByte( str,encode );
		int len = byteValue.length;

		// 内部文字列が指定長より長い場合
		if( len >= su_fill ) {
			return makeString( byteValue,0,su_fill,encode );
		}
		else {
			byte[] space = makeByte( "　",encode );
			int spaceLen = space.length ;
			byte[] bytebuf = new byte[ su_fill ];
			for( int i=0; i<len; i++ ) { bytebuf[i] = byteValue[i]; }
			int k = 0;
			for( int j=len; j<su_fill; j++ ) {		// 余った部分は、スペース埋め
				if( k >= spaceLen ) { k = 0; }
				bytebuf[j] = space[k++];
			}
			return makeString( bytebuf,0,su_fill,encode ); 	// 新たに、すべての長さの部分文字列を作成する。
		}
	}

	/**
	 * 小数点のフォームに合った新しい文字列を作り、文字列を返します。
	 * 現在は、小数点が頭に付いたり、最後に付く場合の対応はしていません。
	 * フォームは、12.4 で、 000000000010.1000 という形で、ピリオドを含みます。
	 *
	 *  // 半角 整数部 10 桁 小数部 ５桁で固定長の文字を得る。
	 *  String str = StringUtil.realFill( "123.45" ,10.5 ) ;
	 *
	 *  実行結果：0000000123.45000
	 *
	 * @param	str		整数の String
	 * @param	su_fill	フォームを表す実数	( 12.4 で、整数部 １２桁、小数部 ４桁 計１７桁 )
	 *
	 * @return	value	小数点のフォーム文字列
	 */
	public static String realFill( final String str,final double su_fill ) {
		if( su_fill < 0 ) {
			String errMsg = "指定文字数が負です。[" + su_fill + "]";
			throw new RuntimeException( errMsg );
		}

		int su_seisu = (int)(su_fill); 						   // 指定のフォームの整数部を取り出す。
		int su_shosu = (int)(su_fill*10 - su_seisu*10);		   // 小数部を取り出しす。
		char[] charbuf = new char[ su_seisu + su_shosu + 1 ];  // 移す char 配列
		Arrays.fill( charbuf,'0' );

		if( str == null ) {
			charbuf[su_seisu] = '.' ;
			return new String( charbuf );
		}

		char[] charValue = str.toCharArray();
		int len = charValue.length;

		// 検査する文字列の加工(検査文字列は、インデックスの値とバイト数で文字数を求める。)
		// 小数点の位置を求める。 本当は、String クラスの indexOf で求めず、byte[] で検索すべきである。
		int valueindex = str.indexOf( '.' );
		if( valueindex < 0 ) {									// valueform 自体が、合っていない。
			String errMsg = "元の文字列に小数点が、含まれません。";
			throw new RuntimeException( errMsg );
		}
		int su_valueseisu = valueindex; 				 // 整数部の文字数は、小数点の位置と同じ
		int su_valueshosu = len - valueindex - 1 ;		// 小数部の文字数は、全文字数－整数文字数－１

		// フォームの整数文字数 ー 加工文字の整数文字部 ＝ 転送先配列位置
		int to_index = su_seisu - su_valueseisu;
		if( to_index < 0 ) {
			String errMsg = "元の数字が、フォームより長いです。(数字が壊れます。) form[" + su_fill + "]";
			throw new RuntimeException( errMsg );
		}
		int end_index;
		// 転送先配列終了位置は、お互いの小数部の文字数により、短い方を選ぶ。
		if( su_shosu < su_valueshosu ) { end_index = su_seisu + su_shosu + 1; }
		else						   { end_index = su_seisu + su_valueshosu + 1; }

		int from_index = 0;
		while( to_index < end_index ) {
			charbuf[to_index++] = charValue[from_index++];	   // 転送(移し替え)
		}
		return new String( charbuf );		 // コピーした配列全てを文字列に変換
	}

	/**
	 * ストリングの部分文字列を,別の文字列に置換えたストリングを返します。
	 * 例えば,リターンコードを&lt; br /&gt;に置換えて,画面上に改行表示させるが可能です。
	 *
	 * @og.rev 5.0.0.1 (2009/08/15) 不要なオブジェクトの生成を抑制する。
	 *
	 * @param	target 元の文字列
	 * @param	from   置換元部分文字列
	 * @param	to	   置換先部分文字列
	 *
	 * @return	置換えた文字列
	 */
	public static String replace( final String target,final String from,final String to ) {
//		if( target == null || from == null || to == null ) { return target; }
		if( target == null || from == null || to == null || target.indexOf( from ) < 0 ) { return target; }

//		StringBuilder strBuf = new StringBuilder( BUFFER_MIDDLE );
		StringBuilder strBuf = new StringBuilder( target.length() );

		int start = 0;
		int end   = target.indexOf( from,start );
		while( end >= 0 ) {
			strBuf.append( target.substring( start,end ) );
			strBuf.append( to );
			start = end + from.length();
			end   = target.indexOf( from,start );
		}

		if( start > 0 ) {
			strBuf.append( target.substring( start ) );
			return strBuf.toString();
		}
		else {
			return target;			// 3.4.0.2 (2003/09/05)
		}
	}

	/**
	 * String型の配列から、カンマ(,)で連結されたString を作成します。
	 * これは，配列を表示用に変換する為のものです。
	 * array2line( array, ",", 0 ); と同等です。
	 *
	 * @param	array		元の文字列配列
	 *
	 * @return	一列に変換した文字列(引数がnullの場合は、長さ０の文字列を返す)
	 */
	public static String array2csv( final String[] array ) {
		return array2line( array, ",", 0 );
	}

	/**
	 * String型の配列から、セパレーターで連結されたString を作成します。
	 * これは，配列を表示用に変換する為のものです。
	 *
	 * @param	array		元の文字列配列
	 * @param	separator	区切り記号
	 *
	 * @return	一列に変換した文字列(引数がnullの場合は、長さ０の文字列を返す)
	 */
	public static String array2line( final String[] array,final String separator ) {
		return array2line( array, separator,0 );
	}

	/**
	 * String型の配列から、セパレーターで連結されたString を作成します。
	 * これは，配列を表示用に変換する為のものです。
	 *
	 * @param	array		元の文字列配列
	 * @param	separator	区切り記号
	 * @param	start		配列の連結開始アドレス
	 *
	 * @return	一列に変換した文字列(引数がnullの場合は、長さ０の文字列を返す)
	 */
	public static String array2line( final String[] array,final String separator,final int start ) {
		if( array == null || array.length <= start ) { return ""; }

		StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );

		rtn.append( valueOf( array[start] ) );
		for(int i=start+1; i < array.length; i++) {
			rtn.append( separator );
			rtn.append( valueOf( array[i] ) );
		}
		return rtn.toString();
	}

	/**
	 * Enumerationから、オブジェクト配列データを返します。
	 * これは，Enumerationを表示用に変換する為のものです。
	 *
	 * @param	enume   元のEnumeration
	 *
	 * @return	オブジェクト配列
	 */
	public static Object[] enume2Array( final Enumeration<?> enume ) {		// 4.3.3.6 (2008/11/15) Generics警告対応
		if( enume == null || ! enume.hasMoreElements() ) { return new Object[0]; }

		ArrayList<Object> obj = new ArrayList<Object>();

		while( enume.hasMoreElements() ) {
			obj.add( enume.nextElement() );
		}
		return obj.toArray();
	}

	/**
	 * Enumerationから、オブジェクト配列データを返します。
	 * これは，Enumerationを表示用に変換する為のものです。
	 *
	 * @param	enume   元のEnumeration
	 * @param	objs - 配列が十分な大きさを持つ場合は、Vector の要素が格納される配列。
	 *			そうでない場合は、要素を格納するために同じ実行時の型の新しい配列が割り当てられる
	 * @return	オブジェクト配列
	 */
	public static Object[] enume2Array( final Enumeration<?> enume,final Object[] objs ) {	// 4.3.3.6 (2008/11/15) Generics警告対応
		if( enume == null || ! enume.hasMoreElements() ) { return objs ; }

		ArrayList<Object> list = new ArrayList<Object>();

		while( enume.hasMoreElements() ) {
			list.add( enume.nextElement() );
		}
		return list.toArray( objs );
	}

	/**
	 * Iteratorから、セパレーターで連結されたString を作成します。
	 * これは，Enumerationを表示用に変換する為のものです。
	 *
	 * @param	ite 		元のIterator
	 * @param	separator	区切り記号
	 *
	 * @return	一列に変換した文字列
	 */
	public static String iterator2line( final Iterator<?> ite,final String separator ) {
		if( ite == null || ! ite.hasNext() ) { return ""; }

		StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );

		rtn.append( valueOf( ite.next() ) );
		while( ite.hasNext() ) {
			rtn.append( separator );
			rtn.append( valueOf( ite.next() ) );
		}
		return rtn.toString();
	}

	/**
	 * カンマ(,)で連結された String を、配列に分解して、その値を返します。
	 * これは，たとえば、AAA,BBB,CCC などのリソースデータを受けてから配列に入れ直して、
	 * メニューなりリストを作成するのに便利です。
	 * 要素が空の場合は、必ずカンマの間にスペースを入れて記述してください。
	 * 分割後の文字列の前後のスペースは、削除されます。
	 *
	 * @param	csvData 	元のデータ
	 *
	 * @return	文字列配列(引数がnull、ゼロ文字列の場合は、サイズ0の配列を返す)
	 */
	public static String[] csv2Array( final String csvData ) {
		return csv2Array( csvData, ',', 0 );
	}

	/**
	 * 区切り文字で連結された String を、配列に分解して、その値を返します。
	 * これは，たとえば、AAA,BBB,CCC などのリソースデータを受けてから配列に入れ直して、
	 * メニューなりリストを作成するのに便利です。
	 * 連続した区切り文字は、１文字に分割します。
	 * 分割後の文字列の前後のスペースは、削除されます。
	 *
	 * @param	csvData 	元のデータ
	 * @param	separator	区切り文字
	 *
	 * @return	文字列配列(引数がnull、ゼロ文字列の場合は、サイズ0の配列を返す)
	 */
	public static String[] csv2Array( final String csvData,final char separator ) {
		return csv2Array( csvData,separator,0 );
	}

	/**
	 * 区切り文字で連結された String を、配列に分解して、その値を返します。
	 * これは，たとえば、AAA,BBB,CCC などのリソースデータを受けてから配列に入れ直して、
	 * メニューなりリストを作成するのに便利です。
	 * 連続した区切り文字は、１文字に分割します。
	 * 分割後の文字列の前後のスペースは、削除されます。
	 * 第３の引数は、リターンする配列の個数を指定します。ただし、第一引数がNULLや、ゼロ文字列
	 * などの不正な情報の場合は、通常と同じく 長さゼロの配列を返します。
	 * len=0 を指定すると分解したデータの個数分の配列を作成します。指定の長さが短い場合は、
	 * そこまで分のみ取り込みます。指定の長さが長い場合は、余分に配列を作成します。
	 * セットされる値は、"" です。
	 *
	 * @og.rev 3.8.5.1 (2006/05/08) 設定配列の数を指定できるように変更
	 * @og.rev 3.8.8.2 (2007/01/26) 分割後の値の前後のスペースは削除します。
	 *
	 * @param	csvData 	元のデータ
	 * @param	separator	区切り文字
	 * @param	len			指定の長さの配列で返します。
	 *
	 * @return	文字列配列(引数がnull、ゼロ文字列の場合は、サイズ0の配列を返す)
	 */
	public static String[] csv2Array( final String csvData,final char separator, final int len ) {
		if( csvData == null || csvData.length() == 0 ) { return new String[0] ; }

		CSVTokenizer token = new CSVTokenizer( csvData,separator );

		int count = (len > 0 ) ? len : token.countTokens() ;
		String[] rtn = new String[ count ];
		int i = 0;
		for( ; i<count && token.hasMoreTokens() ; i++ ) {
			rtn[i] = (token.nextToken()).trim();	// 3.8.8.2 (2007/01/26)
		}
		for( ; i<count; i++ ) {
			rtn[i] = "" ;
		}

		return rtn;
	}

	/**
	 * 区切り文字で連結された String を、配列に分解して、その値を返します。
	 * これは，たとえば、AAA,BBB,CCC などのリソースデータを受けてから配列に入れ直して、
	 * メニューなりリストを作成するのに便利です。
	 * csv2Array と異なり、連続した区切り文字は、分割せずにトークンのみ切り出します。
	 * トークンは、カンマ(,)のみで区切り、その後 trim() により
	 * 前後のスペースを削除します。
	 *
	 * @param	csvData 	元のデータ
	 *
	 * @return	文字列配列
	 */
	public static String[] csv2ArrayOnly( final String csvData ) {
		if( csvData == null || csvData.length() == 0 ) { return new String[0] ; }

		StringTokenizer token = new StringTokenizer( csvData,"," );

		ArrayList<String> list = new ArrayList<String>();
		while( token.hasMoreTokens() ) {
			String temp = (token.nextToken()).trim();
			if( temp.length() > 0 ) { list.add( temp ); }
		}

		return list.toArray( new String[list.size()] );
	}

	/**
	 * カンマ(,)、ハイフン(-)で連結された String を、配列に分解して、その値を返す処理のスペシャル版です。
	 * 0,1,3,5-8,10-* などの数字文字列から、必要な数字をピックアップした文字配列を返します。
	 * 引数の maxNo は、"*" が指定された場合の、最大の数値です。
	 * よって、"*" は、単独(１文字)では、0-maxNo を表し、N-* では、N-maxNo を意味します。
	 * カンマ区切りで指定される値は、基本的に数字で、重複(1,1,2,2)、逆転(3,2,1)で指定できます。
	 * 5-3 と指定した場合は、5,4,3 に分解されます。逆順に登録されます。
	 * また、一文字だけの場合は、アルファベット(a-z,A-Zなど)も指定する事が可能です。
	 * アルファベットの場合は、"*" は指定できません。
	 * 重複削除、昇順並べ替え等が、必要な場合は、取得後の配列を操作してください。
	 *
	 * @og.rev 5.5.7.2 (2012/10/09) 新規追加
	 *
	 * @param	csvData 0,1,3,5-8,10-* などのCSV-ハイフン文字列
	 * @param	maxNo "*" が指定された場合の、最大数
	 * @return	文字列配列(引数がnull、ゼロ文字列の場合は、サイズ0の配列を返す)
	 */
	public static String[] csv2ArrayExt( final String csvData , final int maxNo )  {
		if( csvData == null || csvData.length() == 0 ) { return new String[0] ; }

		String strData = csvData.replace( "-*" , "-" + maxNo );		// まず、N-* 形式を、N-maxNo に変換します。
		strData        = strData.replace( "*"  , "0-" + maxNo );	// その後、"*" 単独(１文字)を、0-maxNo に変換します。

		ArrayList<String> noList = new ArrayList<String>();

		String[] nos = strData.split( "," );		// カンマで分解。N , N-M , N-* のどれか
		int lastNo = 0;								// 現在の番号
		for( int i=0; i<nos.length; i++ ) {
			String sno = nos[i] ;
			int hai = sno.indexOf( '-' );
			// ハイフンが含まれているときは前後に分解して、間を埋める
			if( hai > 0 ) {
				String st1 = sno.substring( 0,hai );	// 先頭からハイフンまで
				String st2 = sno.substring( hai+1 );	// ハイフンから最後まで
				if( st1.length() == 1 &&  st2.length() == 1 ) {		// ともに１文字の場合は、char化して処理。(英数字処理)
					char ch1 = st1.charAt(0);
					char ch2 = st2.charAt(0);
					if( ch1 < ch2 ) { while( ch1 <= ch2 ) { noList.add( String.valueOf(ch1++) ); } }
					else			{ while( ch1 >= ch2 ) { noList.add( String.valueOf(ch1--) ); } }
				}
				else {
					int ch1 = Integer.parseInt( st1 );
					int ch2 = Integer.parseInt( st2 );
					if( ch1 < ch2 ) { while( ch1 <= ch2 ) { noList.add( String.valueOf(ch1++) ); } }
					else			{ while( ch1 >= ch2 ) { noList.add( String.valueOf(ch1--) ); } }
				}
			}
			else {
				noList.add( String.valueOf(sno) );
			}
		}
		return noList.toArray( new String[noList.size()] ) ;
	}

	/**
	 * Object 引数の文字列表現を返します。
	 * これは，String.valueOf とほぼ同じ動作をしますが、引数が null の場合に、
	 * "null" という文字列を返すのではなく、なにもない文字列 "" を返します。
	 *
	 * @param	obj    文字列表現すべき元のオブジェクト
	 *
	 * @return	引数が null の場合は、"" に等しい文字列。そうでない場合は、obj.toString() の値
	 */
	public static String valueOf( final Object obj ) {
		if( obj == null ) { return "";			   }
		else			  { return obj.toString(); }
	}

	/**
	 * HTML上のエスケープ文字を変換します。
	 *
	 * HTMLで表示する場合にきちんとエスケープ文字に変換しておかないと
	 * Script を実行されたり、不要なHTMLコマンドを潜り込まされたりするため、
	 * セキュリティーホールになる可能性があるので、注意してください。
	 *
	 * @param	input HTMLエスケープ前の文字列
	 *
	 * @return	エスケープ文字に変換後の文字列
	 */
	public static String htmlFilter( final String input ) {
		if( input == null || input.length() == 0 ) { return ""; }
		StringBuilder rtn = new StringBuilder( BUFFER_MIDDLE );
		char ch;
		for(int i=0; i<input.length(); i++) {
			ch = input.charAt(i);
			switch( ch ) {
				case '<'  : rtn.append("&lt;");   break;
				case '>'  : rtn.append("&gt;");   break;
				case '"'  : rtn.append("&quot;"); break;
				case '\'' : rtn.append("&#39;");  break;
				case '&'  : rtn.append("&amp;");  break;
				default   : rtn.append(ch);
			}
		}
		return( rtn.toString() );
	}

	/**
	 * JavaScript 等の引数でのクオート文字をASCII変換します。
	 *
	 * JavaScript の引数の値に、ダブルクオート(")、シングルクオート(')が
	 * 含まれると、文字列を表す為に前後に指定しているクオートと混乱し、
	 * データを表現できないケースがあります。その場合には、クオート文字を
	 * ASCII文字に置き換える事で、指定の文字を渡すことが可能になります。
	 * ここでは、引数文字列に、ダブルクオート(")、シングルクオート(')が、
	 * 含まれると、それぞれ、ASCII コード(￥ｘ２２、￥ｘ２７)に置き換えます。
	 * なお、null は、ゼロ文字列に変換して返します。
	 *
	 * @param	input 入力文字列
	 *
	 * @return	クオート文字をASCII文字に置き換えた文字列
	 */
	public static String quoteFilter( final String input ) {
		if( input == null || input.length() == 0 ) { return ""; }
		if( input.indexOf( '\'' ) < 0 && input.indexOf( '"' ) < 0 ) { return input; }

		StringBuilder rtn = new StringBuilder();
		char ch;
		for(int i=0; i<input.length(); i++) {
			ch = input.charAt(i);
			switch( ch ) {
				case '"'  : rtn.append( "\\x22" ); break;
				case '\'' : rtn.append( "\\x27" ); break;
				default   : rtn.append( ch );
			}
		}
		return( rtn.toString() );
	}

	/**
	 * 数字から１６進文字に変換するテーブルです。
	 * MD5 のハッシュ結果(Byte)を文字列にするときに使用します。
	 *
	 * @og.rev 5.2.2.0 (2010/11/01) security.HybsCryptography へ移動
	 */
//	private static final char[] hexadecimal =
//		{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
//		  'a', 'b', 'c', 'd', 'e', 'f' };

	/**
	 * MessageDigestにより、MD5 でハッシュした文字に変換します。
	 *
	 * MD5で、１６Byteのバイトに変換されますが、ここでは、<del>マイナス時には,
	 * 符号を反転させて、</del>１６進数で文字列に変換しています。
	 * <del>よって、このメソッドで変換した文字でのみ突き合わせて正しいかどうかを
	 * 判断してください。</del>
	 *
	 * 変換方法は、各バイトの上位/下位を１６進文字列に変換後、連結しています。
	 * これは、Tomcat等の digest 認証(MD5使用時)と同じ変換方式です。
	 * 連結後の文字列長は、３２バイト(固定)になります。
	 *
	 * @og.rev 5.2.2.0 (2010/11/01) security.HybsCryptography へ移動
	 *
	 * @param	input 変換前の文字列
	 *
	 * @return	MD5 でハッシュした文字列。３２バイト(固定)
	 */
//	public static String getMD5( final String input ) {
//		String rtn = null;
//		if( input != null ) {
//			try {
//				MessageDigest md5 = MessageDigest.getInstance( "MD5" );
//				md5.update( input.getBytes() );
//				byte[] out = md5.digest();
//
//				char[] chs = new char[32];
//				for( int i=0; i<16; i++ ) {
//					int high = ((out[i] & 0xf0) >> 4);
//					int low  = (out[i] & 0x0f);
//					chs[i*2]   = hexadecimal[high];
//					chs[i*2+1] = hexadecimal[low];
//				}
//				rtn =  new String(chs);
//			}
//			catch( NoSuchAlgorithmException ex ) {
//				String errMsg = "MessageDigestで失敗しました。[" + input + "]" + CR
//							+ ex.getMessage() ;
//				throw new RuntimeException( errMsg,ex );
//			}
//		}
//		return rtn;
//	}

	/**
	 * 所定のキャラクタコードを取り除いた文字列を作成します。
	 *
	 * 実現したい機能は、String#replace( 'x','' ) 的な表現です。
	 * つまり、指定のキャラクタを取り除きたいのですが、上記コマンドでは、
	 * コンパイル時にエラーが発生します。
	 * 取り除きたいキャラクタコードが存在しない場合は、指定の文字列を
	 * そのまま返します。
	 *
	 * @param	value 処理対象の文字列
	 * @param	ch 取り除きたいキャラクタ
	 *
	 * @return	処理後の文字列
	 */
	public static String deleteChar( final String value,final char ch ) {
		if( value == null || value.indexOf( ch ) < 0 ) { return value; }
		char[] chs = value.toCharArray() ;
		int j=0;
		for( int i=0;i<chs.length; i++ ) {
			if( chs[i] == ch ) { continue; }
			chs[j] = chs[i];
			j++;
		}
		return String.valueOf( chs,0,j );
	}

	/**
	 * 文字列に含まれる、特定の文字の個数をカウントして返します。
	 *
	 * @og.rev 5.2.0.0 (2010/09/01)
	 *
	 * @param	value 処理対象の文字列
	 * @param	ch カウントする文字
	 *
	 * @return	カウント数
	 */
	public static int countChar( final String value,final char ch ) {
		if( value == null || value.indexOf( ch ) < 0 ) { return 0; }
		char[] chs = value.toCharArray() ;
		int cnt=0;
		for( int i=0;i<chs.length; i++ ) {
			if( chs[i] == ch ) { cnt++; }
		}
		return cnt;
	}

	/**
	 * code39 のチェックデジット計算に使用する モジュラス４３ の変換表です。
	 *
	 */
	private static final String MODULUS_43 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%" ;

	/**
	 * CODE39 の 文字列を作成します。
	 *
	 * CODE39 は、『0～9, A～Z,-,・,　,$,/,+,%』のコードが使用できる
	 * バーコードの体系です。通常 * で始まり * で終了します。
	 * また、チェックデジット に、モジュラス43 が使われます。
	 * ここでは、指定の文字列の前後に、* を付与し、必要であれば
	 * チェックデジットも付与します。
	 * 指定の入力文字列には、* を付けないでください。
	 *
	 * @param	value 処理対象の文字列
	 * @param	checkDigit チェックデジットの付与(true:付ける/false:付けない)
	 *
	 * @return	処理後の文字列
	 */
	public static String code39( final String value,final boolean checkDigit ) {
		String rtn = ( value == null ) ? "" : value ;
		if( ! checkDigit ) { return ( "*" + rtn + "*" ); }

		int kei = 0;
		int cd;
		for( int i=0; i<rtn.length(); i++ ) {
			cd = MODULUS_43.indexOf( rtn.charAt(i) );
			if( cd < 0 ) {
				String errMsg = "指定の文字中に、CODE39 規定外文字が使用されています。[" + rtn.charAt(i) + "]" ;
				throw new RuntimeException( errMsg );
			}
			kei += cd ;
		}
		char digit = MODULUS_43.charAt( kei % 43 );

		return ( "*" + rtn + digit + "*" );
	}

	/**
	 * 引数 in が、null または、ゼロ文字列の場合は､デフォルト値 def を返します。
	 * もちろん、in も def も null の場合は､null を返します。
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト文字列
	 *
	 * @return   ( in != null ) ? in : def ;
	 */
	public static String nval( final String in,final String def ) {
		return ( in == null || in.length() == 0 ) ? def : in ;
	}

	/**
	 * 引数 in が、null または、ゼロ文字列の場合は､デフォルト値 def を返します。
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト数字
	 *
	 * @return   引数 in を変換した数字。変換できない場合は デフォルト値 def
	 */
	public static int nval( final String in,final int def ) {
		return ( in == null || in.length() == 0 ) ? def : Integer.parseInt( in ) ;
	}

	/**
	 * 引数 in が、null または、ゼロ文字列の場合は､デフォルト値 def を返します。
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト数字
	 *
	 * @return   引数 in を変換した数字。変換できない場合は デフォルト値 def
	 */
	public static long nval( final String in,final long def ) {
		return ( in == null || in.length() == 0 ) ? def : Long.parseLong( in ) ;
	}

	/**
	 * 引数 in が、null または、ゼロ文字列の場合は､デフォルト値 def を返します。
	 * 通常は、"true" または、 "TRUE" 文字列を、論理値の true に変換します。
	 * ただし、文字列長が 1文字の場合のみ、"0" 以外を true に変換します。
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト論理値
	 *
	 * @return   引数 in を変換した論理値。変換できない場合は デフォルト値 def
	 */
	public static boolean nval( final String in,final boolean def ) {
		boolean rtn = def;
		if( in != null && in.length() != 0 ) {
			rtn = "true".equalsIgnoreCase( in )  ;
			if( in.length() == 1 ) { rtn = ! "0".equals( in ); }
		}
		return rtn ;
	}

	/**
	 * 引数 in が、null、"_"、ゼロ文字列の場合は､デフォルト値 def を返します。
	 * <del>ただし、NULL代替文字(_)はゼロ文字列に置き換えます。</del>
	 *
	 * さらに、メモリ領域を節約する為、intern() の結果を返します。
	 *
	 * @og.rev  5.2.2.0 (2010/11/01) "_" の取り扱い変更
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト文字列
	 *
	 * @return  null、"_"、ゼロ文字列の場合は、デフォルト文字列を、そうでなければ、入力文字を返す。
	 */
	public static String nval2( final String in,final String def ) {
//		return ( in == null || in.length() == 0 ) ? def : ( "_".equals( in ) ? "" : in.intern() ) ;
		return ( in == null || in.length() == 0 || "_".equals( in ) ) ? def : in.intern() ;
	}

	/**
	 * 引数 in が、null または、ゼロ文字列の場合は､デフォルト値 def を返します。
	 * ただし、NULL代替文字(_)は デフォルト値 def2 に置き換えます。
	 *
	 * さらに、メモリ領域を節約する為、intern() の結果を返します。
	 *
	 * @og.rev  5.2.2.0 (2010/11/01) "_" の取り扱い変更
	 *
	 * @param    in 基準となる文字列
	 * @param    def デフォルト文字列
	 * @param    def2 NULL代替文字(_)の場合のデフォルト文字列
	 *
	 * @return  NULL文字列関係の場合は、ゼロ文字列を、そうでなければ、入力文字を返す。
	 */
	public static String nval2( final String in,final String def,final String def2 ) {
//		return ( in == null || in.length() == 0 ) ? def : ( "_".equals( in ) ? "" : in.intern() ) ;
		return ( in == null || in.length() == 0 ) ? def : ( "_".equals( in ) ? def2 : in.intern() ) ;
	}

	/**
	 * 引数 in が、null または、ゼロ文字列、またはすべて空白文字の場合は､true を返します。
	 * それ以外は false を返します。
	 *
	 * 注意は、オールスペースやタブ文字、改行文字も true になります。
	 *
	 * @param    in 基準となる文字列
	 *
	 * @return  NULL文字列関係の場合は、true を、そうでなければ、false を返す。
	 */
	public static boolean isNull( final String in ) {
		if( in == null || in.length() == 0 ) { return true; }

		// String.trim().length()==0 の高速版
		for( int i=0; i<in.length(); i++ ) {
			if( !Character.isWhitespace( in.charAt(i) ) ) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Throwable の printStackTrace() 結果を文字列に変換して返します。
	 *
	 * @param    th   printStackTraceすべき元のThrowableオブジェクト
	 *
	 * @return   Throwableの詳細メッセージ( th.printStackTrace() )
	 */
	public static String stringStackTrace( final Throwable th ) {
		if( th == null ) { return null; }

		StringWriter sw = new StringWriter();
		th.printStackTrace( new PrintWriter( sw ) );

		return String.valueOf( sw );
	}

	/**
	 * 大きな浮動小数点数について、カンマ編集を行います。
	 *
	 * このメソッドでは、1.23 E12 などの数字は扱いません。通常の
	 * 数字とピリオドで構成された文字列のみ、変換対象になります。
	 * (ただし、不正な文字列を与えてもエラーチェックはしていません。)
	 * minFraction には、少数点部に与える固定値を指定します。入力文字列が
	 * その桁数より少ない場合は、０埋めします。<del>多い場合は、カットします。</del>
	 * 多い場合でもカットしません。
	 * minFraction が 0 の場合は、少数点は付きません。
	 * ".12" などの少数点は、必ず先頭に 0 が付きます。
	 * 入力文字列が null か、ゼロ文字列時は、そのまま入力データを返します。
	 *
	 * <pre>
	 *	DecimalFormat format = new DecimalFormat( "#,##0.00########" );
	 *	double dd = Double.parseDouble( val );
	 *	return format.format( dd );
	 * </pre>
	 * に対して、minFraction分の少数以下のゼロの指定と、inに ',' が
	 * 含まれた処理を追加した感じになります。
	 *
	 * @og.rev  4.0.0.0 (2007/10/26) 空白のトリム処理を追加
	 *
	 * @param	in          	変換元の文字列
	 * @param	minFraction 	変換時の少数点以下の固定桁数
	 *
	 * @return	カンマ編集後の数字型文字列
	 */
	public static String numberFormat( final String in, final int minFraction ) {
		if( in == null || in.length() == 0 ) { return in ; }

		// 4.0.0.0 (2007/10/26)
		String tmp = in.trim();

		if( tmp.length() == 0 ) { return tmp ; }

		char[] chs = tmp.toCharArray();
		int pos = 0;

		// 整数部の設定
		boolean firstZero = true;
		StringBuilder buf1 = new StringBuilder();
		while( pos < chs.length ) {
			char ch = chs[pos++];
			if( ch == '.' ) { break; }
			else if( ch != '-' && ch != ',' && ( ch != '0' || !firstZero )) {
				buf1.append( ch );
				firstZero = false;
			}
		}
		if( buf1.length() == 0 ) {
			buf1.append( '0' );
		}

		for( int i=buf1.length()-3; i>0; i-=3 ) {
			buf1.insert( i,',' );
		}
		if( chs[0] == '-' ) { buf1.insert( 0,'-' ); }

		// 少数部の設定
		// 3.6.0.3 (2004/10/05) 桁数が多い場合でもカットしない
		StringBuilder buf2 = new StringBuilder();
		while( pos < chs.length ) {
			buf2.append( chs[pos++] );
		}

		while( buf2.length() < minFraction ) {
			buf2.append( '0' );
		}

		if( buf2.length() > 0 ) {
			buf1.append( '.' ).append( buf2 );
		}

		return buf1.toString();
	}

	/**
	 * 識別id に応じた オブジェクトを作成します。
	 * 作成するには、デフォルトコンストラクターが必要です。
	 *
	 * @param	cls 作成するクラスのフルネーム
	 *
	 * @return	オブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 */
	public static Object newInstance( final String cls ) {
		return newInstance( cls,Thread.currentThread().getContextClassLoader() );
	}

	/**
	 * 指定されたクラスローダを使って、識別id に応じた オブジェクトを作成します。
	 * 作成するには、デフォルトコンストラクターが必要です。
	 * initialize パラメータは true 相当(それまでに初期化されていない場合だけ初期化)です。
	 *
	 * @param	cls		作成するクラスのフルネーム
	 * @param	loader	作成するクラスのクラスローダ
	 *
	 * @return	オブジェクト
	 * @throws RuntimeException 何らかのエラーが発生した場合
	 */
	public static Object newInstance( final String cls,final ClassLoader loader ) {
		try {
			return Class.forName( cls,true,loader ).newInstance();
		}
		catch( ClassNotFoundException ex1 ) {
			String errMsg = "クラスが見つかりません。class=[" + cls + "]" + CR
						+ ex1.getMessage() ;
			throw new RuntimeException( errMsg,ex1 );
		}
		catch( LinkageError ex2 ) {
			String errMsg = "リンケージが失敗しました。class=[" + cls + "]" + CR
						+ ex2.getMessage();
			throw new RuntimeException( errMsg,ex2 );
		}
		catch( InstantiationException ex3 ) {
			String errMsg = "インスタンスの生成が失敗しました。class=[" + cls + "]" + CR
						+ ex3.getMessage() ;
			throw new RuntimeException( errMsg,ex3 );
		}
		catch( IllegalAccessException ex4 ) {
			String errMsg = "クラスまたは初期化子にアクセスできません。class=[" + cls + "]" + CR
						+ ex4.getMessage();
			throw new RuntimeException( errMsg,ex4 );
		}
		catch( RuntimeException ex5 ) {		// 3.6.0.0 (2004/09/17)
			String errMsg = "予期せぬエラー class=[" + cls + "]" + CR
						+ ex5.getMessage() ;
			throw new RuntimeException( errMsg,ex5 );
		}
	}

	/**
	 * 指定のURL文字列同士を連結させます。
	 * そのとき、後方URLが、絶対パスの場合は、連結せず 後方URLを返します。
	 *
	 * 絶対パスかどうかは、通常のファイル属性と同様に、先頭が、'/' (UNIX)または、
	 * ２文字目が、":" (Windows)の場合、または、先頭が "\" (ネットワークパス)で
	 * 始まる場合で判断します。
	 * 連結時に、前方URLの末尾に "/" を付加します。
	 *
	 * @og.rev  5.0.0.1 (2009/08/15) 不要なオブジェクトの生成を抑制する。
	 * @og.rev  5.6.5.2 (2013/06/21) 第３引数を可変長引数に変更のため、廃止
	 *
	 * @param	url1 先頭URL文字列
	 * @param	url2 後方URL文字列(絶対パスの場合は、返り値)
	 *
	 * @return	URL文字列同士を連結結果 url1 + url2(url2が絶対パスの場合は、url2のみ)
	 */
//	public static String urlAppend( final String url1,final String url2 ) {
//		if(      url2 == null || url2.length() == 0 ) { return url1 ; }
//		else if( ( url1 == null || url1.length() == 0 ) ||
//				 ( url2.charAt(0) == '/'  ) ||							// 実ディレクトリが UNIX
//				 ( url2.length() > 1 && url2.charAt(1) == ':' ) ||		// 実ディレクトリが Windows
//				 ( url2.charAt(0) == '\\' )	) {							// 実ディレクトリが ネットワークパス
//					return url2 ;
//		}
//
//		StringBuilder rtnUrl = new StringBuilder( url1.length() + url2.length() + 1 );
//
////		if( url2 == null || url2.length() == 0 ) { rtnUrl.append( url1 ); }
////		else if( url1 == null || url1.length() == 0 ) { rtnUrl.append( url2 ); }
////		else if( ( url2.charAt(0) == '/'  ) ||		// 実ディレクトリが UNIX
////				 ( url2.length() > 1 && url2.charAt(1) == ':' ) ||		// 実ディレクトリが Windows
////				 ( url2.charAt(0) == '\\' )	) {		// 実ディレクトリが ネットワークパス
////				rtnUrl.append( url2 );
////		}
////		else {
//			char ch = url1.charAt( url1.length()-1 ) ;
//			if( ch == '/' || ch == '\\' ) {
//				rtnUrl.append( url1 ).append( url2 ) ;
//			}
//			else {
//				rtnUrl.append( url1 ).append( "/" ).append( url2 ) ;
//			}
////		}
//
//		return rtnUrl.toString() ;
//	}

	/**
	 * 指定のURL文字列同士を連結させます。
	 * そのとき、後方URLが、絶対パスの場合は、連結せず 後方URLを返します。
	 * 第２引数以降は、絶対パス判定をせず直前のURLの末尾判定のみで連結します。
	 *
	 * 絶対パスかどうかは、通常のファイル属性と同様に、先頭が、'/' (UNIX)または、
	 * ２文字目が、":" (Windows)の場合、または、先頭が "\" (ネットワークパス)で
	 * 始まる場合で判断します。
	 * 連結時に、前方URLの末尾に "/" を付加します。
	 *
	 * 処理の互換性確保のため、第３引数の可変長引数を追加しています。
	 *
	 * @og.rev  5.0.0.1 (2009/08/15) 不要なオブジェクトの生成を抑制する。
	 * @og.rev  5.6.5.2 (2013/06/21) 第３引数を可変長引数に変更
	 *
	 * @param	url1 先頭URL文字列
	 * @param	url2 後方URL文字列(絶対パスの場合は、返り値)
	 * @param	urls 後方URL文字列
	 *
	 * @return	URL文字列同士の連結結果 url1 + url2(url2が絶対パスの場合は、url2から連結開始)
	 */
	public static String urlAppend( final String url1,final String url2,final String... urls ) {
		StringBuilder rtnUrl = new StringBuilder( 200 );

		if(        url2 == null || url2.length() == 0 ) { rtnUrl.append( url1 ) ; }
		else if( ( url1 == null || url1.length() == 0 ) ||
				 ( url2.charAt(0) == '/'  ) ||							// 実ディレクトリが UNIX
				 ( url2.length() > 1 && url2.charAt(1) == ':' ) ||		// 実ディレクトリが Windows
				 ( url2.charAt(0) == '\\' )	) {							// 実ディレクトリが ネットワークパス
					rtnUrl.append( url2 ) ;
		}
		else {
			char ch = url1.charAt( url1.length()-1 ) ;
			if( ch == '/' || ch == '\\' ) {
				rtnUrl.append( url1 ).append( url2 ) ;
			}
			else {
				rtnUrl.append( url1 ).append( "/" ).append( url2 ) ;
			}
		}

		// ここからが、追加分
		for( String url : urls ) {
			if( url != null && url.length() > 0 ) {
				char ch = rtnUrl.charAt( rtnUrl.length()-1 ) ;
				if( ch == '/' || ch == '\\' ) {
					rtnUrl.append( url ) ;
				}
				else {
					rtnUrl.append( "/" ).append( url ) ;
				}
			}
		}

		return rtnUrl.toString() ;
	}

	/**
	 * getUnicodeEscape で使用する桁合わせ用文字列配列です。
	 * Unicodeの HexString 変換後の桁に応じて、埋め合わせします。
	 *
	 */
	private static final String[] UTF_STR = { "&#x0000", "&#x000", "&#x00", "&#x0", "&#x" };

	/**
	 * Unicode文字列の値を HTML のエスケープ記号(&amp;#xZZZZ;)に変換します。
	 *
	 * SJIS(JA16SJIS) で作成されたデータベースに、(NVARCHAR2)を使用して中国語等を登録するのは
	 * 非常に複雑でかつ、リスクが大きい処理になります。
	 * ORACLE殿でも、自信を持っては勧められない機能とのコメントを頂いています。
	 * そこで、HTMLでのエスケープ文字を使用して、Unicodeを文字列化して登録する為の
	 * DBType として、新規に作成します。
	 * ここでは、入力文字を、キャラクタ(char)型に分解し、(&amp;#xZZZZ;)に変換していきます。
	 * よって、通常に１文字(Shift-JISで２Byte,UTF-8で３Byte)が、８Byteになります。
	 * この変換された文字列を、HTML上でそのまま取り出すと、元のUnicode文字に戻る為、
	 * 通常のShift-JISでは、扱えない文字(中国語など)でも表示可能になります。
	 * ここでは、2バイト文字のみ、変換しています。
	 *
	 * @param	value 変換前の文字列
	 *
	 * @return	HTMLのエスケープ記号(&amp;#xZZZZ;)
	 */
	public static String getUnicodeEscape( final String value ) {
		if( value == null || value.length() == 0 ) { return ""; }

		StringBuilder rtn = new StringBuilder( value.length() * 4 );

		for( int i=0; i<value.length(); i++ ) {
			char ch = value.charAt(i);

			if( ch > 0xff ) {
				String hex = Integer.toHexString( (int)ch ) ;
				rtn.append( UTF_STR[hex.length()] ).append( hex ).append( ";" );
			}
			else {
				rtn.append( ch );
			}
		}

		return rtn.toString();
	}

	/**
	 * HTML のエスケープ記号(&amp;#xZZZZ;)をUnicode文字列に戻します。
	 *
	 * HTMLでのエスケープ文字を使用して登録された文字を、Unicodeに戻します。
	 * (&amp;#xZZZZ;)の８Byteを、もとのキャラクタコードに戻し、合成します。
	 * ここでは、通常の文字列に混在したエスケープ文字も戻せるようにします。
	 *
	 * @param	value	HTMLのエスケープ記号(&amp;#xZZZZ;)を含む文字列
	 *
	 * @return	通常のUnicode文字列
	 */
	public static String getReplaceEscape( final String value ) {
		if( value == null || value.length() == 0 ) { return ""; }

		StringBuilder rtn = new StringBuilder( value );

		int st = rtn.indexOf( "&#" );
		while( st >= 0 ) {
			if( st+7 < rtn.length() && rtn.charAt( st+7 ) == ';' ) {
				int ch = Integer.parseInt( rtn.substring( st+3,st+7 ),16 );
				rtn.replace( st,st+8, Character.toString( (char)ch ) );
			}
			st = rtn.indexOf( "&#",st );
		}

		return rtn.toString();
	}

	/**
	 * 文字列をdoubleに変換します。
	 *
	 * これは、Double.parseDouble( value ) と、ほぼ同じ動作を行います。
	 * 内部的には、引数の カンマ(,) を削除した文字列を、Double.parseDouble( value )
	 * に渡します。
	 * また、引数が、null,ゼロ文字列,'_' の時には、0.0 を返します。
	 *
	 * @param	value	doubleに変換する元の文字列
	 *
	 * @return	変換後のdouble数値
	 */
	public static double parseDouble( final String value ) {
		double rtn ;

		if( value == null || value.length() == 0 || value.equals( "_" ) ) {
			rtn = 0.0d;
		}
		else if( value.indexOf( ',' ) < 0 ) {
			rtn = Double.parseDouble( value );
		}
		else {
			char[] chs = value.toCharArray() ;
			int j=0;
			for( int i=0;i<chs.length; i++ ) {
				if( chs[i] == ',' ) { continue; }
				chs[j] = chs[i];
				j++;
			}
			rtn = Double.parseDouble( String.valueOf( chs,0,j ) );
		}

		return rtn ;
	}

	// 4.0.3.0 (2007/12/26) 色コードにPURPLE を追加
	private static final Map<String,Color> CLR_MAP;
	static {
		CLR_MAP = new HashMap<String,Color>();
		CLR_MAP.put( "BLACK"		,Color.BLACK		);
		CLR_MAP.put( "BLUE"			,Color.BLUE			);
		CLR_MAP.put( "CYAN"			,Color.CYAN			);
		CLR_MAP.put( "DARK_GRAY"	,Color.DARK_GRAY	);
		CLR_MAP.put( "GRAY"			,Color.GRAY			);
		CLR_MAP.put( "GREEN"		,Color.GREEN		);
		CLR_MAP.put( "LIGHT_GRAY"	,Color.LIGHT_GRAY	);
		CLR_MAP.put( "MAGENTA"		,Color.MAGENTA		);
		CLR_MAP.put( "ORANGE"		,Color.ORANGE		);
		CLR_MAP.put( "PINK"			,Color.PINK			);
		CLR_MAP.put( "RED"			,Color.RED			);
		CLR_MAP.put( "WHITE"		,Color.WHITE		);
		CLR_MAP.put( "YELLOW"		,Color.YELLOW		);
		CLR_MAP.put( "PURPLE"		,new Color( 8388736 )	);		// #800080
	}

	/**
	 * カラーキーワードより、Colorオブジェクトを作成します。
	 *
	 * 指定文字列は、java.awt.Color クラスのstatic フィールド名で指定します。
	 * BLACK , BLUE , CYAN , DARK_GRAY , GRAY , GREEN , LIGHT_GRAY ,
	 * MAGENTA , ORANGE , PINK , RED , WHITE , YELLOW , PURPLE が指定できます。
	 * また、先頭に、# を付ける事で、#XXXXXX形式の16bitRGB表記 でも指定可能です。
	 * static フィールド名のMapを管理していますが、存在しない場合は、エラーになります。
	 *
	 * @og.rev 3.8.9.1 (2007/06/29) 新規作成
	 * @og.rev 4.1.1.0 (2008/02/04) CLR_MAP に存在しない場合はエラーにします。
	 *
	 * @param	value java.awt.Color フィールドを示す文字列または、#XXXXXX形式の16bitRGB表記
	 *
	 * @return	Colorオブジェクト
	 * @see		java.awt.Color#BLACK
	 */
	public static Color getColorInstance( final String value ) {
		final Color clr ;

		if( value.startsWith("#") ) {
			int code = Integer.parseInt( value.substring(1),16 );
			clr = new Color( code );
		}
		else {
			clr = CLR_MAP.get( value );
			if( clr == null ) {
				String errMsg = "指定の色コードは使用できません Color=[" + value + "]" + CR
							+ "ColorMap=" + CLR_MAP.keySet().toString();
				throw new RuntimeException( errMsg );
			}
		}

		return clr;
	}

	/**
	 * 引数に指定された文字列のNativeタイプを返します。
	 *
	 * 1.STRING -> 数値以外の文字列(先頭の'-'、または任意の場所の'.'を除く)が含まれていた場合
	 * 2.DOUBLE -> 1以外で'.'が含まれている場合
	 * 3.INT    -> 1,2以外
	 *
	 * @og.rev 5.1.8.0 (2010/07/01) org.opengion.fukurou.model.NativeType のメソッドを採用、ここは廃止
	 *
	 * @param	str チェックを行う文字列
	 *
	 * @return	NATIVEの型の識別コード
	 * @see org.opengion.fukurou.model.NativeType
	 */
//	public static NativeType getNativeType( final String str ) {
//		if( str == null || str.length() == 0 ) { return NativeType.STRING; }
//
//		NativeType type = NativeType.INT;
//		char ch;
//		for( int i=0; i<str.length(); i++ ) {
//			ch = str.charAt( i );
//			if( ch < '0' || ch > '9' ) {
//				if( ch != '-' || ( ch == '-' && i > 0 ) ) {
//					if( ch == '.' ) {
//						type = NativeType.DOUBLE;
//					}
//					else {
//						type = NativeType.STRING;
//						break;
//					}
//				}
//			}
//		}
//		return type;
//	}

	/**
	 * 引数からspanタグを取り除いて返します。
	 *
	 * 引数が、&lt;span ･･･&gt;XXXX&lt;/span&gt;形式の場合、XXXX のみ出力します。
	 *
	 * @og.rev 4.3.4.3 (2008/12/22) TableWriterで利用していたものを移動
	 *
	 * @param	 data 元のString文字列
	 *
	 * @return	 spanタグが取り除かれた文字列
	 */
	public static String spanCut( final String data ) {
		String rtn = data;
		if( data != null && data.startsWith( "<span" ) ) {
			int st = data.indexOf( '>' );
			int ed = data.indexOf( "</span>",st );
			rtn = data.substring( st+1,ed );
		}
		return rtn ;
	}

	/**
	 * 簡易CSS形式のフォーマットを、Mapにセットします。
	 *
	 * 簡易CSS形式とは、セレクタのない、{ プロパティ1 : 値1 ; ・・・ } 形式とします。
	 * これを、プロパティ1 と 値1 のMap にセットする処理を行います。
	 * コメントは、削除されます。また、同一プロパティが記述されている場合は、後処理を採用します。
	 *
	 * なお、入力テキストが、null か、{…} が存在しない場合は、null を返します。
	 *
	 * @og.rev 5.6.5.2 (2013/06/21) 新規追加
	 *
	 * @param	 cssText 簡易CSS形式のフォーマット文字列
	 *
	 * @return	 パース結果のMap
	 */
	public static Map<String,String> cssParse( final String cssText ) {
		Map<String,String> map = null;

		if( cssText != null ) {
			// まずコメントを削除します。
			StringBuilder buf = new StringBuilder( cssText );

			int ad1 = buf.indexOf( "/*" );
			while( ad1 >= 0 ) {
				int ad2 = buf.indexOf( "*/" , ad1 );
				if( ad2 < 0 ) { buf = buf.delete( ad1,buf.length() ); break; }		// 閉じてなければ以降を全削除
				buf = buf.delete( ad1,ad2+2 );
				ad1 = buf.indexOf( "/*" );		// コメントは削除されたので、初めから検索する。
			}

			// 処理対象は、{ ～ } の間の文字列。
			ad1 = buf.indexOf( "{" );
			int ad2 = buf.indexOf( "}",ad1 );
			if( ad1 >= 0 && ad2 > 0 ) {
				String tempText = buf.substring( ad1+1,ad2 );		// これが処理対象の文字列

				String[] recode = tempText.split( ";" );			// KEY1 : VAL1; の ; で分割する。

				for( int i=0; i<recode.length; i++ ) {
					int ad = recode[i].indexOf( ':' );
					if( ad > 0 ) {
						String key = recode[i].substring( 0,ad ).trim();
						String val = recode[i].substring( ad+1 ).trim();
						if( key.isEmpty() || val.isEmpty() ) { continue; }

						if( map == null ) { map = new HashMap<String,String>(); }	// 対象データがある時だけMapを作りたかったので。
						map.put( key,val );
					}
				}
			}
		}
		return map ;
	}
}
