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

/**
 * エレメントをあらわす、OGElement クラスを定義します。
 *
 * エレメントは、OGNode クラスを継承し、名称、属性、ノードリストを持つオブジェクトです。
 * 通常で言うところの、タグになります。
 * 属性は、OGAttributes クラスで管理します。ノードリスト に関する操作は、OGNodeクラスの実装です。
 *
 * OGNode は、enum OGNodeType で区別される状態を持っています。
 * OGNodeType は、それぞれ、再設定が可能です。
 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
 * ファイル等への出力時にコメントとして出力されます。
 *
 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
 *
 * @version  5.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK6.0,
 */
public class OGElement extends OGNode {

	private final String		qName ;				// このタグの名前(nameSpace も含むエレメントの名前)
	private 	  OGAttributes	attri = null;		// 属性オブジェクト

	// 階層に応じたスペースの設定
	private static final int      PARA_LEN  = 8;
	private static final String   PARA_CHAR = "\t";
	private static final String[] PARA = new String[PARA_LEN];
	static {
		PARA[0] = CR;
		StringBuilder buf = new StringBuilder();
		buf.append( CR );
		for( int i=1; i<PARA_LEN; i++ ) {
			buf.append( PARA_CHAR );
			PARA[i] = buf.toString();
		}
	}

	/**
	 * ノード名を指定してのトコンストラクター
	 *
	 * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。
	 *
	 * @param	qName	ノード名
	 */
	public OGElement( final String qName ) {
		this( qName,null );
	}

	/**
	 * ノード名、属性タブ、属性リストを指定してのトコンストラクター
	 *
	 * 注意 属性値の正規化は必ず行われます。
	 * 属性値に含まれるCR(復帰), LF(改行), TAB(タブ)は、 半角スペースに置き換えられます。
	 * XMLの規定では、属性の並び順は保障されませんが、SAXのAttributesは、XMLに記述された順番で
	 * 取得できていますので、このクラスでの属性リストも、記述順での並び順になります。
	 *
	 * @og.rev 5.2.1.0 (2010/10/01) タグ属性の改行処理を、Set からString[] に変更。
	 * @og.rev 5.6.1.2 (2013/02/22) CR_SET を配列から文字列に変更
	 *
	 * @param	qName	ノード名
	 * @param	atts	属性リスト
	 */
	public OGElement( final String qName , final Attributes atts ) {
		super();
		setNodeType( OGNodeType.Element );

		if( qName == null ) {
			String errMsg = "エレメントには、ノード名は必須です。";
			throw new RuntimeException( errMsg );
		}

		this.qName = qName;
		this.attri = new OGAttributes( atts ) ;
	}

	/**
	 * ノード名を返します。
	 *
	 * @return	ノード名
	 */
	public String getTagName() {
		return qName;
	}

	/**
	 * 属性オブジェクトを返します。
	 *
	 * これは、org.xml.sax.Attributes ではなく、OGAttributes オブジェクトを返します。
	 * 内部オブジェクトそのものを返しますので、この OGAttributes の変更は、この
	 * エレメントが持つ内部属性も変更されます。
	 *
	 * @return	属性オブジェクト
	 */
	public OGAttributes getOGAttributes() {
		return attri;
	}

	/**
	 * 属性オブジェクトをセットします。
	 *
	 * 属性オブジェクトのセットは、このメソッドからのみできるようにします。
	 * 内部オブジェクトそのものにセットしますので、異なる OGAttributes をセットしたい場合は、
	 * 外部で、コピーしてからセットしてください。
	 *
	 * @og.rev 5.6.1.2 (2013/02/22) 新規追加
	 *
	 * @param	attri 属性オブジェクト(org.opengion.fukurou.xml.OGAttributes)
	 */
	public void setOGAttributes( final OGAttributes attri ) {
		this.attri = attri;
	}

	/**
	 * 属性リストから、id属性の、属性値を取得します。
	 *
	 * id属性 は、内部的にキャッシュしており、すぐに取り出せます。
	 * タグを特定する場合、一般属性のキーと値で選別するのではなく、
	 * id属性を付与して選別するようにすれば、高速に見つけることが可能になります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @return	id属性値
	 */
	public String getId() {
		return (attri != null) ? attri.getId() : null ;
	}

	/**
	 * 属性リストから、指定の属性キーの、属性値を取得します。
	 *
	 * この処理は、属性リストをすべてスキャンして、キーにマッチする
	 * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
	 * パフォーマンスに問題があります。
	 * 基本的には、アドレス指定で、属性値を取り出すようにしてください。
	 *
	 * @og.rev 5.6.1.2 (2013/02/22) 新規追加
	 *
	 * @param	key	属性キー
	 *
	 * @return	属性値
	 */
	public String getVal( final String key ) {
		return (attri != null) ? attri.getVal( key ) : null ;
	}

	/**
	 * 属性リストに、属性(キー、値のセット)を設定します。
	 *
	 * 属性リストの一番最後に、属性(キー、値のセット)を設定します。
	 *
	 * @param	key	属性リストのキー
	 * @param	val	属性リストの値
	 */
	public void addAttr( final String key , final String val ) {
		if( attri == null ) { attri = new OGAttributes() ; }
		attri.add( key,val ) ;
	}

	/**
	 * 自分自身の状態が、指定の条件に合致しているかどうか、判定します。
	 *
	 * 合致している場合は、true を、合致していない場合は、false を返します。
	 *
	 * 指定の属性が null の場合は、すべてに合致すると判断します。
	 * 例えば、kye のみ指定すると、その属性名を持っているエレメントすべてで
	 * true が返されます。
	 * 実行速度を考えると、ノード名は指定すべきです。
	 *
	 * @param	name	ノード名 null の場合は、すべての ノード名 に合致
	 * @param	key	属性名 null の場合は、すべての 属性名 に合致
	 * @param	val	属性値 null の場合は、すべての 属性値 に合致
	 *
	 * @return	条件がこのエレメントに合致した場合 true
	 */
	public boolean match( final String name , final String key , final String val ) {
		// name が存在するが、不一致の場合は、false
		if( name != null && ! name.equals( qName ) ) { return false; }

		// attri が null なのに、key か val が、null でない場合は合致しないので、false と判断
		if( attri == null && ( key != null || val != null ) ) { return false; }

		// キーが存在し、値も存在する場合は、その値の合致と同じ結果となる。
		if( key != null ) {
			if( val != null ) { return val.equals( attri.getVal( key ) ); }		// 値があれば、比較する。
			else              { return attri.getAdrs( key ) >= 0 ;     }		// 値がなければ、存在チェック
		}

		// 値が存在する場合は、その値が含まれるかチェックし、あれば、true, なければ false
		if( val != null ) {
			boolean flag = false;
			int len = attri.size();
			for( int i=0; i<len; i++ ) {
				if( val.equals( attri.getVal(i) ) ) { flag = true; break; }
			}
			return flag;
		}

		// 上記の条件以外は、すべてが null なので、true
		return true;
	}

	/**
	 * 段落文字列を返します。
	 *
	 * 段落文字列は、階層を表す文字列です。
	 * 通常は　TAB ですが、XMLの階層が、PARA_LEN を超えても、段落を増やしません。
	 * 段落の最初の文字は、改行です。
	 *
	 * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
	 * @og.rev 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
	 *
	 * @param	cnt	階層(-1:なし。
	 * @return	段落文字列
	 * @see OGNodeType
	 */
	private String getPara( final int cnt ) {
		if( cnt < 0 ) { return ""; }
		if( cnt < PARA_LEN ) {	return PARA[cnt]; }
		else {					return  PARA[PARA_LEN-1]; }			// 5.6.4.4 (2013/05/31) PARA_LEN を超えても、段落を増やしません。
	}

	/**
	 * オブジェクトの文字列表現を返します。
	 *
	 * 文字列は、OGNodeType により異なります。
	 * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
	 * つけて出力します。
	 *
	 * @og.rev 5.6.1.2 (2013/02/22) 内部テキストがない場合のタグの終了時にスペースは入れない。
	 * @og.rev 5.6.4.4 (2013/05/31) 改行３つを改行２つに置換します。
	 *
	 * @param	cnt		Nodeの階層（-1:なし、0:改行のみ、1:改行＋"  "・・・・）
	 * @return	このオブジェクトの文字列表現
	 * @see OGNode#toString()
	 */
	@Override
	public String getText( final int cnt ) {
		StringBuilder buf = new StringBuilder();

		buf.append( getPara(cnt) );
		buf.append( "<" ).append( qName );

		buf.append( attri.getText( getPara(cnt+1) ) );

		String text = super.getText(cnt+1);

		if( text.trim().isEmpty() ) {
			buf.append( "/>" );					// 5.6.1.2 (2013/02/22) タグの終了時にスペースは入れない。
		}
		else {
			buf.append( ">" ).append( text );
			buf.append( getPara(cnt) );
			buf.append( "</" ).append( qName ).append( ">" );
		//	buf.append( CR );
		}
		String rtn = buf.toString();

		switch( getNodeType() ) {
			case Comment:	rtn = "<!-- "      + rtn + " -->"; break;
			case Cdata:		rtn = "<![CDATA[ " + rtn + " ]]>"; break;
	//		case Text:
	//		case List:
			default:		break;
		}

		return rtn.replaceAll( CR+CR+CR , CR+CR ) ;			// 改行３つを改行２つに置換します。
	}
}
