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

import java.util.Map;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Collections;										// 6.4.3.1 (2016/02/12) refactoring

import static org.opengion.fukurou.system.HybsConst.CR;				// 6.1.0.0 (2014/12/26) refactoring
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

/**
 * このクラスは、XMLファイルのタグエレメントを表すオブジェクトです。
 * タグとしては、コンストラクタにMapを指定すれば(defaultMap)タグのカラムを
 * 初期設定します。この場合、Map に LinkedHashMap を指定すれば、カラムの
 * 順番も指定順になります。
 * ここで指定したMapの値は、put メソッドにより上書きされます。
 * setAfterMap で指定したMapは、既存の内部情報を上書きします。キーがあれば、
 * afterMap の値が上書きされ、キーが無ければ新規にキーが追加されます。
 * 一般には、XMLファイルから構築された後で、XMLファイルの情報を一括して
 * 書き換える場合などに使用します。
 * 処理の途中にセットした場合は、それまでの値が上書きされ、それ以降の値は、
 * put により設定された値が 優先されます。
 * toString() により、簡易的に オラクルＸＤＫ形式のＸＭＬファイルの
 * １レコード 分の情報を返します。
 * オラクルＸＤＫ形式のＸＭＬファイルとは、下記のような ROWSET をトップとする ROW の
 * 集まりで１レコードを表し、各ROWには、カラム名をキーとするXMLになっています。
 *
 *   &lt;ROWSET&gt;
 *       &lt;ROW num="1"&gt;                         ← この部分のみ
 *           &lt;カラム1&gt;値1&lt;/カラム1&gt;      ← この部分のみ
 *             ･･･                                   ← この部分のみ
 *           &lt;カラムn&gt;値n&lt;/カラムn&gt;      ← この部分のみ
 *       &lt;/ROW&gt;                                ← この部分のみ
 *        ･･･
 *       &lt;ROW num="n"&gt;
 *          ･･･
 *       &lt;/ROW&gt;
 *   &lt;ROWSET&gt;
 *
 * この形式であれば、XDK(Oracle XML Developer's Kit)を利用すれば、非常に簡単に
 * データベースとXMLファイルとの交換が可能です。
 * <a href="http://otn.oracle.co.jp/software/tech/xml/xdk/index.html" target="_blank" >
 * XDK(Oracle XML Developer's Kit)</a>
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class TagElement {
	private final String name ;
	/** 6.4.3.1 (2016/02/12) Collections.synchronizedMap で同期処理を行います。  */
	private final Map<String,String> tagMap = Collections.synchronizedMap( new LinkedHashMap<>() );		// 6.4.3.1 (2016/02/12) ついでに変数名変更。
	private String num	;
	private String body	;

	/**
	 * Tag名を指定して、オブジェクトを構築します。
	 *
	 * @param	name	Tag名
	 */
	public TagElement( final String name ) {
		this( name,null );
	}

	/**
	 * Tag名と初期化マップを指定して、オブジェクトを構築します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param	name		Tag名
	 * @param	defaultMap	初期化マップ
	 */
	public TagElement( final String name, final Map<String,String> defaultMap ) {
		this.name = name;
		// 6.4.3.1 (2016/02/12) 最初から、構築しておきます。
		if( defaultMap != null ) { tagMap.putAll( defaultMap ); }
	}

	/**
	 * Tag名を取得します。
	 *
	 * @return	Tag名
	 */
	public String getName() {
		return name;
	}

	/**
	 * カラムと値のペアを登録します。
	 * すでに 内部に同一カラムが存在する場合は、値の書き換えを、カラムが
	 * 存在しない場合は、カラムの追加を行います。
	 * カラムは、追加された順番を再現してXML化できます。
	 *
	 * @og.rev 5.6.6.1 (2013/07/12) key が null や ゼロ文字列の場合は、Map に追加しません。
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param	key	カラム
	 * @param	val	値
	 */
	public void put( final String key, final String val ) {
		if( key != null && key.length() > 0 ) {					// 5.6.6.1 (2013/07/12)
			tagMap.put( key,val );
		}
	}

	/**
	 * すでに構築済みの カラムと値に、上書きで マップ情報を追加します。
	 * すでに 内部に同一カラムが存在する場合は、値の書き換えを、カラムが
	 * 存在しない場合は、カラムの追加を行います。
	 * カラムは、追加された順番を再現してXML化できます。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param afterMap	後設定マップ
	 */
	public void setAfterMap( final Map<String,String> afterMap ) {
		if( afterMap != null ) {
			tagMap.putAll( afterMap );
		}
	}

	/**
	 * キーを指定して値を取得します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param	key	カラム
	 *
	 * @return	値
	 */
	public String get( final String key ) {
		// 6.4.3.1 (2016/02/12) ConcurrentMap 系は、key,val ともに not null 制限です。
		return key == null ? null : tagMap.get( key );
	}

	/**
	 * 行番号を取得します。
	 *
	 * @return	値
	 */
	public String getRowNo() {
		return num;
	}

	/**
	 * 行番号を設定します。
	 *
	 * @param	num	値
	 */
	public void setRowNo( final String num ) {
		this.num = num ;
	}

	/**
	 * BODY部の文字列を取得します。
	 *
	 * @return	値
	 */
	public String getBody() {
		return body;
	}

	/**
	 * BODY部の文字列を設定します。
	 *
	 * @param	body	値
	 */
	public void setBody( final String body ) {
		this.body = body ;
	}

	/**
	 * カラムの配列を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @return	カラムの配列(順序は、カラムの登録順)
	 * @og.rtnNotNull
	 */
	public String[] getKeys() {
		return tagMap.keySet().toArray( new String[tagMap.size()] );
	}

	/**
	 * カラム配列の順と同じ、値の配列を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @return	値の配列(順序は、カラムの登録順と一致しています。)
	 * @og.rtnNotNull
	 */
	public String[] getValues() {
		return tagMap.values().toArray( new String[tagMap.size()] );
	}

	/**
	 * 引数のカラム名のListの順番で、カラム配列の値の配列を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param clms	カラム名のListオブジェクト
	 *
	 * @return	値の配列(順序は、カラムの登録順と一致しています。)
	 */
	public String[] getValues( final List<String> clms ) {
		final int size = clms.size();
		final String[] vals = new String[size];
		for( int i=0; i<size; i++ ) {
			vals[i] = tagMap.get( clms.get(i) );
		}

		return vals;
	}

	/**
	 * 引数のカラム名の配列の順番で、カラム配列の値の配列を返します。
	 * カラム名が、存在しない場合は、値は、null を返します。
	 *
	 * @og.rev 6.4.3.1 (2016/02/12) Collections.synchronizedMap に置き換え。ついでに変数名変更。
	 *
	 * @param	clms	カラム名の文字列配列(可変長引数)
	 *
	 * @return	値の配列(順序は、カラムの配列順と一致しています。)
	 */
	public String[] getValues( final String... clms ) {
		final int size = clms.length;
		final String[] vals = new String[size];
		for( int i=0; i<size; i++ ) {
			vals[i] = tagMap.get( clms[i] );
		}

		return vals;
	}

	/**
	 * 内部情報の文字列表現を返します。
	 * １レコード分を表す ROW のXML表現を作成します。前後に、ROWSET を
	 * 追加すれば、オラクルXDK形式のXMLを作成できます。
	 *
	 * @return	内部情報の文字列表現
	 * @og.rtnNotNull
	 */
	@Override
	public String toString() {

		// 6.0.2.5 (2014/10/31) char を append する。
		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		buf.append( '<' ).append( name );
		if( num != null ) {
			buf.append( " num=\"" ).append( num ).append( "\" " );
		}
		buf.append( '>' ).append( CR );

		// 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
		if( body == null ) {
			final String[] keys = getKeys();
			for( int i=0; i<keys.length; i++ ) {
				final String val = get( keys[i] );
				if( val == null ) {
					buf.append( "  <" ).append( keys[i] ).append( " />" );		// XML なので、このまま。
				}
				else {
					buf.append( "  <" ).append( keys[i] ).append( '>' )
						.append( val )
						.append( "</" ).append( keys[i] ).append( '>' );
				}
				buf.append( CR );
			}
		}
		else {
			buf.append( body ).append( CR );
		}

		buf.append( "</" ).append( name ).append( '>' ).append( CR );

		return buf.toString();
	}
}
