/*
 * XMLPath.java
 * Copyright (C) 2008 Cyber Beans Corporation. All rights reserved.
 */
package jp.co.cybec.cb3.accessor.fileaccess.xml;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import jp.co.cybec.cb3.exception.AccessorUnknownException;
import jp.co.cybec.cb3.exception.BadXmlGrammerException;
import jp.co.cybec.cb3.util.logging.ErrorCodeConstant;
import jp.co.cybec.cb3.util.logging.LogMessageConstant;
import jp.co.cybec.cb3.util.logging.LogMessageFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlObject;

/**
 * XMLファイルのXPathを表すクラス。
 * @author Daisuke Suga
 */
public class XMLPath {

	/** ログ */
	private static final Log LOG = LogFactory.getLog(XMLPath.class);

	/** メッセージ */
	private static final LogMessageFactory MESSAGE = new LogMessageFactory(
			LogMessageConstant.CB3_MESSAGE_FILENAME);

	/**
	 * アクセッサのキー名。
	 */
	private String key = null;

	/**
	 * 検索パス。
	 */
	private String path = null;

	/**
	 * XMLファイルのXmlCursor。
	 */
	private XmlCursor cursor = null;

	/**
	 * selectChildPathで検索した子のXmlCursorを保持するリスト。
	 */
	private List<XmlCursor> childCursorList = new ArrayList<XmlCursor>();

	/** 処理中の行番号 */
	private int row = 0;

	/**
	 * コンストラクタ。
	 */
	protected XMLPath() {
		super();
	}

	/**
	 * コンストラクタ。<br>
	 * XMLファイルのXmlCursorを作成し、指定されたパスで検索します。
	 *
	 * @param key アクセッサのキー名
	 * @param xml XmlObject
	 * @param path 検索パス
	 * @throws BadXmlGrammerException XMLの文法が間違っているときに発生します。
	 * @throws AccessorUnknownException アクセッサで不明のエラーがおきたときに発生します。
	 */
	protected XMLPath(String key, XmlObject xml, String path) throws BadXmlGrammerException,
			AccessorUnknownException {

		// アクセッサのキー名を保持します。
		this.key = key;

		// 検索パスを保持します。
		this.path = path;

		// 新しいカーソルを作成します。
		try {
			cursor = xml.newCursor();
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_036,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// 指定されたパスで検索します。
		try {
			cursor.selectPath(path);
		} catch (Exception e) {
			BadXmlGrammerException ex = new BadXmlGrammerException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_037,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_002, key, path), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

	}

	/**
	 * コンストラクタ。<br>
	 * 指定されたXmlCursorで初期化します。
	 *
	 * @param key アクセッサのキー名
	 * @param cursor XmlCursor
	 * @param path 検索パス
	 */
	protected XMLPath(String key, XmlCursor cursor, String path) {

		// アクセッサのキー名を保持します。
		this.key = key;

		// 検索パスを保持します。
		this.path = path;

		// カーソルを設定します。
		this.cursor = cursor;
	}

	/**
	 * 次のXMLElementを取得します。<br>
	 * 取得するXMLElementがない場合はnullを返します。
	 *
	 * @return XMLElement
	 * @throws AccessorUnknownException アクセッサで不明のエラーがおきたときに発生します。
	 */
	public XMLElement next() throws AccessorUnknownException {

		try {

			// 行番号をインクリメント。
			row++;

			// 子の検索を行っていた場合は、すべての子のカーソルをクローズします。
			for (XmlCursor childCursor : childCursorList) {
				childCursor.dispose();
			}
			childCursorList.clear();

			// 次のXMLElementがない場合はnullを返します。
			if (!cursor.hasNextSelection()) {
				return null;
			}

			cursor.toNextSelection();
			cursor.push();

			XMLElement element = new XMLElement(cursor.getName().getLocalPart(), key, path);

			// 値を設定します。
			element.setString(cursor.getTextValue());

			// 属性の値を設定します。
			if (cursor.toFirstAttribute()) {
				do {
					String name = cursor.getName().getLocalPart();
					element.putAttribute(new XMLAttribute(name, cursor.getTextValue(), element
							.getName(), key, path));
				} while (cursor.toNextAttribute());
			}

			cursor.pop();

			// デバッグログ
			if (LOG.isDebugEnabled()) {
				StringBuilder elementMessage = new StringBuilder();
				elementMessage.append(element.getName());
				elementMessage.append("=");
				elementMessage.append(element.getString());

				StringBuilder attributeMessage = new StringBuilder();
				int index = 0;
				for (Map.Entry<String, XMLAttribute> entry : element.getAttributes().entrySet()) {
					index++;
					if (index > 1) {
						attributeMessage.append(", ");
					}
					attributeMessage.append(entry.getKey());
					attributeMessage.append("=");
					attributeMessage.append(entry.getValue().getString());
				}
				LOG.debug(MESSAGE.getMessage(LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_008,
						key, path, row, elementMessage.toString(), attributeMessage.toString()));
			}

			return element;

		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_038,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

	}

	/**
	 * 現在のノードから指定された子のパスを検索し、XMLPathを返却します。
	 *
	 * @param path 検索するパス
	 * @return XMLPath
	 * @throws BadXmlGrammerException XMLの文法が間違っているときに発生します。
	 * @throws AccessorUnknownException アクセッサで不明のエラーがおきたときに発生します。
	 */
	public XMLPath selectChildPath(String path) throws BadXmlGrammerException,
			AccessorUnknownException {

		// デバッグログ
		if (LOG.isDebugEnabled()) {
			LOG.debug(MESSAGE.getMessage(LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_007, key,
					path));
		}

		// 新しいカーソルを作成します。
		XmlCursor childCursor = null;
		try {
			childCursor = cursor.newCursor();
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_039,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// 子のパスを検索します。
		try {
			childCursor.selectPath(path);
		} catch (Exception e) {
			BadXmlGrammerException ex = new BadXmlGrammerException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_040,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_002, key, path), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		childCursorList.add(childCursor);
		return new XMLPath(key, childCursor, path);

	}

}
