/*
このファイルの中身は、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) ______________________________________.
*/
package jp.co.ntt.lms.installer.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.xpath.XPathAPI;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * ＸＭＬの一般的なユーティリティ・クラス。
 * @author T.Nishiki
 */
public class XmlUtil {
	/**
	 * ログ出力オブジェクト。
	 */
	private static Logger _logger;

	/**
	 * スタティック・イニシャライザ。<br>
	 * ログ出力オブジェクトを取得します。
	 */
	static {
		_logger = LoggerUtil.getLogger(XmlUtil.class);
	}


	/**
	 * 指定されたＸＭＬリソースからDOM Documentに変換します。<br>
	 * @param resourceURI ＸＭＬリソースが存在するURI。
	 * @return xmlStreamから生成されるDOM Document。
	 * @throws ParserConfigurationException DocumentBuilderを生成できない場合。
	 * @throws SAXException ストリームからのデータがXMLとして不正な場合。
	 * @throws IOException ストリームからの入力でエラーが発生した場合。
	 */
	public static Document convertXmlResourceToDocument(String resourceURI)
			throws ParserConfigurationException
			, SAXException, IOException  {
		// メソッド開始ログ
		LoggerUtil.methodHead(
			_logger, "convertXmlResourceToDocument(" +  resourceURI + ")");

		Document document = null;
		InputStream stream = null;
		// tryブロックの処理が成功したか示す。成功ならtrue。
		boolean successFlag = false;
		try {
			ClassLoader loader = ClassLoader.getSystemClassLoader();
			stream = loader.getResourceAsStream(resourceURI);
			document = XmlUtil.convertXmlStreamToDocument(stream);
			// tryブロック終端まで到達したら成功
			successFlag = true;
		}
		finally {
			FileUtil.close(stream, successFlag);
		}
		// メソッド終了ログ
		LoggerUtil.methodTail(
			_logger, "convertXmlResourceToDocument(" +  resourceURI + ")",
			document);
		return document;
	}

	/**
	 * 指定されたＸＭＬストリームからDOM Documentに変換します。<br>
	 * @param xmlStream ＸＭＬストリーム。
	 * @return xmlStreamから生成されるDOM Document。
	 * @throws ParserConfigurationException DocumentBuilderを生成できない場合。
	 * @throws SAXException ストリームからのデータがXMLとして不正な場合。
	 * @throws IOException ストリームからの入力でエラーが発生した場合。
	 */
	public static Document convertXmlStreamToDocument(InputStream xmlStream)
			throws ParserConfigurationException
			, SAXException, IOException  {
		// メソッド開始ログ
		LoggerUtil.methodHead(
			_logger, "convertXmlStreamToDocument(InputStream)");

		Document document = null;
		DocumentBuilderFactory factory
			= DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		document = builder.parse(xmlStream);
		// メソッド終了ログ
		LoggerUtil.methodTail(
			_logger, "convertXmlStreamToDocument(InputStream)");
		return document;
	}

	/**
	 * 指定されたＸＭＬファイルからDOM Documentに変換します。<br>
	 * @param file ＸＭＬファイル。
	 * @return fileから生成されるDOM Document。
	 * @throws ParserConfigurationException DocumentBuilderを生成できない場合。
	 * @throws SAXException fileの内容がXMLとして不正な場合。
	 * @throws IOException fileからの入力でエラーが発生した場合。
	 */
	public static Document convertXmlFileToDocument(File file)
			throws ParserConfigurationException
			, SAXException, IOException  {
		// メソッド開始ログ
		LoggerUtil.methodHead(
			_logger, "convertXmlStreamToDocument(" + file + ")");

		Document document = null;
		try {
			DocumentBuilderFactory factory
				= DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.parse(file);
		}
		catch (IOException e) {
			LoggerUtil.throwable(_logger, e);
			throw e;
		}
		// メソッド終了ログ
		LoggerUtil.methodTail(
			_logger, "convertXmlStreamToDocument(" + file + ")");
		return document;
	}

	/**
	 * DOM DocumentをXMLファイルに変換・保存します。<br>
	 *
	 * @param doc 保存するDOM Document。
	 * @param file 保存先となるファイルを表すFileオブジェクト。
	 * @param outputProperties 変換プロパティ。
	 * @throws TransformerConfigurationException
	 *          変換するモジュールの生成ができなかった場合。
	 * @throws TransformerException 変換処理が行えなかった場合。
	 */
	public static void convertDocumentToFile(
			Document doc, File file, Properties outputProperties)
			throws TransformerException, TransformerConfigurationException {

		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(file);
		TransformerFactory factory = TransformerFactory.newInstance();
		Transformer transformer = factory.newTransformer();
		if (outputProperties != null) {
			transformer.setOutputProperties(outputProperties);
		}
		transformer.transform(source, result);
	}
	/**
	 * 指定したノードの値を取得します。<br>
	 * XPathを利用して、指定した基点ノードから指定したパスでノードを検索し、
	 * 検索して見つかったノードの値を戻します。<br>
	 * @param originNode 基点となるノード。
	 * @param locationPath 検索するためロケーション・パス。
	 * @return 対象のノードが見つかった場合、そのノードの値を戻します。
	 *          見つからなかった場合、nullを戻します。
	 * @throws DOMException XMLデータ操作中に例外が発生した場合。
	 * @throws TransformerException 指定したパスがロケーション・パスが
	 *          誤っているなど検索中に例外が発生した場合。
	 */
	public static String getNodeValue(Node originNode, String locationPath)
			throws DOMException, TransformerException {
		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"getNodeValue(" + originNode + "," + locationPath + ")");
		// このメソッドの戻り値
		String nodeValue = null;
		try {
			Node targetNode = XPathAPI.selectSingleNode(
									originNode, locationPath);
			if ( targetNode != null ) {
				nodeValue = targetNode.getNodeValue();
			}
		}
		catch (DOMException e) {
			LoggerUtil.throwable(_logger, e);
			throw e;
		}
		catch (TransformerException e) {
			LoggerUtil.throwable(_logger, e);
			throw e;
		}
		// メソッド終了ログ
		LoggerUtil.methodTail( _logger,
			"getNodeValue(" + originNode + "," + locationPath + ")",
			nodeValue);
		return nodeValue;
	}

	/**
	 * 指定したノードの値を取得します。<br>
	 * XPathを利用して、指定した基点ノードから指定したパスでノードを検索し、
	 * 検索して見つかったノードの値を戻します。<br>
	 * @param originNode 基点となるノード。
	 * @param locationPath 検索するためロケーション・パス。
	 * @return 対象のノードが見つかった場合、それらノードの値を配列戻します。
	 *          見つからなかった場合、空要素配列として戻します。
	 * @throws DOMException XMLデータ操作中に例外が発生した場合。
	 * @throws TransformerException 指定したパスがロケーション・パスが
	 *          誤っているなど検索中に例外が発生した場合。
	 */
	public static String[] getNodeValues(Node originNode, String locationPath)
			throws DOMException, TransformerException {
		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"getNodeValues(" + originNode + "," + locationPath + ")");
		// このメソッドの戻り値
		String[] nodeValues = null;
		try {
			List nodeValuesList = new ArrayList();
			NodeList targetNodes = XPathAPI.selectNodeList(
									originNode, locationPath);
			for (int i = 0; i < targetNodes.getLength(); i++) {
				Node node = targetNodes.item(i);
				nodeValuesList.add(node.getNodeValue());
			}
			nodeValues = new String[nodeValuesList.size()];
			nodeValuesList.toArray(nodeValues);
		}
		catch (DOMException e) {
			throw e;
		}
		catch (TransformerException e) {
			throw e;
		}
		// メソッド終了ログ
		LoggerUtil.methodTail( _logger,
				"getNodeValues(" + originNode + "," + locationPath + ")",
				nodeValues);
		return nodeValues;
	}

	/**
	 * 指定されたENTITYの値を取得します。
	 * @param document DOM Document。
	 * @param entityName 取得するENTITYの名称。
	 * @return 該当するENTITYが存在すればその値。存在しなければnull。
	 */
	public static String getEntityValue(
			Document document, String entityName) {
		// メソッド開始ログ
		LoggerUtil.methodHead(_logger,
			"getEntityValue(" + document + "," + entityName + ")");
		// ENTITY値（このメソッドの戻り値）
		String entityValue = null;
		// ENTITYがあるDTDを取得
		DocumentType documentType = document.getDoctype();
		if (documentType != null) {
			// すべてENTITYを取得し、１つ１つ確認する。
			NamedNodeMap nodeMap = documentType.getEntities();
			int length = nodeMap.getLength();
			for (int i = 0; i < length; i++) {
				Node entity = nodeMap.item(i);
				// 名称が違う場合、無視
				if (entityName.equals(entity.getNodeName()) == false) {
					continue;
				}
				// 対象のENTITYだった場合、TEXTノードを取得
				NodeList nodeList = entity.getChildNodes();
				for (int j = 0; j < nodeList.getLength(); j++) {
					Node entityChild = nodeList.item(j);
					// TEXTノードが見つかれば終了
					if (entityChild.getNodeType() == Node.TEXT_NODE) {
						entityValue = entityChild.getNodeValue();
						break;
					}
				}
				break;
			}
		}
		// メソッド開始ログ
		LoggerUtil.methodTail(_logger,
			"getEntityValue(" + document + "," + entityName + ")",
			entityValue);
		return entityValue;
	}
}
