/*
 * 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 static org.opengion.fukurou.util.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * TagBuffer.java は、共通的に使用される 簡易タグ作成クラスです。
 * タグヘッダーは、オブジェクト作成時に登録する為、後の変更は出来ません。
 * BODY部や、属性は、一度登録すると書き換えできません。
 * また、同一属性チェックは行いません。登録した属性のキーや、値を取り出すことも出来ません。
 * あくまで、タグ文字列をストレートに作成することに特化したクラスです。
 * これらの高度な機能が必要であれば、{@link org.opengion.fukurou.util.Attributes } をご参照ください。
 *
 * makeTag() メソッドを呼び出した時点で、内部にタグ文字列をキャッシュします。
 * それ以降の変更は、出来ません。
 *
 * 内部的には、構造化されていません。あくまで、文字列連結(StringBuilder)の
 * 簡易クラスとして、使用してください。
 *
 * ※ 6.1.1.0 (2015/01/17)
 *    StringBuilder と同様、add メソッドの戻り値に、自分自身を戻します。
 *    これにより、連結処理できるようにします。
 *
 * @og.group ユーティリティ
 *
 * @version  4.0
 * @author	 Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public final class TagBuffer {
	private final	StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
	private final	String tagName	;
	private			String tagBody	;
	private			String cacheTag	;

	/**
	 * デフォルトコンストラクター
	 * このコンストラクターを使用すると、タグ名を指定できないため、
	 * 完成形のタグは、作成できません。属性リスト(key="value")の
	 * 連結形式を得る場合にのみ、使用して下さい。
	 *
	 */
	public TagBuffer() {
		this( null );				// 6.1.1.0 (2015/01/17) メソッドの集約
	}

	/**
	 * コンストラクター
	 * タグ名に null を指定すると、デフォルトコンストラクターと同様に、
	 * 完成形のタグは、作成できません。属性リスト(key="value")の
	 * 連結形式を得る場合にのみ、タグ名 にnull を指定して下さい。
	 *
	 * @param	tagName	 タグ名称
	 */
	public TagBuffer( final String tagName ) {
		this.tagName = tagName;
		if( tagName != null ) {
			buf.append( '<' ).append( tagName ).append( ' ' );		// 6.0.2.5 (2014/10/31) char を append する。
		}
	}

	/**
	 * タグの BODY部を追加登録します。
	 *
	 * すでに、登録済みの場合は、その値に、追記します。
	 * そうでない場合(初めての場合)は、その値をセットします。
	 * null を登録した場合は、何もしません。
	 * ※ 従来あった #setBody(String)の様に、null を登録した場合、Body を
	 * リセットする機能はありません。新しいオブジェクトを作成してください。
	 *
	 * このメソッドは、makeTag() が呼ばれた後は、登録できなくなります。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) #setBody(String) の代替えメソッドとして、新規追加
	 *
	 * @param	body	 タグのBODY部に追加する文字列
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer addBody( final String body ) {
		return addBody( body,true );
	}

	/**
	 * タグの BODY部を追加登録します。
	 *
	 * すでに、#setBody(String) で、登録済みの場合は、その値に、追記します。
	 * そうでない場合は、#setBody(String) と同じ動作になります。
	 * null を登録した場合は、何もしません。
	 * 
	 * flag に、false を指定すると、追加登録しません。これは、if( flag ) { tagBuffer.addBody( body ); }
	 * の様な判定処理を、tagBuffer.body( body,flag ); に簡素化するために使用できます。
	 * TagBuffer は、StringBuilderの様に、メソッドの戻り値は、自分自身のオブジェクトなので、
	 * 上記の flag を使用して、切れ目なく連結させることが可能になります。
	 *
	 * このメソッドは、makeTag() が呼ばれた後は、登録できなくなります。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
	 *
	 * @param	body	タグのBODY部に追加する文字列
	 * @param	flag	タグのBODY部に追加かどうかを決めるフラグ(true:追加する/false:何もしない)
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer addBody( final String body,final boolean flag ) {
		if( cacheTag != null ) {
			final String errMsg = "makeTag() 実行後に、BODY部の値を書き換えることは出来ません。" ;
			throw new RuntimeException( errMsg );
		}

		if( flag && body != null ) {
			if( tagBody !=null ) { tagBody += body; }
			else				 { tagBody =  body; }
		}

		return this ;
	}

	/**
	 * タグの属性に、追加登録します。
	 * 文字列として、key="value" の形式データを与えてください。連結時は、後ろに
	 * スペースを一つ挟みますので、与える引数自体に連結用スペースを追加しておく
	 * 必要はありません。
	 * 通常は、tagName なしで作成した、Tagbuffer オブジェクトの makeTag() メソッドの
	 * 返り値を渡しますが、Attributes.getAttribute() の返り値でも使用できます。
	 * 引数が null の場合は、なにもしません。
	 * このメソッドは、makeTag() が呼ばれた後は、登録できなくなります。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
	 *
	 * @param	str	タグバッファーを追加します。
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 * @see		#makeTag()
	 * @see		Attributes#getAttribute()
	 */
	public TagBuffer add( final String str ) {
		return add( null,str,true );					// 6.1.1.0 (2015/01/17) メソッドの集約

	}

	/**
	 * タグの 属性(key="value")を登録します。
	 * 属性は、key="value" の文字列を作成します。
	 * key が null の場合は、値だけをそのまま連結します。
	 * val が null の場合は、登録しません。
	 *
	 * value に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
	 * でくくります。
	 * 両方含まれている場合は、シングルコーテーションをエスケープ文字(&amp;#39;)に変換します。
	 * このメソッドは、makeTag() が呼ばれた後は、登録できなくなります。
	 *
	 * @og.rev 3.8.6.1 (2006/10/20) シングルとダブルが混在する場合は、シングルをエスケープする
	 * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
	 *
	 * @param	key	 属性キー (null の場合は、val属性値 をそのまま追加する)
	 * @param	val	 属性値 (null の場合は、なにもしない)
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String key,final String val ) {
		return add( key,val,true );					// 6.1.1.0 (2015/01/17) メソッドの集約
	}

	/**
	 * タグの 属性(key="value")を登録します。
	 * 属性は、key="value" の文字列を作成します。
	 * key が null の場合は、値だけをそのまま連結します。
	 * val が null の場合は、登録しません。
	 * 
	 * flag に、false を指定すると、属性は追加しません。これは、if( flag ) { tagBuffer.add( key,val ); }
	 * の様な判定処理を、tagBuffer.add( key,val,flag ); に簡素化するために使用できます。
	 * TagBuffer は、StringBuilderの様に、メソッドの戻り値は、自分自身のオブジェクトなので、
	 * 上記の flag を使用して、切れ目なく連結させることが可能になります。
	 *
	 * value に、ダブルコーテーション(")が含まれている場合は、属性値をシングルコーテーション
	 * でくくります。
	 * 両方含まれている場合は、シングルコーテーションをエスケープ文字に変換します。
	 * このメソッドは、makeTag() が呼ばれた後は、登録できなくなります。
	 *
	 * @og.rev 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返すようにします。
	 *
	 * @param	key	 	属性キー (null の場合は、val属性値 をそのまま追加する)
	 * @param	val	 	属性値 (null の場合は、なにもしない)
	 * @param	flag	属性を追加かどうかを決めるフラグ(true:追加する/false:何もしない)
	 *
	 * @return	自分自身
	 * @og.rtnNotNull
	 */
	public TagBuffer add( final String key,final String val,final boolean flag ) {
		if( cacheTag != null ) {
			final String errMsg = "makeTag() 実行後に、属性を追加することは出来ません。" ;
			throw new RuntimeException( errMsg );
		}

		// 6.1.1.0 (2015/01/17) ロジックを見直します。
		if( flag && val != null ) {
			if( key != null ) {
				buf.append( key );
				// ダブルコーテーションがある場合は、シングルコーテーションで囲う。
				if( val.indexOf( '"' ) >= 0 ) {
					// ただし、シングルコーテーションを含む場合は、エスケープ文字に置き換える。
					buf.append( "='" )
						.append( val.indexOf( '\'' ) >= 0 ? val.replaceAll( "'","&#39;" ) : val )
						.append( "' " );
				}
				else {
					buf.append( "=\"" ).append( val ).append( "\" " );
				}
			}
			// 6.1.1.0 (2015/01/17) 元 add( String ) メソッドの処理
			else {
				buf.append( val ).append( ' ' );
			}
		}

		return this ;		// 6.1.1.0 (2015/01/17) 戻り値に、自分自身を返す
	}

	/**
	 * タグの 整形された文字列を 作成します。
	 * このメソッドは、tagName,body,属性より タグの完成形の文字列を作成します。
	 * 作成された文字列は、内部でキャッシュされます。
	 * BODY部を指定すると、&lt;tagName key="val" ･･･ &gt;body&lt;/tagName&gt; 形式になります。
	 * BODY部が、null の場合は、 &lt;tagName key="val" ･･･ /&gt; 形式になります。
	 * タグ名を指定しない(null)と、完成形のタグは、作成できません。
	 * 属性リスト(key="value")の連結形式を返します。
	 *
	 * このメソッドの呼び出し以降では、setBody() も add() も実行できません。
	 *
	 * @return	整形された タグ文字列
	 */
	public String makeTag() {
		if( cacheTag == null ) {
			if( tagName != null ) {
				if( tagBody == null ) {
					buf.append( "/>" );
				}
				else {
					buf.append( '>' ).append( tagBody )						// 6.0.2.5 (2014/10/31) char を append する。
					   .append( "</" ).append( tagName ).append( '>' );		// 6.0.2.5 (2014/10/31) char を append する。
				}
			}
			cacheTag = buf.toString();
		}

		return cacheTag;
	}

	/**
	 * 行番号付きのタグの 整形された文字列を 作成します。
	 * このメソッドは、makeTag() の結果より、[I] 文字列を引数の行番号と変換します。
	 * これにより、動的に行番号つきの情報を属性に付与することが可能になります。
	 * 内部にキャッシュされる値は、[I] 記号を変換していない状態の文字列です。
	 * よって、このメソッドは、rowNo を変えて、何度でも呼び出すことは可能ですが、
	 * setBody() や add() は実行できません。
	 *
	 * @param	rowNo	行番号([I] 文字列を変換します。)
	 * @param	val	設定値([V] 文字列を変換します。)
	 *
	 * @return	整形された タグ文字列
	 */
	public String makeTag( final int rowNo,final String val ) {
		String tag = makeTag();
		tag = StringUtil.replace( tag,"[I]",String.valueOf( rowNo ) );
		tag = StringUtil.replace( tag,"[V]",val );

		return tag ;
	}
}
