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

/**
 * ノードの基底クラスとなる、OGNode クラスを定義します。
 *
 * OGElement、OGDocument は、この、OGNode クラスを継承します。
 * ただし、OGAttributes は、独立しているため、このクラスは継承していません。
 * 
 * 最も一般的なノードは、テキストノードであり、
 *
 * OGNode は、enum OGNodeType で区別される状態を持っています。
 * その内、OGElement と OGDocument は、サブクラスになっています。
 * OGNodeType は、それぞれ、再設定が可能です。
 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
 * ファイル等への出力時にコメントとして出力されます。
 *
 * 　　List　　　:内部に、OGNode の ArrayList を持つ
 * 　　Text　　　:内部は、文字列の BODY 部分を持つ
 * 　　Comment 　:内部は、文字列であるが、toString() 時には、コメント記号を前後に出力する。
 * 　　Cdata 　　:内部は、TextNodeのArrayList を持つ、toString() 時には、Cdataを前後に出力する。
 * 　　Element 　:タグ名、属性、OGNode の ArrayList の入れ子状態をもつ
 * 　　Document　:トップのElement として、read/write するときに使用。構造は、唯一の OGElement を持つ List タイプ
 *
 * @og.rev 5.1.8.0 (2010/07/01) 新規作成
 *
 * @version  5.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK6.0,
 */
public class OGNode {
	// 内部で使用する タグの開始場所以前の タブ を規定するための文字列です。（toString()時のフォーマット用）
//	private final static String   TABS = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" ;		// 5.1.9.0 (2010/08/01) 廃止

//	private static int NODE_NO = 0;				// OGNode クラスのstatic なカウンター
//	private final  int nodeNo  = NODE_NO++;		// 自身のオブジェクトに割り当てられたカウンター値
	public static final String CR = System.getProperty("line.separator");

	private final List<OGNode> nodes = new ArrayList<OGNode>();		// ノードリスト
	private final String	text;									// テキストノード用の文字列ノード値
	private OGNodeType		nodeType;								// List,Text,Comment,Cdata,Element,Document 
	private OGNode 			parentNode = null;						// 自身の親ノード(ただし、最終セットされたノード)

	/**
	 * デフォルトコンストラクター
	 *
	 * ここでは、NodeType は、List に設定されます。
	 */
	public OGNode() {
		this.text  = null;
		nodeType   = OGNodeType.List;
	}

	/**
	 * テキストノードを構築するためのコンストラクター
	 *
	 * テキストノードは、簡易的に、内部には、ノードリストではなく文字列を持っています。
	 *
	 * ここでは、NodeType は、Text に設定されます。
	 *
	 * @param txt String テキストノードの設定値
	 */
	public OGNode( final String txt ) {
		text		= ( txt == null ) ? "" : txt ;
		nodeType	= OGNodeType.Text;
	}

	/**
	 * テキストノードをノードリストに追加します。
	 *
	 * 内部的にテキストノードを構築して、リストに追加しています。
	 * 戻り値は、StringBuilder#append(String) の様に、連結登録できるように
	 * 自分自身を返しています。
	 *
	 * @param txt String テキストノードの設定値
	 * @return OGNode 自分自身(this)
	 */
	public OGNode addNode( final String txt ) {
		OGNode node = new OGNode( txt );
		node.parentNode = this;
		nodes.add( node );
		return this;
	}

	/**
	 * ノードをノードリストに追加します。
	 *
	 * 追加するノードの親として、自分自身を登録します。
	 * なお、同じオブジェクトを、複数の親に追加する場合（ノードリストには追加可能）は、
	 * 親ノードは、最後に登録されたノードのみが設定されます。
	 *
	 * @param node OGNode ノード
	 * @return OGNode 自分自身(this)
	 */
	public OGNode addNode( final OGNode node ) {
		node.parentNode = this;
		nodes.add( node );
		return this;
	}

	/**
	 * タブノードをノードリストに追加します。
	 *
	 * タブノードとは、引数の個数だけの タブ文字列のみのノードです。
	 * 改行＋タブ が登録されます。
	 * これは、toString() 時に、XML が整形された形で出すときに、使用します。
	 * 引数が、0 の場合は、改行のみのテキストノードが追加されます。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 廃止
	 *
	 * @param tab int タブの個数（０から９まで）
	 * @return OGNode 自分自身(this)
	 */
//	public OGNode addTabNode( final int tab ) {
//		nodes.add( new OGNode( TABS.substring( 0,tab ) ) );
//		return this;
//	}

	/**
	 * ノードの追加時に、タブノードも追加する簡易メソッドです。
	 *
	 * タブノード＋addNode(OGNode) を一つのメソッドで行うための簡易メソッドです。
	 *
	 * @og.rev 5.1.9.0 (2010/08/01) 廃止
	 *
	 * @param tab int タブの個数（０から９まで）
	 * @param node OGNode ノード
	 * @return OGNode 自分自身(this)
	 */
//	public OGNode addTabNode( final int tab , final OGNode node ) {
//		nodes.add( new OGNode( TABS.substring( 0,tab ) ) );
//		node.parentNode = this;
//		nodes.add( node );
//		return this;
//	}

	/**
	 * ノードリストに追加されている、ノードの個数を返します。
	 *
	 * @return int ノードリストの数
	 */
	public int nodeSize() {
		return nodes.size();
	}

	/**
	 * ノードリストに追加されている、ノードを返します。
	 *
	 * ノードの指定には、配列番号を使用します。
	 * ノードの個数は、事前に、nodeSize() で調べて置いてください。
	 *
	 * @param adrs int ノードリストの位置
	 * @return OGNode ノード
	 */
	public OGNode getNode( final int adrs ) {
		return nodes.get(adrs);
	}

	/**
	 * ノードリストに、ノードをセットします。
	 *
	 * ノードリストの指定のアドレスに、ノードをセットします。
	 * これは、追加ではなく置換えになります。
	 * ノードの指定には、配列番号を使用します。
	 * ノードの個数は、事前に、nodeSize() で調べて置いてください。
	 *
	 * @param adrs int ノードリストの位置
	 * @param node OGNode セットするノード
	 */
	public void setNode( final int adrs , final OGNode node ) {
		nodes.set(adrs,node);
	}

	/**
	 * 自身にセットされている、親ノードを返します。
	 *
	 * 親ノードは、自身のオブジェクトに、一つしか設定できません。
	 * これは、オブジェクトとして、同一ノードを、複数の親ノードに
	 * 追加した場合（これは、ノードリストへの追加なので可能）最後に追加した
	 * 親ノードのみ、保持していることになります。
	 * XML を構築するときは、同一のノードであっても、毎回、作成しなおさないと、
	 * 親ノードを見つけて、何かを行う場合には、おかしな動きをすることになります。
	 * なお、ノードオブジェクト自体が、親ノードから削除されても、自身の
	 * 親ノード情報は保持し続けています。
	 * ある Element から削除したノードを別のElementに追加すると、その時点で、
	 * 親ノードも更新されます。
	 *
	 * @return OGNode 親ノード
	 */
	public OGNode getParentNode() {
		return parentNode;
	}

	/**
	 * ノードリストから、指定の配列番号の、ノードを削除します。
	 *
	 * ノードの指定には、配列番号を使用します。
	 * ノードの個数は、事前に、nodeSize() で調べて置いてください。
	 *
	 * @param adrs int ノードリストの位置
	 * @return OGNode 削除されたノード
	 */
	public OGNode removeNode( final int adrs ) {
		return nodes.remove(adrs);
	}

	/**
	 * ノードリストから、すべてのノードを削除します。
	 *
	 * これは、ノードリストをクリアします。
	 *
	 */
	public void clearNode() {
		nodes.clear();
	}

	/**
	 * ノードリストから、指定のノード(orgNode)を新しいノード(newNode)に置き換えます。
	 *
	 * ノードは、それぞれ、ノードが作成された順番で、ユニークな番号を持っています。
	 * その番号を元に、ノードを探し出して、置き換えます。
	 * 通常の、XMLパースから作成されたノードは、すべて一意にユニーク番号が振られますが、
	 * 新しくつったノードを複数のノードと置き換える場合、置き換えられた後のノードは、
	 * オブジェクトそのものが、同一になるため、注意が必要です。
	 *
	 * @param orgNode OGNode 置換元のオリジナルノード
	 * @param newNode OGNode 置換する新しいノード
	 */
	public void changeNode( final OGNode orgNode , final OGNode newNode ) {
		int size = nodes.size();
		for( int i=0; i<size; i++ ) {
			OGNode node = nodes.get(i);
//			if( node.nodeNo == orgNode.nodeNo ) {
			if( node.equals( orgNode ) ) {		// Object.equals なので、オブジェクトそのものの一致判定
				nodes.set( i,newNode );
			}
			else {
				node.changeNode( orgNode,newNode );
			}
		}
	}

	/**
	 * ノードリストから、直下(メンバー)のエレメントのみをリストにして返します。
	 *
	 * ノードリストの第一レベルで、エレメントのみを返します。
	 * 通常は、あるエレメントを、getElementList( String ) 等で検索した後、その子要素を
	 * 取り出す場合に使用します。
	 * 該当するエレメントが、なにも存在しない場合は、空のリストオブジェクトが返されます。
	 *
	 * @return List<OGElement> 直下(メンバー)のエレメントのリスト
	 */
	public List<OGElement> getChildElementList() {
		List<OGElement> eles = new ArrayList<OGElement>();

		for( OGNode node : nodes ) {
			if( node.nodeType == OGNodeType.Element ) {
				eles.add( (OGElement)node );
			}
		}

		return eles;
	}

	/**
	 * ノードリストから、下位の階層に存在するすべてのエレメントをリストにして返します。
	 *
	 * エレメントは、名前を指定して検索します。
	 * 該当するエレメントが、なにも存在しない場合は、空のリストオブジェクトが返されます。
	 *
	 * @param qName String エレメントの名前
	 * @return List<OGElement> 下位の階層に存在するすべてのエレメントのリスト
	 */
	public List<OGElement> getElementList( final String qName ) {
		List<OGElement> eles = new ArrayList<OGElement>();

		if( qName != null ) {
			for( OGNode node : nodes ) {
				if( node.nodeType == OGNodeType.Element ) {
					OGElement ele = (OGElement)node;
					if( qName.equals( ele.getTagName() ) ) {
						eles.add( ele );
					}
					eles.addAll( ele.getElementList( qName ) );
				}
			}
		}

		return eles;
	}

	/**
	 * ノードリストの文字列を返します。
	 *
	 * これは、タグで言うところのBODY部に書かれた文字列に相当します。
	 * 該当する文字列が、存在しない場合は、空の文字列(ゼロストリング)が返されます。
	 *
	 * @return String ノードリストの文字列（BODY部に書かれた文字列）
	 */
	public String getText() {
		final String rtn ;

		if( text != null ) {
			rtn = text;
		}
		else {
			StringBuilder buf = new StringBuilder();
			for( OGNode node : nodes ) {
				buf.append( node.toString() );
			}
			rtn = buf.toString();
		}

		return rtn ;
	}

	/**
	 * ノードタイプを設定します。
	 *
	 * ノードタイプとは、List , Text , Comment , Cdata , Element , Document などの
	 * ノードの種別を表す enum タイプです。
	 * 基本的には、オブジェクトの取得時に、ファクトリメソッド経由であれば、自動的に設定
	 * されています。
	 * ここでは、可変設定できます。
	 * 例えば、既存のエレメントやノードに対して、コメントタイプ(Comment)を指定すると、
	 * ファイル等への出力時にコメントとして出力されます。
	 * null を指定すると、なにも処理されません。
	 *
	 * @param type OGNodeType enum のOGNodeType
	 * @see OGNodeType
	 */
	public void setNodeType( final OGNodeType type ) {
		if( type != null ) { nodeType = type ; }
	}

	/**
	 * ノードタイプを取得します。
	 *
	 * ノードタイプとは、List , Text , Comment , Cdata , Element , Document などの
	 * ノードの種別を表す enum タイプです。
	 * 基本的には、オブジェクトの取得時に、ファクトリメソッド経由であれば、自動的に設定
	 * されています。
	 *
	 * @return OGNodeType ノードタイプ
	 * @see OGNodeType
	 */
	public OGNodeType getNodeType() {
		return nodeType;
	}

	/**
	 * このオブジェクトと「等価」になるオブジェクトがあるかどうかを示します。
	 *
	 * ここでの等価とは、内部ユニークキーが一致しているかどうかで判定します。
	 * 内部の文字列や、タグ名、属性などの一致ではありません。
	 * ノードは、XMLから構築するときに、すべてに一意菜ユニーク番号を振ります。
	 * この番号に基づいて、XMLツリーの途中から検索したノードの置換処理などを行います。
	 * 結果的には、Object#equals(Object) と同等の結果を得ることになります。
	 *
	 * @param node OGNode 比較対象の参照オブジェクト
	 * @return boolean
	 * @see Object#equals(Object)
	 */
//	public boolean equals( final OGNode node ) {
//		return ( node != null && node.nodeNo == nodeNo ) ;
//	}

	/**
	 * オブジェクトのハッシュコード値を返します。
	 *
	 * ハッシュコード値は、内部ユニークキーそのものです。
	 * 結果的には、Object#hashCode() と同等の結果を得ることになります。
	 *
	 * @return int このオブジェクトのハッシュコード値
	 * @see Object#hashCode()
	 */
//	public int hashCode() {
//		return nodeNo ;
//	}

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

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

		return rtn ;
	}
}
