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

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Stack;

import jp.co.cybec.cb3.accessor.AccessorsLifeCycle;
import jp.co.cybec.cb3.exception.AccessorAlreadyOpenedException;
import jp.co.cybec.cb3.exception.AccessorNotOpenException;
import jp.co.cybec.cb3.exception.AccessorRuntimeException;
import jp.co.cybec.cb3.exception.AccessorUnknownException;
import jp.co.cybec.cb3.exception.BadXmlGrammerException;
import jp.co.cybec.cb3.exception.ConfigFileDefinitionException;
import jp.co.cybec.cb3.exception.ConfigFileIOException;
import jp.co.cybec.cb3.exception.ConfigFileNotFoundException;
import jp.co.cybec.cb3.exception.ConfigFileParseException;
import jp.co.cybec.cb3.exception.DataFileIOException;
import jp.co.cybec.cb3.exception.DataFileNotFoundException;
import jp.co.cybec.cb3.util.ConfigurationLoader;
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.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;
import org.xml.sax.helpers.NamespaceSupport;

/**
 * XMLファイルの書き込みクラス。
 * @author Daisuke Suga
 */
public class XMLFileWriterImpl implements XMLFileWriter, AccessorsLifeCycle {

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

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

	/**
	 * XML定義ファイルのdigesterルールのファイルパス。
	 */
	private static final String XML_FILE_WRITER_RULE_FILE_NAME = "XMLFileWriterRule.xml";

	/**
	 * デフォルトの改行コード。
	 */
	private static final String DEFAULT_LINE_SEPARATOR = System.getProperty("line.separator");

	/**
	 * デフォルトのエンコード文字列。
	 */
	private static final String DEFAULT_ENCODE = "UTF-8";

	/**
	 * 書き込みの状態−なし。
	 */
	private static final Object SEEN_NOTHING = new Object();

	/**
	 * 書き込みの状態−要素。
	 */
	private static final Object SEEN_ELEMENT = new Object();

	/**
	 * 書き込みの状態−データ。
	 */
	private static final Object SEEN_DATA = new Object();

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

	/**
	 * XML定義ファイル。
	 */
	private XMLFileWriterConfig config = null;

	/**
	 * ファイルのオープン状態を保持するフラグ。
	 */
	private boolean isOpen = false;

	/**
	 * XMLファイルのOutputStreamWriter。
	 */
	private OutputStreamWriter writer = null;

	/**
	 * XMLファイルのBufferedWriter。
	 */
	private Object state = SEEN_NOTHING;

	/**
	 * 書き込みの状態を保持するスタック。
	 */
	private Stack<Object> stateStack = new Stack<Object>();

	/**
	 * インデントの空白文字数。
	 */
	private int indentSpace = 2;

	/**
	 * 要素の深さ。
	 */
	private int depth = 0;

	/**
	 * 名前空間の接頭辞を保持するMap。
	 */
	private Map<String, String> prefixMap = null;

	/**
	 * 名前空間を保持するMap。
	 */
	private Map<String, Boolean> namespaceMap = null;

	/**
	 * 設定された名前空間と接頭辞を保持するMap。
	 */
	private Map<String, String> declaredNamespaceMap = null;

	/**
	 * NamespaceSupport。
	 */
	private NamespaceSupport nsSupport = null;

	/**
	 * {@inheritDoc}
	 */
	public void startElement(XMLElement element) throws AccessorNotOpenException,
			DataFileIOException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_019,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// XMLの要素の開始タグを出力します。
		try {
			startElement(element.getURI(), element.getName(), element.getQName(),
					getAttributes(element));
		} catch (AccessorRuntimeException e) {
			throw e;
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_020,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void endElement(XMLElement element) throws AccessorNotOpenException,
			BadXmlGrammerException, DataFileIOException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_021,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// XMLの要素の終了タグを出力します。
		try {
			endElement(element.getURI(), element.getName(), element.getQName());
		} catch (AccessorRuntimeException e) {
			throw e;
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_022,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void dataElement(XMLElement element) throws AccessorNotOpenException,
			DataFileIOException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_023,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// XMLの要素を書き込みます。
		try {
			dataElement(element.getURI(), element.getName(), element.getQName(),
					getAttributes(element), element.getString());
		} catch (AccessorRuntimeException e) {
			throw e;
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_024,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void characters(XMLElement element) throws AccessorNotOpenException, DataFileIOException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_025,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// XMLの要素の値を書き込みます。
		try {
			characters(element.getString());
		} catch (AccessorRuntimeException e) {
			throw e;
		} catch (Exception e) {
			AccessorUnknownException ex = new AccessorUnknownException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_026,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_006, key), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public void open() throws AccessorAlreadyOpenedException, DataFileNotFoundException,
			DataFileIOException {

		// すでにopenされている場合、例外をスローします。
		if (isOpen) {
			AccessorAlreadyOpenedException ex = new AccessorAlreadyOpenedException(MESSAGE
					.getErrorMessage(ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_027,
							LogMessageConstant.CB3_ACCESSOR_FILEACCESS_002, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		// ファイルパスが設定されていない場合は例外をスローします。
		if (config.getFilepath() == null || config.getFilepath().length() == 0) {
			DataFileNotFoundException ex = new DataFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_042,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_018));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		try {

			// XMLファイルをオープンします。
			File file = new File(config.getFilepath());
			writer = new OutputStreamWriter(new FileOutputStream(file), config.getEncode());

			// 変数の初期化
			init();

			// XMLドキュメントを開始します。
			startDocument();

			// ファイルをオープン状態にします。
			isOpen = true;

		} catch (FileNotFoundException e) {
			DataFileNotFoundException ex = new DataFileNotFoundException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_028,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_003, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		} catch (IOException e) {
			DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_029,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

	}

	/**
	 * {@inheritDoc}
	 */
	public void open(String filepath) throws AccessorAlreadyOpenedException, DataFileNotFoundException,
			DataFileIOException {
		config.setFilepath(filepath);
		open();
	}

	/**
	 * {@inheritDoc}
	 */
	public void close() throws DataFileIOException {

		if (isOpen) {
			try {

				// XMLドキュメントを終了します。
				endDocument();

				// XMLファイルをクローズします。
				writer.flush();
				writer.close();
				writer = null;
				isOpen = false;

			} catch (IOException e) {
				DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
						ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_030,
						LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
				LOG.info(ex.getMessage(), ex);
				throw ex;
			}
		}

	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws ConfigFileNotFoundException 設定ファイルが存在しない場合に発生します。
	 * @throws ConfigFileDefinitionException 設定ファイルの間違いにより発生します。
	 * @throws ConfigFileIOException 設定ファイル読み込み時のIOエラーにより発生します。
	 * @throws ConfigFileParseException 設定ファイルのXMLファイル解析が失敗した時に発生します。
	 */
	public void initialize(String key) throws ConfigFileNotFoundException,
			ConfigFileDefinitionException, ConfigFileIOException, ConfigFileParseException {

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

		// key（設定ファイル名）から設定ファイルを読み込む
		config = (XMLFileWriterConfig) ConfigurationLoader
				.load(XML_FILE_WRITER_RULE_FILE_NAME, key);

		// 設定ファイルに改行コードが設定されていない場合はデフォルトの値を設定します。
		if (config.getLineSeparator() == null || config.getLineSeparator().length() == 0) {
			config.setLineSeparator(DEFAULT_LINE_SEPARATOR);
		} else {
			config.setLineSeparator(config.getLineSeparator().replaceAll("\\\\r", "\r"));
			config.setLineSeparator(config.getLineSeparator().replaceAll("\\\\n", "\n"));
		}

		// 設定ファイルにエンコード文字列が設定されていない場合はデフォルトの値を設定します。
		if (config.getEncode() == null || config.getEncode().length() == 0) {
			config.setEncode(DEFAULT_ENCODE);
		}

		// インデントの空白文字数を設定します。
		if (config.getIndentSpace() != null && config.getIndentSpace().length() > 0) {
			indentSpace = Integer.parseInt(config.getIndentSpace());
		}

	}

	/**
	 * {@inheritDoc}
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	public void release() throws DataFileIOException {
		close();
	}

	/**
	 * 名前空間の定義を追加します。
	 *
	 * @param uri 名前空間のURI
	 * @param prefix 接頭辞
	 * @throws AccessorNotOpenException {@link XMLFileWriterImpl#open()}が呼ばれていない場合に発生します。
	 */
	public void addNamespace(String uri, String prefix) throws AccessorNotOpenException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_031,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		prefixMap.put(uri, prefix);
		addNamespace(uri);
	}

	/**
	 * 名前空間の定義を追加します。
	 *
	 * @param uri 名前空間のURI
	 * @throws AccessorNotOpenException {@link XMLFileWriterImpl#open()}が呼ばれていない場合に発生します。
	 */
	public void addNamespace(String uri) throws AccessorNotOpenException {

		// ファイルがオープンされていない場合、例外をスローします。
		if (!isOpen) {
			AccessorNotOpenException ex = new AccessorNotOpenException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_032,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_001, key));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}

		namespaceMap.put(uri, Boolean.TRUE);
	}

	/**
	 * XMLドキュメントを開始します。
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void startDocument() throws DataFileIOException {
		write("<?xml version=\"1.0\" encoding=\"");
		write(config.getEncode());
		write("\"?>");
		write(config.getLineSeparator());
		write(config.getLineSeparator());
	}

	/**
	 * XMLドキュメントを終了します。
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void endDocument() throws DataFileIOException {
		write(config.getLineSeparator());
	}

	/**
	 * XMLの要素の開始タグを出力します。
	 *
	 * @param uri 名前空間のURI
	 * @param localName 要素の名前
	 * @param qName 要素のQName
	 * @param atts 要素の属性
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void startElement(String uri, String localName, String qName, Attributes atts)
			throws DataFileIOException {
		stateStack.push(SEEN_ELEMENT);
		state = SEEN_NOTHING;
		if (depth > 0) {
			characters(config.getLineSeparator());
		}
		doIndent();
		nsSupport.pushContext();
		write('<');
		writeName(uri, localName, qName, true);
		writeAttributes(atts);
		if (depth == 0) {
			declareNamespace();
		}
		writeNSDecls();
		write('>');
		depth++;
	}

	/**
	 * XMLの要素の終了タグを出力します。
	 *
	 * @param uri 名前空間のURI
	 * @param localName 要素の名前
	 * @param qName 要素のQName
	 * @throws BadXmlGrammerException XMLの文法が間違っているときに発生します。
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void endElement(String uri, String localName, String qName)
			throws BadXmlGrammerException, DataFileIOException {

		if (depth == 0) {
			BadXmlGrammerException ex = new BadXmlGrammerException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_033,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_XML_006, key, localName));
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
		depth--;
		if (state == SEEN_ELEMENT) {
			characters(config.getLineSeparator());
			doIndent();
		}
		write("</");
		writeName(uri, localName, qName, true);
		write('>');
		nsSupport.popContext();
		state = stateStack.pop();

	}

	/**
	 * XMLの要素を書き込みます。<br>
	 * startElementとcharacters、endElementを行います。
	 *
	 * @param uri 名前空間のURI
	 * @param localName 要素の名前
	 * @param qName 要素のQName
	 * @param atts 要素の属性
	 * @param content 要素の値
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void dataElement(String uri, String localName, String qName, Attributes atts,
			String content) throws DataFileIOException {
		startElement(uri, localName, qName, atts);
		characters(content);
		endElement(uri, localName, qName);
	}

	/**
	 * XMLの要素の値を書き込みます。
	 *
	 * @param data 要素の値
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void characters(String data) throws DataFileIOException {
		String writeData = data;
		if (writeData == null) {
			writeData = "";
		}
		char ch[] = writeData.toCharArray();
		characters(ch, 0, ch.length);
	}

	/**
	 * XMLの要素の値を書き込みます。
	 *
	 * @param ch 要素の値
	 * @param start 要素の値の開始位置
	 * @param len 要素の値の長さ
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void characters(char[] ch, int start, int len) throws DataFileIOException {
		state = SEEN_DATA;
		writeEsc(ch, start, len, false);
	}

	/**
	 * 変数を初期化します。
	 */
	private void init() {
		depth = 0;
		state = SEEN_NOTHING;
		stateStack = new Stack<Object>();
		prefixMap = new HashMap<String, String>();
		namespaceMap = new LinkedHashMap<String, Boolean>();
		declaredNamespaceMap = new HashMap<String, String>();
		nsSupport = new NamespaceSupport();
	}

	/**
	 * すべての名前空間を設定します。
	 */
	private void declareNamespace() {

		for (String uri : namespaceMap.keySet()) {
			doPrefix(uri, null, true);
		}

	}

	/**
	 * 名前空間に接頭辞を設定します。
	 *
	 * @param uri 名前空間のURI
	 * @param qName 要素のQName
	 * @param isElement 要素の設定をする場合true、属性の設定をする場合false
	 *
	 * @return 接頭辞
	 */
	private String doPrefix(String uri, String qName, boolean isElement) {

		if (uri == null || uri.length() == 0) {
			return null;
		}

		String prefix = nsSupport.getPrefix(uri);
		if (prefix != null) {
			return prefix;
		}

		prefix = (String) declaredNamespaceMap.get(uri);
		if (prefix != null) {
			return prefix;
		}

		prefix = (String) prefixMap.get(uri);
		if (prefix == null && qName != null && !"".equals(qName)) {
			int i = qName.indexOf(':');
			if (i == -1) {
				return null;
			} else {
				prefix = qName.substring(0, i);
			}
		}
		if (prefix == null) {
			prefix = "";
		}
		nsSupport.declarePrefix(prefix, uri);
		declaredNamespaceMap.put(uri, prefix);
		return prefix;
	}

	/**
	 * １バイト書き込みます。
	 *
	 * @param c 書き込む文字
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void write(char c) throws DataFileIOException {
		try {
			writer.write(c);
		} catch (IOException e) {
			DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_034,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * 文字列を書き込みます。
	 *
	 * @param s 書き込む文字列
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void write(String s) throws DataFileIOException {
		try {
			writer.write(s);
		} catch (IOException e) {
			DataFileIOException ex = new DataFileIOException(MESSAGE.getErrorMessage(
					ErrorCodeConstant.ACCESSOR_FILEACCESS_XML_035,
					LogMessageConstant.CB3_ACCESSOR_FILEACCESS_004, config.getFilepath()), e);
			LOG.info(ex.getMessage(), ex);
			throw ex;
		}
	}

	/**
	 * すべての属性を書き込みます。
	 *
	 * @param atts 属性
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void writeAttributes(Attributes atts) throws DataFileIOException {
		int len = atts.getLength();
		for (int i = 0; i < len; i++) {
			char ch[] = atts.getValue(i).toCharArray();
			write(' ');
			writeName(atts.getURI(i), atts.getLocalName(i), atts.getQName(i), false);
			write("=\"");
			writeEsc(ch, 0, ch.length, true);
			write('"');
		}
	}

	/**
	 * 文字列をエスケープして書き込みます。
	 *
	 * @param ch 書き込む文字列
	 * @param start 書き込む文字列の開始位置
	 * @param length 書き込む文字列の長さ
	 * @param isAttVal 書き込む文字列が属性の場合true、それ以外はfalse
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void writeEsc(char ch[], int start, int length, boolean isAttVal)
			throws DataFileIOException {

		for (int i = start; i < start + length; i++) {
			switch (ch[i]) {
			case '&':
				write("&amp;");
				break;
			case '<':
				write("&lt;");
				break;
			case '>':
				write("&gt;");
				break;
			case '\"':
				if (isAttVal) {
					write("&quot;");
				} else {
					write('\"');
				}
				break;
			default:
				write(ch[i]);
			}
		}

	}

	/**
	 * 名前空間を書き込みます。
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void writeNSDecls() throws DataFileIOException {

		Enumeration<String> prefixes = nsSupport.getDeclaredPrefixes();
		while (prefixes.hasMoreElements()) {
			String prefix = prefixes.nextElement();
			String uri = nsSupport.getURI(prefix);
			char ch[] = uri.toCharArray();
			write(' ');
			if ("".equals(prefix)) {
				write("xmlns=\"");
			} else {
				write("xmlns:");
				write(prefix);
				write("=\"");
			}
			writeEsc(ch, 0, ch.length, true);
			write('\"');
		}

	}

	/**
	 * 要素または属性の名前を書き込みます。
	 *
	 * @param uri 名前空間のURI
	 * @param localName 要素または属性の名前
	 * @param qName 要素または属性のQName
	 * @param isElement 要素の設定をする場合true、属性の設定をする場合false
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void writeName(String uri, String localName, String qName, boolean isElement)
			throws DataFileIOException {

		String prefix = doPrefix(uri, qName, isElement);
		if (prefix != null && !"".equals(prefix)) {
			write(prefix);
			write(':');
		}
		write(localName);

	}

	/**
	 * インデントを書き込みます。
	 *
	 * @throws DataFileIOException データファイル読み書き時のIOエラーにより発生します。
	 */
	private void doIndent() throws DataFileIOException {

		if (indentSpace > 0 && depth > 0) {
			int n = indentSpace * depth;
			char ch[] = new char[n];
			for (int i = 0; i < n; i++) {
				ch[i] = ' ';
			}
			characters(ch, 0, n);
		}

	}

	/**
	 * 指定されたXMLElementからAttributesに変換して取得します。
	 *
	 * @param element XMLElement
	 * @return XMLElementの全ての属性
	 */
	private Attributes getAttributes(XMLElement element) {

		AttributesImpl attributes = new AttributesImpl();
		for (XMLAttribute attribute : element.getAttributes().values()) {
			attributes.addAttribute(attribute.getURI(), attribute.getName(), attribute.getQName(),
					"CDATA", attribute.getString());
		}
		return attributes;

	}

}
