/*
 * 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;
import java.util.List;

/**
 * エレメントをあらわす、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 {

//	/** 特殊：内部的に、タグ属性の改行処理を行うタグ名の Map(Set)を持っています。 **/
//	private static final Set<String> CR_SET = new HashSet<String>();
//	// 初期処理
//	static {
//		CR_SET.add( "og:comment" );
//		CR_SET.add( "og:view" );
//	//	CR_SET.add( "og:link" );
//	}

	// 5.2.1.0 (2010/10/01) nameSpace 対応(og: や mis:)
	/** 特殊：内部的に、タグ属性の改行処理を行うタグ名の String配列を持っています。 **/
	private static final String[] CR_SET = new String[] { ":comment" , ":view" };

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

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

	/**
	 * ノード名を指定してのトコンストラクター
	 *
	 * ノード名のみ指定するため、属性と、ノードリストが空のエレメントを構築します。
	 *
	 * @param	qName	ノード名
	 * @param	attri	属性オブジェクト
	 */
	public OGElement( final String qName , final OGAttributes attri ) {
		super();
		setNodeType( OGNodeType.Element );

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

		this.qName = qName;
		this.attri = attri ;
	}

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

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

		this.qName    = qName;

		// 5.2.1.0 (2010/10/01) nameSpace の取得。CR_SET を SetからString[] に変更。
//		boolean useCR = CR_SET.contains( qName );
		boolean useCR = false;
		for( int i=0; i<CR_SET.length; i++ ) {
			if( qName.indexOf( CR_SET[i] ) >= 0 ) { useCR = true; break; }
		}
		this.attri = new OGAttributes( attTab , atts , useCR ) ;
	}

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

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

	/**
	 * 属性リストの個数を返します。
	 *
	 * @return	属性リストの個数
	 */
//	public int getAttSize() {
//		return attri.size();
//	}

	/**
	 * 指定のアドレスの属性リストのキーを返します。
	 *
	 * @param	adrs	属性リストのアドレス
	 *
	 * @return	属性リストのキー
	 */
//	public String getKey( final int adrs ) {
//		return attri.getKey( adrs );
//	}

	/**
	 * 指定のアドレスの属性リストの値を返します。
	 *
	 * @param	adrs	属性リストのアドレス
	 *
	 * @return	属性リストの値
	 */
//	public String getVal( final int adrs ) {
//		return attri.getVal( adrs );
//	}

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

	/**
	 * 属性リストから、指定の属性キーの、アドレスを取得します。
	 *
	 * どちらかというと、キーの存在チェックに近い処理を行います。
	 * この処理は、属性リストをすべてスキャンして、キーにマッチする
	 * 属性オブジェクトを見つけ、そこから、属性値を取り出すので、
	 * パフォーマンスに問題があります。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 新規追加
	 *
	 * @param	key	属性キー
	 *
	 * @return	アドレス キーが存在しない場合は、-1 を返す。
	 */
//	public int getAdrs( final String key ) {
//		return attri.getAdrs( key );
//	}

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

	/**
	 * 指定のアドレスの属性リストの値を設定します。
	 *
	 * これは、キー指定ではなく、アドレス指定で、属性値の書き換えを行います。
	 *
	 * @param	adrs	属性リストのアドレス
	 * @param	val	属性リストの値
	 */
//	public void setVal( final int adrs , final String val ) {
//		attri.setVal( adrs,val ) ;
//	}

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

	/**
	 * 指定のアドレスの属性リストに、属性(キー、値のセット)を設定します。
	 *
	 * 指定のアドレスの属性を置き換えるのではなく追加します。
	 *
	 * @param	adrs	属性リストのアドレス
	 * @param	key	属性リストのキー
	 * @param	val	属性リストの値
	 */
//	public void addAttr( final int adrs , final String key , final String val ) {
//		attri.add( adrs,key,val ) ;
//	}

	/**
	 * 指定のアドレスの属性リストから、属性情報を削除します。
	 *
	 * 指定のアドレスの属性を置き換えるのではなく追加します。
	 *
	 * @param	adrs	属性リストのアドレス
	 */
//	public void removeAttr( final int adrs ) {
//		attri.remove( adrs ) ;
//	}

	/**
	 * 自分自身の状態が、指定の条件に合致しているかどうか、判定します。
	 *
	 * 合致している場合は、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; }

		// キーが存在し、値も存在する場合は、その値の合致と同じ結果となる。
		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;
	}

	/**
	 * オブジェクトの文字列表現を返します。
	 *
	 * 文字列は、OGNodeType により異なります。
	 * Comment ノードの場合は、コメント記号を、Cdata ノードの場合は、CDATA を
	 * つけて出力します。
	 *
	 * @return	このオブジェクトの文字列表現
	 * @see OGNode#toString()
	 */
	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();

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

		buf.append( attri.toString() );

		String text = getText();

		if( text.trim().isEmpty() ) {
			buf.append( " />" );
		}
		else {
			buf.append( ">" ).append( text ).append( "</" ).append( qName ).append( ">" );
		}

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

		return rtn ;
	}
}
