/*
このファイルの中身は、NTT オープンソースライセンス　バージョン 1.0（「本
契約」という）の適用を受けます。
本契約を遵守しない限り、このファイルを使用してはなりません。
本契約のコピーは、次のＵＲＬから入手できます。
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

本契約に基づき頒布されるソフトウェアは、現状のまま、明示又は黙示のあらゆ
る種類の保証なしで、頒布されます。本契約に基づく権利及び制限を律する特定
の文言については、本契約を参照してください。

「オリジナルコード」は、 NTT Cyber Space Laboratories Code です。 
「オリジナルコード」の「初期開発者」は、日本電信電話株式会社です。  
日本電信電話株式会社により創作された部分は、Copyright (C) 2004 
日本電信電話株式会社 です。
全ての権利が留保されます。 
「コントリビュータ」：_____________________________________ 


The contents of this file are subject to the NTT Opensource License
Version 1.0 (the License); you may not use this file except in
compliance with the License. You may obtain a copy of the License at
【配布サイトのURL】 http://www.oss.ecl.ntt.co.jp/lms/

Software distributed under the License is distributed on an AS IS
basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
License for the specific language governing rights and limitations
under the License.

The Original Code is NTT Cyber Space Laboratories Code .

The Initial Developer of the Original Code is NIPPON TELEGRAPH AND 
TELEPHONE CORPORATION.
Portions created by the NIPPON TELEGRAPH AND TELEPHONE CORPORATION 
are Copyright (C) 2004 NIPPON TELEGRAPH AND TELEPHONE CORPORATION. 
All Rights Reserved.

Contributor(s) ______________________________________.
*/

//
//	CachedNode
//	変更履歴
//		2004.02.01	新規作成
//

package jp.co.ntt.lms.lo.scorm.util;

import java.util.ArrayList;
import java.util.Hashtable;

import javax.xml.transform.TransformerException;

import org.w3c.dom.Node;

/**
 *	CachedNode クラス<br>
 *	Nodeに対するXPathの取得結果をキャッシュする<br>
 */
public class CachedNode {

	/** null識別 */
	private static CachedNode[] __null = new CachedNode[0];

	/** null識別 */
	private static CachedNode _null = new CachedNode();

	/** ArrayListからCachedNode[]に変換する為のCachedNode[0] */
	private static CachedNode[] _toSingleArray = new CachedNode[0];

	/** キャッシュするノード */
	private Node node;

	/** XPath文字列と単一ノード選択結果(CacheNode)のマップ */
	private Hashtable nodeCache = new Hashtable();

	/** XPath文字列と複数ノード選択結果(CacheNode[])のマップ */
	private Hashtable nodesCache = new Hashtable();

	/**
	 * デフォルトコンストラクタ
	 */
	private CachedNode() {}

	/**
	 * コンストラクタ
	 *
	 * @param node	xpathの検索対象とするノードを指定する
	 */
	public CachedNode(Node node) {
		this.node = node;
	}

	/**
	 * ノードを取得する
	 *
	 * @return コンストラクタで指定されたNodeを返却する
	 */
	public Node getNode() {
		return node;
	}

	/**
	 * ノードの値を取得する
	 *
	 * @return コンストラクタで指定されたNodeのgetNodeValueメソッドの戻り値を返却する
	 */
	public String getNodeValue() {
		return node.getNodeValue();
	}

	/**
	 * nodeから複数ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する
	 * @return	XPathAPI#selectNodeListから選択できたノードのCachedNode[]。
	 *			XPathAPI#selectNodeListがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectNodeList(String xpath) throws TransformerException {
		return selectNodeList(xpath, true);
	}

	/**
	 * nodeから複数ノードを選択する。
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathAPI#selectSingleNodeから選択できたNodeのCachedNode。
	 *			XPathAPI#selectSingleNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectNodeList(String xpath, boolean cache) throws TransformerException {
		CachedNode[] cachedNodes = (CachedNode[])nodesCache.get(xpath);

		if (cachedNodes != null) {
			if (cachedNodes == __null) {
				return null;
			}

			return cachedNodes;
		}

		Node[] nodes = XPathUtil.selectNodeList(node, xpath);

		if (nodes == null) {
			nodesCache.put(xpath, __null);
			nodeCache.put(xpath, _null);

			return null;
		}

		cachedNodes = new CachedNode[nodes.length];

		for (int i = 0; i < cachedNodes.length; i++) {
			cachedNodes[i] = new CachedNode(nodes[i]);
		}

		if (cache) {
			nodesCache.put(xpath, cachedNodes);
			nodeCache.put(xpath, (cachedNodes.length >= 1) ? cachedNodes[0] : _null);
		}

		return cachedNodes;
	}

	/**
	 * nodeから複数ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleNodeListで
	 *					指定可能な式に限定される
	 * @return	XPathUtil#selectSimpleNodeListから選択できたノードのCachedNode[]。
	 *			XPathUtil#selectSimpleNodeListがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectSimpleNodeList(String xpath) {
		return selectSimpleNodeList(xpath, true);
	}

	/**
	 * nodeから複数ノードを選択する。
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleNodeListで
	 *					指定可能な式に限定される
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathUtil#selectSimpleNodeListから選択できたNodeのCachedNode。
	 *			XPathUtil#selectSimpleNodeListがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectSimpleNodeList(String xpath, boolean cache) {
		CachedNode[] cachedNodes = (CachedNode[])nodesCache.get(xpath);

		if (cachedNodes != null) {
			if (cachedNodes == __null) {
				return null;
			}

			return cachedNodes;
		}

		Node[] result = XPathUtil.selectSimpleNodeList(node, xpath);

		if (result == null) {
			nodesCache.put(xpath, __null);
			nodeCache.put(xpath, _null);

			return null;
		}

		cachedNodes = new CachedNode[result.length];

		for (int i = 0; i < cachedNodes.length; i++) {
			cachedNodes[i] = new CachedNode(result[i]);
		}

		if (cache) {
			nodesCache.put(xpath, cachedNodes);
			nodeCache.put(xpath, (cachedNodes.length >= 1) ? cachedNodes[0] : _null);
		}

		return cachedNodes;
	}

	/**
	 * nodeの子ノードから複数ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectChildNodeListで
	 *					指定可能な式に限定される
	 * @return	XPathAPI#selectChildNodeListから選択できたノードのCachedNode[]。
	 *			XPathAPI#selectChildNodeListがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectChildNodeList(String xpath) {
		return selectChildNodeList(xpath, true);
	}

	/**
	 * nodeの子ノードから複数ノードを選択する。
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectChildNodeListで
	 *					指定可能な式に限定される
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathUtil#selectChildNodeListから選択できたNodeのCachedNode。
	 *			XPathUtil#selectChildNodeListがnullを返却した場合はnullを返却する
	 */
	public CachedNode[] selectChildNodeList(String xpath, boolean cache) {
		CachedNode[] cachedNodes = (CachedNode[])nodesCache.get(xpath);

		if (cachedNodes != null) {
			if (cachedNodes == __null) {
				return null;
			}

			return cachedNodes;
		}

		Node[] result = XPathUtil.selectChildNodeList(node, xpath);

		if (result == null) {
			nodesCache.put(xpath, __null);
			nodeCache.put(xpath, _null);

			return null;
		}

		cachedNodes = new CachedNode[result.length];

		for (int i = 0; i < cachedNodes.length; i++) {
			cachedNodes[i] = new CachedNode(result[i]);
		}

		if (cache) {
			nodesCache.put(xpath, cachedNodes);
			nodeCache.put(xpath, (cachedNodes.length >= 1) ? cachedNodes[0] : _null);
		}

		return cachedNodes;
	}

	/**
	 * nodeから単一ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する
	 * @return	XPathAPI#selectSingleNodeから選択できたNodeのCachedNode。
	 *			XPathAPI#selectSingleNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSingleNode(String xpath) throws TransformerException {
		return selectSingleNode(xpath, true);
	}

	/**
	 * nodeから単一ノードを選択する。<br>
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathAPI#selectSingleNodeから選択できたNodeのCachedNode。
	 *			XPathAPI#selectSingleNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSingleNode(String xpath, boolean cache) throws TransformerException {
		CachedNode cachedNode = (CachedNode)nodeCache.get(xpath);

		if (cachedNode != null) {
			if (cachedNode == _null) {
				return null;
			}

			return cachedNode;
		}

		Node result = XPathUtil.selectSingleNode(node, xpath);

		if (result == null) {
			nodeCache.put(xpath, _null);
			return null;
		}

		cachedNode = new CachedNode(result);

		if (cache) {
			nodeCache.put(xpath, cachedNode);
		}

		return cachedNode;
	}

	/**
	 * nodeから単一ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleSingleNodeで
	 *					指定可能な式に限定される
	 * @return	XPathUtil#selectSimpleSingleNodeから選択できたNodeのCachedNode。
	 *			XPathUtil#selectSimpleSingleNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSimpleSingleNode(String xpath) {
		return selectSimpleSingleNode(xpath, true);
	}

	/**
	 * nodeから複数ノードを選択する。
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleSingleNodeで
	 *					指定可能な式に限定される
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathUtil#selectSimpleSingleNodeから選択できたNodeのCachedNode。
	 *			XPathUtil#selectSimpleSingleNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSimpleSingleNode(String xpath, boolean cache) {
		CachedNode cachedNode = (CachedNode)nodeCache.get(xpath);

		if (cachedNode != null) {
			if (cachedNode == _null) {
				return null;
			}

			return cachedNode;
		}

		Node result = XPathUtil.selectSimpleSingleNode(node, xpath);

		if (result == null) {
			nodeCache.put(xpath, _null);
			return null;
		}

		cachedNode = new CachedNode(result);

		if (cache) {
			nodeCache.put(xpath, cachedNode);
		}

		return cachedNode;
	}

	/**
	 * nodeの子ノードから複数ノードを選択し、キャッシュする
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSingleChildNodeで
	 *					指定可能な式に限定される
	 * @return	XPathAPI#selectSingleChildNodeから選択できたノードのCachedNode[]。
	 *			XPathAPI#selectSingleChildNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSingleChildNode(String xpath) {
		return selectSingleChildNode(xpath, true);
	}

	/**
	 * nodeの子ノードから複数ノードを選択する。
	 * cacheパラメータがtrueの場合は選択結果をキャッシュする<br>
	 * cacheパラメータがfalseの場合は選択結果のキャッシュを行わない<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSingleChildNodeで
	 *					指定可能な式に限定される
	 * @param	cache	検索結果をキャッシュするかどうかを指定する。
	 *					trueが指定された場合はキャッシュする。falseが指定された場合はキャッシュしない。
	 * @return	XPathUtil#selectSingleChildNodeから選択できたNodeのCachedNode。
	 *			XPathUtil#selectSingleChildNodeがnullを返却した場合はnullを返却する
	 */
	public CachedNode selectSingleChildNode(String xpath, boolean cache) {
		CachedNode cachedNode = (CachedNode)nodeCache.get(xpath);

		if (cachedNode != null) {
			if (cachedNode == _null) {
				return null;
			}

			return cachedNode;
		}

		Node result = XPathUtil.selectSingleChildNode(node, xpath);

		if (result == null) {
			nodeCache.put(xpath, _null);
			return null;
		}

		cachedNode = new CachedNode(result);

		if (cache) {
			nodeCache.put(xpath, cachedNode);
		}

		return cachedNode;
	}

	/**
	 * 以下のXPath式と同じ結果の検索を行い、(最初に見つかった)単一のノードを返却する<br>
	 *
	 * 1.valueOfMatchがnullでない場合<br>
	 * &nbsp;&nbsp;&nbsp;&nbsp;%xpath%[%xpathOfTestExpression% = %valueOfMatch%]<br>
	 * 2.valueOfMatchがnullの場合<br>
	 * &nbsp;&nbsp;&nbsp;&nbsp;%xpath%[%xpathOfTestExpression%]<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleNodeListで
	 *					指定可能な式に限定される
	 * @param	xpathOfTestExpression	xpathパラメータで指定されたXPath式の検索結果から
	 *									ノードテストを行う為のXPath式を指定する。XPath式には
	 *									XPathUtil#selectSimpleSingleNodeで指定可能な式に限定される
	 * @param	valueOfMatch	xpathOfTestExpressionパラメータで指定されたノードの値とテストする
	 *							値を指定する。nullを指定した場合はxpathパラメータで指定された
	 *							ノードからxpathOfTestExpressionパラメータで指定されたノードを持つ
	 *							全てのノードを返却する。
	 * @return	検索できたノードのCachedNodeを返却する。検索できなかった場合はnullを返却する
	 */
	public CachedNode selectSingleNodeWithNodeTest(String xpath, String xpathOfTestExpression, String valueOfMatch) {

		CachedNode[] cachedNodes = selectSimpleNodeList(xpath, true);

		if (cachedNodes == null) {
			return null;
		}

		for (int i = 0; i < cachedNodes.length; i++) {
			CachedNode[] testResultNodes = cachedNodes[i].selectSimpleNodeList(xpathOfTestExpression, true);

			if ((testResultNodes != null) && (testResultNodes.length > 0)) {
				if (valueOfMatch == null) {
					return cachedNodes[i];
				}
				else {
					for (int j = 0; j < testResultNodes.length; j++) {
						String testValue = testResultNodes[j].getNodeValue();

						if (testValue != null) {
							if (testValue.equals(valueOfMatch)) {
								return cachedNodes[i];
							}
						}
					}
				}
			}
		}

		return null;
	}

	/**
	 * 以下のXPath式と同じ結果の検索を行い、全てのノードを返却する<br>
	 *
	 * 1.valueOfMatchがnullでない場合<br>
	 * &nbsp;&nbsp;&nbsp;&nbsp;%xpath%[%xpathOfTestExpression% = %valueOfMatch%]<br>
	 * 2.valueOfMatchがnullの場合<br>
	 * &nbsp;&nbsp;&nbsp;&nbsp;%xpath%[%xpathOfTestExpression%]<br>
	 *
	 * @param	xpath	検索を行うXPathを指定する。XPath式にはXPathUtil#selectSimpleNodeListで
	 *					指定可能な式に限定される
	 * @param	xpathOfTestExpression	xpathパラメータで指定されたXPath式の検索結果から
	 *									ノードテストを行う為のXPath式を指定する。XPath式には
	 *									XPathUtil#selectSimpleNodeListで指定可能な式に限定される
	 * @param	valueOfMatch	xpathOfTestExpressionパラメータで指定されたノードの値とテストする
	 *							値を指定する。nullを指定した場合はxpathパラメータで指定された
	 *							ノードからxpathOfTestExpressionパラメータで指定されたノードを持つ
	 *							全てのノードを返却する。
	 * @return	検索できた全てのノードのCachedNode[]を返却する。xpathの検索時に使用する
	 *			CachedNoe#selectSimpleNodeListがnullを返却した場合はnullを返却する。それ以外で
	 *			検索結果が得られなかった場合はlengthが0のCachedNode[]を返却する。
	 */
	public CachedNode[] selectNodeListWithNodeTest(String xpath, String xpathOfTestExpression, String valueOfMatch) {

		CachedNode[] cachedNodes = selectSimpleNodeList(xpath, true);

		if (cachedNodes == null) {
			return null;
		}

		ArrayList result = new ArrayList();

		for (int i = 0; i < cachedNodes.length; i++) {
			CachedNode[] testResultNodes = cachedNodes[i].selectSimpleNodeList(xpathOfTestExpression, true);

			if (testResultNodes != null) {
				if (valueOfMatch == null) {
					result.add(cachedNodes[i]);
				}
				else {
					for (int j = 0; j < testResultNodes.length; j++) {
						String testValue = testResultNodes[j].getNodeValue();

						if (testValue != null) {
							if (testValue.equals(valueOfMatch)) {
								result.add(cachedNodes[i]);
								break;
							}
						}
					}
				}
			}
		}

		return (CachedNode[])result.toArray(_toSingleArray);
	}
}
