/*
 * MosP - Mind Open Source Project    http://www.mosp.jp/
 * Copyright (C) MIND Co., Ltd.       http://www.e-mind.co.jp/
 * 
 * This program is free software: you can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License
 * as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package jp.mosp.framework.base;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import jp.mosp.framework.constant.MospConst;
import jp.mosp.framework.property.AddonProperty;
import jp.mosp.framework.property.CodeItemProperty;
import jp.mosp.framework.property.CodeProperty;
import jp.mosp.framework.property.CommandProperty;
import jp.mosp.framework.property.MainMenuProperty;
import jp.mosp.framework.property.MenuProperty;
import jp.mosp.framework.property.MessageProperty;
import jp.mosp.framework.property.MospProperties;
import jp.mosp.framework.property.RangeProperty;
import jp.mosp.framework.property.RoleMenuProperty;
import jp.mosp.framework.property.RoleProperty;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * MosP設定情報を作成する。<br>
 * <br>
 * {@link #PATH_PROPERTY}以下で{@link #SUFFIX_PROPERTY_FILE}で終わるファイルを対象とし、
 * MosP設定情報を作成する。<br>
 * MosP設定情報ファイルはファイル名の自然順序付けに依ってソートされる。<br>
 * 但し、{@link #FILE_PROPERTY}は、必ず先頭となる。<br>
 * 同様のキーを持つ要素が存在した場合、当該要素は上書きされる。<br>
 * <br>
 * MosP設定情報ファイルは、ドキュメント要素としてMosP要素を持たなくてはならない。<br>
 * MosP要素の子要素は、{@link #ATTRIBUTE_KEY}属性を持たなくてはならない。<br>
 */
public final class MospPropertiesParser {
	
	/**
	 * 他クラスからのインスタンス化を防止する。<br>
	 */
	private MospPropertiesParser() {
		// 処理無し
	}
	

	/**
	 * MosP設定情報ファイル格納ディレクトリパス。
	 */
	private static final String	PATH_PROPERTY			= "/WEB-INF/xml/";
	
	/**
	 * MosPアドオン設定情報ファイル格納ディレクトリパス。
	 */
	private static final String	PATH_ADDON_PROPERTY		= "/WEB-INF/xml/addon/";
	
	/**
	 * MosPユーザ作成設定情報ファイル格納ディレクトリパス。
	 */
	private static final String	PATH_USER_PROPERTY		= "/WEB-INF/xml/user/";
	
	/**
	 * MosP設定情報ファイル名。
	 */
	private static final String	FILE_PROPERTY			= "mosp.xml";
	
	/**
	 * MosP設定情報ファイル接尾辞。
	 */
	private static final String	SUFFIX_PROPERTY_FILE	= ".xml";
	
	/**
	 * ドキュメントルート要素の要素名。
	 */
	private static final String	TAG_DOCUMENT			= "MosP";
	
	/**
	 * アプリケーション要素の要素名。
	 */
	private static final String	TAG_APPLICATION			= "Application";
	
	/**
	 * コントローラー要素の要素名。
	 */
	private static final String	TAG_CONTROLLER			= "Controller";
	
	/**
	 * メッセージ要素の要素名。
	 */
	private static final String	TAG_MESSAGE				= "Message";
	
	/**
	 * 文言要素の要素名。
	 */
	private static final String	TAG_NAMING				= "Naming";
	
	/**
	 * コード要素の要素名。
	 */
	private static final String	TAG_CODE				= "Code";
	
	/**
	 * アドオン要素の要素名。
	 */
	private static final String	TAG_ADDON				= "Addon";
	
	/**
	 * メインメニュー要素の要素名。
	 */
	private static final String	TAG_MAIN_MENU			= "MainMenu";
	
	/**
	 * ロール要素の要素名。
	 */
	private static final String	TAG_ROLE				= "Role";
	
	/**
	 * 要素のキー属性名。
	 */
	private static final String	ATTRIBUTE_KEY			= "key";
	
	/**
	 * コントローラー要素の下位要素名(アクションクラス)。
	 */
	private static final String	TAG_ACTION_CLASS		= "ActionClass";
	
	/**
	 * コントローラー要素の下位要素名(HTTPセッション要否)。
	 */
	private static final String	TAG_NEED_SESSION		= "NeedSession";
	
	/**
	 * コントローラー要素の下位要素名(処理シーケンス要否)。
	 */
	private static final String	TAG_NEED_PROC_SEQ		= "NeedProcSeq";
	
	/**
	 * コントローラー要素の下位要素名(許可HTTPメソッド)。
	 */
	private static final String	TAG_ACCEPT_METHOD		= "AcceptMethod";
	
	/**
	 * メッセージ要素の下位要素名(メッセージ本体)。
	 */
	private static final String	TAG_MESSAGE_BODY		= "MessageBody";
	
	/**
	 * メッセージ要素の下位要素名(クライアント利用可否)。
	 */
	private static final String	TAG_CLIENT_AVAILABLE	= "ClientAvailable";
	
	/**
	 * コード要素の下位要素名(コード項目)。
	 */
	private static final String	TAG_CODE_ITEM			= "CodeItem";
	
	/**
	 * コード項目要素の下位要素名(コード項目名)。
	 */
	private static final String	TAG_ITEM_NAME			= "ItemName";
	
	/**
	 * コード項目要素の下位要素名(コード項目表示フラグ)。
	 */
	private static final String	TAG_VIEW_FLAG			= "ViewFlag";
	
	/**
	 * メインメニュー要素の下位要素名(メニュー要素)。
	 */
	private static final String	TAG_MENU				= "Menu";
	
	/**
	 * メニュー要素の下位要素名(コマンド)。
	 */
	private static final String	TAG_COMMAND				= "Command";
	
	/**
	 * メニュー要素の下位要素名(VOクラス)。
	 */
	private static final String	TAG_VO_CLASS			= "VoClass";
	
	/**
	 * メニュー要素の下位要素名(メニュー有効フラグ)。
	 */
	private static final String	TAG_MENU_VALID			= "MenuValid";
	
	/**
	 * ロールメニュー要素の下位要素名(レンジ要素)。
	 */
	private static final String	TAG_RANGE				= "Range";
	
	/**
	 * ロール要素の下位要素名(ロール名称)。
	 */
	private static final String	TAG_ROLE_NAME			= "RoleName";
	
	/**
	 * ロール要素の下位要素名(ロール追加情報)。
	 */
	private static final String	TAG_ROLE_EXTRA			= "RoleExtra";
	
	/**
	 * ロール要素の下位要素名(ロール実行可能コマンド)。
	 */
	private static final String	TAG_ROLE_ACCEPT_COMMAND	= "RoleAcceptCommand";
	
	/**
	 * ロール要素の下位要素名(ロール実行不能コマンド)。
	 */
	private static final String	TAG_ROLE_REJECT_COMMAND	= "RoleRejectCommand";
	
	/**
	 * ロール要素の下位要素名(ロール実行可能コマンド除去)。
	 */
	private static final String	TAG_ROLE_ACCEPT_REMOVE	= "RoleAcceptRemove";
	
	/**
	 * ロール要素の下位要素名(ロール実行不能コマンド除去)。
	 */
	private static final String	TAG_ROLE_REJECT_REMOVE	= "RoleRejectRemove";
	
	/**
	 * レンジ要素の下位要素名(勤務地範囲要素)。
	 */
	private static final String	TAG_WORK_PLACE			= "WorkPlace";
	
	/**
	 * レンジ要素の下位要素名(雇用契約範囲要素)。
	 */
	private static final String	TAG_EMPLOYMENT_CONTRACT	= "EmploymentContract";
	
	/**
	 * レンジ要素の下位要素名(所属範囲要素)。
	 */
	private static final String	TAG_SECTION				= "Section";
	
	/**
	 * レンジ要素の下位要素名(職位範囲要素)。
	 */
	private static final String	TAG_POSITION			= "Position";
	
	/**
	 * レンジ要素の下位要素名(社員範囲要素)。
	 */
	private static final String	TAG_EMPLOYEE			= "Employee";
	
	/**
	 * アドオン要素の下位要素名(アドオン名称)。
	 */
	private static final String	TAG_ADDON_NAME			= "AddonName";
	
	/**
	 * アドオン要素の下位要素名(アドオン有効フラグ)。
	 */
	private static final String	TAG_ADDON_VALID			= "AddonValid";
	
	/**
	 * 表示順要素名。
	 */
	private static final String	TAG_VIEW_INDEX			= "ViewIndex";
	
	/**
	 * インデックス要素名。
	 */
	private static final String	TAG_INDEX				= "Index";
	
	/**
	 * ログメッセージ(解析)。
	 */
	private static final String	MSG_PARSING				= "MosP設定情報ファイル解析。";
	
	/**
	 * ログメッセージ(作成)。
	 */
	private static final String	MSG_CREATING			= "MosP設定情報作成。";
	
	/**
	 * ログメッセージ(ドキュメントパース失敗)。
	 */
	private static final String	MSG_PARSE_FAILED		= "  MosP設定情報ファイルの解析ができませんでした。";
	
	/**
	 * ログメッセージ(ドキュメントエレメント不正)。
	 */
	private static final String	MSG_INVALID_DOC_ELEMENT	= "  MosP設定情報ファイルのドキュメント要素が不正です。";
	
	/**
	 * ログメッセージ(要素名不正)。
	 */
	private static final String	MSG_INVALID_TAG_NAME	= "  MosP設定情報ファイルの要素名が不正です。";
	
	/**
	 * ログメッセージ(要素キー無し)。
	 */
	private static final String	MSG_NO_ELEMENT_KEY		= "  MosP設定情報ファイルの要素キーがありません。";
	
	/**
	 * ログメッセージ(要素値無し)。
	 */
	private static final String	MSG_INVALID_VALUE		= "  MosP設定情報ファイルの要素値が不正です。";
	
	/**
	 * ログメッセージ(要素名)。
	 */
	private static final String	MSG_TAGNAME				= "要素名=";
	
	/**
	 * ログメッセージ(キー)。
	 */
	private static final String	MSG_KEY					= "キー=";
	

	/**
	 * MosP設定情報を作成する。<br>
	 * @param docBase MosPアプリケーションが配置されている実際のパス
	 * @return MosP設定情報
	 * @throws MospException MosP設定情報の作成に失敗した場合
	 */
	public static MospProperties parseMospProperties(String docBase) throws MospException {
		// MosP設定ファイルパスリスト取得
		List<String> pathList = getPathList(docBase);
		// MosP設定ファイルドキュメントリスト取得
		List<Document> documentList = getDocumentList(pathList);
		// MosP設定情報生成
		MospProperties mospProperties = createMospProperties(documentList, pathList);
		// ドキュメントベース設定
		mospProperties.setApplicationProperty(MospConst.APP_DOCBASE, docBase);
		return mospProperties;
	}
	
	/**
	 * MosP設定ファイルパスリストを取得する。<br>
	 * @param docBase MosPアプリケーションが配置されている実際のパス
	 * @return MosP設定ファイルパスリスト
	 */
	private static List<String> getPathList(String docBase) {
		// MosP設定ファイル格納ディレクトリ内設定ファイル取得
		List<String> propertyList = getPathList(docBase + PATH_PROPERTY, false);
		Collections.sort(propertyList);
		// MosP設定ファイルパスリスト準備
		List<String> list = new ArrayList<String>();
		// MosP設定ファイルパス設定
		list.add(docBase + PATH_PROPERTY + FILE_PROPERTY);
		list.addAll(propertyList);
		// アドオン設定ファイルパス設定
		list.addAll(getPathList(docBase + PATH_ADDON_PROPERTY, true));
		// ユーザ作成設定ファイルパス設定
		list.addAll(getPathList(docBase + PATH_USER_PROPERTY, false));
		return list;
	}
	
	/**
	 * ファイルパスリストからドキュメントリストを取得する。<br>
	 * @param pathList ファイルパスリスト
	 * @return ドキュメントリスト
	 * @throws MospException ドキュメントリストの取得に失敗した場合
	 */
	private static List<Document> getDocumentList(List<String> pathList) throws MospException {
		// ログ出力
		log(MSG_PARSING);
		// XMLファイル読込準備
		DocumentBuilder builder;
		try {
			builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			log(MSG_PARSE_FAILED);
			throw new MospException(e);
		}
		List<Document> documentList = new ArrayList<Document>();
		for (String path : pathList) {
			// XMLファイル読込
			log(path);
			File file = new File(path);
			try {
				Document document = builder.parse(file);
				documentList.add(document);
			} catch (SAXException e) {
				log(MSG_PARSE_FAILED);
				throw new MospException(e);
			} catch (IOException e) {
				log(MSG_PARSE_FAILED);
				throw new MospException(e);
			}
		}
		return documentList;
	}
	
	/**
	 * ドキュメントリストからMosP設定情報を作成する。<br>
	 * @param documentList ドキュメントリスト
	 * @param pathList     ファイルパスリスト
	 * @return MosP設定情報
	 */
	private static MospProperties createMospProperties(List<Document> documentList, List<String> pathList) {
		// ログ出力
		log(MSG_CREATING);
		// アプリケーション設定情報群準備
		Map<String, String> application = new HashMap<String, String>();
		// コントローラー設定情報群準備
		Map<String, CommandProperty> controller = new HashMap<String, CommandProperty>();
		// メッセージ設定情報群準備
		Map<String, MessageProperty> message = new HashMap<String, MessageProperty>();
		// 文言設定情報群準備
		Map<String, String> naming = new HashMap<String, String>();
		// コード設定情報群準備
		Map<String, CodeProperty> code = new HashMap<String, CodeProperty>();
		// アドオン設定情報群準備
		Map<String, AddonProperty> addon = new HashMap<String, AddonProperty>();
		// メインメニュー設定情報群準備
		Map<String, MainMenuProperty> mainMenu = new HashMap<String, MainMenuProperty>();
		// ロール設定情報群準備
		Map<String, RoleProperty> role = new HashMap<String, RoleProperty>();
		// ログ出力用ファイルパスインデックス
		int idx = 0;
		// ドキュメント読込
		for (Document document : documentList) {
			// ログ出力
			log(pathList.get(idx++));
			// ドキュメント要素取得
			Element documentElement = document.getDocumentElement();
			// ドキュメント要素確認
			if (documentElement.getTagName().equals(TAG_DOCUMENT) == false) {
				// エラーログ出力
				log(MSG_INVALID_DOC_ELEMENT + MSG_TAGNAME + documentElement.getTagName());
				continue;
			}
			// 下位ノードリスト取得
			NodeList nodeList = documentElement.getChildNodes();
			for (int i = 0; i < nodeList.getLength(); i++) {
				// ノード取得
				Node node = nodeList.item(i);
				// ノード確認
				if (isElement(node) == false) {
					continue;
				}
				// 要素取得
				Element element = (Element)node;
				// 要素名取得
				String tagName = element.getTagName();
				if (tagName.equals(TAG_APPLICATION)) {
					// アプリケーション設定情報群に情報を追加
					putProperty(application, element);
				} else if (tagName.equals(TAG_CONTROLLER)) {
					// コントローラー設定情報群に情報を追加
					putControllerProperty(controller, element);
				} else if (tagName.equals(TAG_MESSAGE)) {
					// メッセージ設定情報群に情報を追加
					putMessageProperty(message, element);
				} else if (tagName.equals(TAG_NAMING)) {
					// 文言設定情報群に情報を追加
					putProperty(naming, element);
				} else if (tagName.equals(TAG_CODE)) {
					// コード設定情報群に情報を追加
					putCodeProperty(code, element);
				} else if (tagName.equals(TAG_ADDON)) {
					// アドオン設定情報群に情報を追加
					AddonProperty addonProperty = putAddonProperty(addon, element);
					// アドオン無効確認
					if (addonProperty == null || addonProperty.isAddonValid() == false) {
						// 当ファイルの以降の設定は無視
						break;
					}
				} else if (tagName.equals(TAG_MAIN_MENU)) {
					// メインメニュー設定情報群に情報を追加
					putMainMenuProperty(mainMenu, element);
				} else if (tagName.equals(TAG_ROLE)) {
					// ロール設定情報群に情報を追加
					putRoleProperty(role, element);
				} else {
					// エラーログ出力
					log(MSG_INVALID_TAG_NAME + MSG_TAGNAME + tagName);
					continue;
				}
			}
		}
		// MosP設定情報作成
		MospProperties mospProperties = new MospProperties();
		mospProperties.setApplicationProperties(application);
		mospProperties.setCommandProperties(controller);
		mospProperties.setMessageProperties(message);
		mospProperties.setNamingProperties(naming);
		mospProperties.setCodeProperties(code);
		mospProperties.setAddonProperties(addon);
		mospProperties.setMainMenuProperties(mainMenu);
		mospProperties.setRoleProperties(role);
		return mospProperties;
	}
	
	/**
	 * 設定情報(アプリケーション、文言)を追加する。<br>
	 * @param properties 設定情報群
	 * @param element    要素
	 */
	private static void putProperty(Map<String, String> properties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// 設定情報取得
		String value = getTextNodeValue(element);
		// 設定情報確認
		if (value == null) {
			// エラーログ出力
			log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
			return;
		}
		// 設定情報追加
		properties.put(key, getTextNodeValue(element));
	}
	
	/**
	 * コントローラ設定情報を追加する。<br>
	 * @param controllerProperties コントローラー設定情報群
	 * @param element              コントローラー要素
	 */
	private static void putControllerProperty(Map<String, CommandProperty> controllerProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// アクションクラス(コントローラー設定情報)準備
		String actionClass = null;
		// HTTPセッション要否(コントローラー設定情報)準備
		String needSession = null;
		// 処理シーケンス要否(コントローラー設定情報)準備
		String needProcSeq = null;
		// 許可HPPTメソッド(コントローラー設定情報)準備
		String acceptMethod = null;
		// コントローラー設定情報取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// ノード取得
			Node node = nodeList.item(i);
			Element lowerElement = null;
			if (node.hasChildNodes() == false) {
				continue;
			}
			if (node instanceof Element == false) {
				continue;
			}
			lowerElement = (Element)node;
			if (lowerElement.getTagName().equals(TAG_ACTION_CLASS)) {
				actionClass = getTextNodeValue(lowerElement);
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_NEED_SESSION)) {
				needSession = getTextNodeValue(lowerElement);
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_NEED_PROC_SEQ)) {
				needProcSeq = getTextNodeValue(lowerElement);
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ACCEPT_METHOD)) {
				acceptMethod = getTextNodeValue(lowerElement);
				continue;
			}
		}
		// コントローラー設定情報追加確認
		if (actionClass == null) {
			// エラーログ出力
			log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
			return;
		}
		// コントローラー設定情報追加
		controllerProperties.put(key, new CommandProperty(key, actionClass, needSession, needProcSeq, acceptMethod));
	}
	
	/**
	 * メッセージ設定情報を追加する。<br>
	 * @param messageProperties メッセージ設定情報群
	 * @param element           メッセージ要素
	 */
	private static void putMessageProperty(Map<String, MessageProperty> messageProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// メッセージ(メッセージ設定情報)準備
		String messageBody = null;
		// クライアント利用可否(メッセージ設定情報)準備(default:false)
		boolean clientAvailable = false;
		// メッセージ設定情報取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// ノード取得
			Node node = nodeList.item(i);
			// ノード確認
			if (isElement(node) == false) {
				continue;
			}
			// 要素取得
			Element lowerElement = (Element)node;
			if (lowerElement.getTagName().equals(TAG_MESSAGE_BODY)) {
				messageBody = getTextNodeValue(lowerElement);
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_CLIENT_AVAILABLE)) {
				clientAvailable = Boolean.parseBoolean(getTextNodeValue(lowerElement));
				continue;
			}
		}
		// コントローラー設定情報追加確認
		if (messageBody == null) {
			// エラーログ出力
			log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
			return;
		}
		// コントローラー設定情報追加
		messageProperties.put(key, new MessageProperty(key, messageBody, clientAvailable));
	}
	
	/**
	 * コード設定情報を追加する。<br>
	 * 同一キーのコード設定情報が存在した場合、そこにコード項目設定情報を追加していく。<br>
	 * 同一キーのコード設定情報内に同一キーのコード項目設定情報が存在した場合、
	 * 当該コード項目設定情報は上書きされる。<br>
	 * @param codeProperties コード設定情報群
	 * @param element        コード要素
	 */
	private static void putCodeProperty(Map<String, CodeProperty> codeProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// コード設定情報取得
		CodeProperty codeProperty = codeProperties.get(key);
		// コード設定情報確認
		if (codeProperty == null) {
			// コード設定情報追加
			codeProperty = new CodeProperty(key);
			codeProperties.put(key, codeProperty);
		}
		// コード項目設定情報群取得
		Map<String, CodeItemProperty> codeItemMap = codeProperty.getCodeItemMap();
		// メインメニュー設定情報取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// ノード取得
			Node node = nodeList.item(i);
			// ノード確認
			if (isElement(node) == false) {
				continue;
			}
			// 要素取得
			Element codeItemElement = (Element)node;
			// 要素名確認
			if (codeItemElement.getTagName().equals(TAG_CODE_ITEM) == false) {
				// エラーログ出力
				log(MSG_INVALID_TAG_NAME + MSG_TAGNAME + codeItemElement.getTagName());
				continue;
			}
			// メニュー設定情報追加
			putCodeItemProperty(codeItemMap, codeItemElement);
		}
	}
	
	/**
	 * コード項目設定情報を追加する。<br>
	 * @param codeItemMap コード項目設定情報群取得
	 * @param element     コード項目要素
	 */
	private static void putCodeItemProperty(Map<String, CodeItemProperty> codeItemMap, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// 情報取得準備
		String itemName = null;
		int viewIndex = 0;
		int viewFlag = 0;
		// 下位ノードリスト取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// 下位ノード取得
			Node node = nodeList.item(i);
			// 要素確認
			if (isElement(node) == false) {
				continue;
			}
			// 下位要素取得
			Element lowerElement = (Element)node;
			// コード項目名称取得
			if (lowerElement.getTagName().equals(TAG_ITEM_NAME)) {
				itemName = getTextNodeValue(lowerElement);
				continue;
			}
			// コード項目表示順取得
			if (lowerElement.getTagName().equals(TAG_VIEW_INDEX)) {
				try {
					viewIndex = Integer.parseInt(getTextNodeValue(lowerElement));
				} catch (NumberFormatException e) {
					// エラーログ出力
					log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
				}
				continue;
			}
			// コード項目表示フラグ取得
			if (lowerElement.getTagName().equals(TAG_VIEW_FLAG)) {
				try {
					viewFlag = Integer.parseInt(getTextNodeValue(lowerElement));
				} catch (NumberFormatException e) {
					// エラーログ出力
					log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
				}
				continue;
			}
		}
		// コード項目設定情報追加
		codeItemMap.put(key, new CodeItemProperty(key, itemName, viewIndex, viewFlag));
	}
	
	/**
	 * アドオン設定情報を追加する。<br>
	 * @param addonProperties アドオン設定情報群
	 * @param element         アドオン要素
	 * @return アドオン設定情報
	 */
	private static AddonProperty putAddonProperty(Map<String, AddonProperty> addonProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return null;
		}
		// 情報取得準備
		String addonName = null;
		// アドオン有効フラグ準備(デフォルト：無効)
		boolean addonValid = false;
		// 下位ノードリスト取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// 下位ノード取得
			Node node = nodeList.item(i);
			// 要素確認
			if (isElement(node) == false) {
				continue;
			}
			// 下位要素取得
			Element lowerElement = (Element)node;
			// アドオン名称取得
			if (lowerElement.getTagName().equals(TAG_ADDON_NAME)) {
				addonName = getTextNodeValue(lowerElement);
				continue;
			}
			// アドオン有効フラグ取得
			if (lowerElement.getTagName().equals(TAG_ADDON_VALID)) {
				addonValid = Boolean.parseBoolean(getTextNodeValue(lowerElement));
				continue;
			}
		}
		// アドオン設定情報追加
		AddonProperty addonProperty = new AddonProperty(key, addonName, addonValid);
		addonProperties.put(key, addonProperty);
		return addonProperty;
	}
	
	/**
	 * メインメニュー設定情報を追加する。<br>
	 * 同一キーのメインメニュー設定情報が存在した場合、そこにメニュー設定情報を追加していく。<br>
	 * 同一メインメニュー設定情報内に同一キーのメニュー設定情報が存在した場合、
	 * 当該メニュー設定情報は上書きされる。<br>
	 * @param mainMenuProperties メインメニュー設定情報群
	 * @param element            メインメニュー要素
	 */
	private static void putMainMenuProperty(Map<String, MainMenuProperty> mainMenuProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// メインメニュー設定情報取得
		MainMenuProperty mainMenuProperty = mainMenuProperties.get(key);
		// メインメニュー設定情報確認
		if (mainMenuProperty == null) {
			// メインメニュー設定情報追加
			mainMenuProperty = new MainMenuProperty(key);
			mainMenuProperties.put(key, mainMenuProperty);
		}
		// メニュー設定情報群取得
		Map<String, MenuProperty> menuMap = mainMenuProperty.getMenuMap();
		// メインメニュー設定情報取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// ノード取得
			Node node = nodeList.item(i);
			// ノード確認
			if (isElement(node) == false) {
				continue;
			}
			// 要素取得
			Element menuElement = (Element)node;
			// 要素名確認
			if (menuElement.getTagName().equals(TAG_MENU) == false) {
				// エラーログ出力
				log(MSG_INVALID_TAG_NAME + MSG_TAGNAME + menuElement.getTagName());
				continue;
			}
			// メニュー設定情報追加
			putMenuProperty(menuMap, menuElement);
		}
	}
	
	/**
	 * メニュー設定情報を追加する。<br>
	 * 但し、引数の要素にキー属性が設定されていない場合、
	 * 及び下位にコマンド要素或いはVOクラス要素が無い場合は、追加されない。<br>
	 * @param menuMap メニュー設定情報群取得
	 * @param element メインメニュー要素
	 */
	private static void putMenuProperty(Map<String, MenuProperty> menuMap, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// 情報取得準備
		String command = null;
		String voClass = null;
		// メニュー有効フラグ準備(デフォルト：有効)
		boolean menuValid = true;
		// 下位ノードリスト取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// 下位ノード取得
			Node node = nodeList.item(i);
			// 要素確認
			if (isElement(node) == false) {
				continue;
			}
			// 下位要素取得
			Element lowerElement = (Element)node;
			// コマンド取得
			if (lowerElement.getTagName().equals(TAG_COMMAND)) {
				command = getTextNodeValue(lowerElement);
				continue;
			}
			// VOクラス取得
			if (lowerElement.getTagName().equals(TAG_VO_CLASS)) {
				voClass = getTextNodeValue(lowerElement);
				continue;
			}
			// アドオン有効フラグ取得
			if (lowerElement.getTagName().equals(TAG_MENU_VALID)) {
				menuValid = Boolean.parseBoolean(getTextNodeValue(lowerElement));
				continue;
			}
		}
		// メニュー設定情報確認
		if (menuValid && (command == null || voClass == null)) {
			// エラーログ出力
			log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
			return;
		}
		// メニュー設定情報追加
		menuMap.put(key, new MenuProperty(key, command, voClass, menuValid));
	}
	
	/**
	 * ロール設定情報を追加する。<br>
	 * 同一キーのロール設定情報が存在した場合、そこにロールメニュー設定情報を追加していく。<br>
	 * 同一ロール設定情報内に同一キーのロールメニュー設定情報が存在した場合、
	 * 当該ロールメニュー設定情報は上書きされる。<br>
	 * @param roleProperties ロール設定情報群
	 * @param element        ロール要素
	 */
	private static void putRoleProperty(Map<String, RoleProperty> roleProperties, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// ロール設定情報取得
		RoleProperty roleProperty = roleProperties.get(key);
		// ロール設定情報確認
		if (roleProperty == null) {
			// メインメニュー設定情報追加
			roleProperty = new RoleProperty(key);
			roleProperties.put(key, roleProperty);
		}
		// ロールメニュー設定情報群取得
		Map<String, RoleMenuProperty> roleMenuMap = roleProperty.getRoleMenuMap();
		// ロール実行可能コマンドリスト取得
		List<String> acceptCmdList = roleProperty.getAcceptCmdList();
		// ロール実行不能コマンドリスト取得
		List<String> rejecrtCmdList = roleProperty.getRejectCmdList();
		// 名称及びメインメニュー設定情報取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// ノード取得
			Node node = nodeList.item(i);
			// ノード確認
			if (isElement(node) == false) {
				continue;
			}
			// 要素取得
			Element lowerElement = (Element)node;
			// 要素名確認
			if (lowerElement.getTagName().equals(TAG_ROLE_NAME)) {
				// ロール名称設定
				roleProperty.setRoleName(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ROLE_EXTRA)) {
				// ロール追加情報設定
				roleProperty.setRoleExtra(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_MENU)) {
				// メニュー設定情報追加
				putRoleMenuProperty(roleMenuMap, lowerElement);
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ROLE_ACCEPT_COMMAND)) {
				// ロール実行可能コマンド追加
				acceptCmdList.add(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ROLE_REJECT_COMMAND)) {
				// ロール実行可能コマンド追加
				rejecrtCmdList.add(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ROLE_ACCEPT_REMOVE)) {
				// ロール実行可能コマンド除去
				acceptCmdList.remove(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_ROLE_REJECT_REMOVE)) {
				// ロール実行可能コマンド除去
				rejecrtCmdList.remove(getTextNodeValue(lowerElement));
				continue;
			}
			if (lowerElement.getTagName().equals(TAG_VIEW_INDEX)) {
				// ロール表示順情報設定
				try {
					roleProperty.setViewIndex(Integer.parseInt(getTextNodeValue(lowerElement)));
				} catch (NumberFormatException e) {
					// エラーログ出力
					log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
				}
				continue;
			}
		}
	}
	
	/**
	 * ロールメニュー設定情報を追加する。<br>
	 * 但し、引数の要素にキー属性が設定されていない場合、
	 * 及びレンジ要素が無い場合は、追加されない。<br>
	 * @param roleMenuMap ロールメニュー設定情報群取得
	 * @param element     ロールメニュー要素
	 */
	private static void putRoleMenuProperty(Map<String, RoleMenuProperty> roleMenuMap, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// 情報取得準備
		Integer index = null;
		Map<String, RangeProperty> rangeMap = new HashMap<String, RangeProperty>();
		// 下位ノードリスト取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// 下位ノード取得
			Node node = nodeList.item(i);
			// 要素確認
			if (isElement(node) == false) {
				continue;
			}
			// 下位要素取得
			Element lowerElement = (Element)node;
			// インデックス取得
			if (lowerElement.getTagName().equals(TAG_INDEX)) {
				try {
					index = Integer.parseInt(getTextNodeValue(lowerElement));
				} catch (NumberFormatException e) {
					// エラーログ出力
					log(MSG_INVALID_VALUE + MSG_TAGNAME + element.getTagName() + MSG_KEY + key);
				}
				continue;
			}
			// レンジ設定情報取得
			if (lowerElement.getTagName().equals(TAG_RANGE)) {
				putRangeProperty(rangeMap, lowerElement);
				continue;
			}
		}
		// メニュー設定情報追加
		roleMenuMap.put(key, new RoleMenuProperty(key, index, rangeMap));
	}
	
	/**
	 * レンジ設定情報を追加する。<br>
	 * 但し、引数の要素にキー属性が設定されていない場合は、追加されない。<br>
	 * @param rangeMap レンジ設定情報群取得
	 * @param element  レンジ要素
	 */
	private static void putRangeProperty(Map<String, RangeProperty> rangeMap, Element element) {
		// キー情報取得
		String key = element.getAttribute(ATTRIBUTE_KEY);
		// キー情報確認
		if (key.isEmpty()) {
			// エラーログ出力
			log(MSG_NO_ELEMENT_KEY + MSG_TAGNAME + element.getTagName());
			return;
		}
		// 情報取得準備
		String workPlace = null;
		String employmetnContract = null;
		String section = null;
		String position = null;
		String employee = null;
		// 下位ノードリスト取得
		NodeList nodeList = element.getChildNodes();
		for (int i = 0; i < nodeList.getLength(); i++) {
			// 下位ノード取得
			Node node = nodeList.item(i);
			// 要素確認
			if (isElement(node) == false) {
				continue;
			}
			// 下位要素取得
			Element lowerElement = (Element)node;
			// 勤務地範囲取得
			if (lowerElement.getTagName().equals(TAG_WORK_PLACE)) {
				workPlace = getTextNodeValue(lowerElement);
				continue;
			}
			// 雇用契約範囲取得
			if (lowerElement.getTagName().equals(TAG_EMPLOYMENT_CONTRACT)) {
				employmetnContract = getTextNodeValue(lowerElement);
				continue;
			}
			// 所属範囲取得
			if (lowerElement.getTagName().equals(TAG_SECTION)) {
				section = getTextNodeValue(lowerElement);
				continue;
			}
			// 職位範囲取得
			if (lowerElement.getTagName().equals(TAG_POSITION)) {
				position = getTextNodeValue(lowerElement);
				continue;
			}
			// 社員範囲取得
			if (lowerElement.getTagName().equals(TAG_EMPLOYEE)) {
				employee = getTextNodeValue(lowerElement);
				continue;
			}
		}
		// 範囲設定情報追加
		rangeMap.put(key, new RangeProperty(key, workPlace, employmetnContract, section, position, employee));
	}
	
	/**
	 * 要素かどうかの判定を行う。<br>
	 * @param node 対象ノード
	 * @return 判定結果(true：要素、false：要素でない)
	 */
	private static boolean isElement(Node node) {
		if (node == null) {
			return false;
		}
		if (node.hasChildNodes() == false) {
			return false;
		}
		if (node instanceof Element == false) {
			return false;
		}
		return true;
	}
	
	/**
	 * ノードの下位テキストノードの値を取得する。<br>
	 * 下位ノードがテキストノードでない場合、NULLを返す。<br>
	 * @param node ノード
	 * @return 下位テキストノードの値
	 */
	private static String getTextNodeValue(Node node) {
		// ノード確認
		if (node == null) {
			return null;
		}
		// 下位ノード確認
		if (node.getFirstChild() == null) {
			return null;
		}
		// 下位ノード型確認
		if (node.getFirstChild().getNodeType() != Node.TEXT_NODE) {
			return null;
		}
		// 下位テキストノード値取得
		return node.getFirstChild().getTextContent().trim();
	}
	
	/**
	 * ログを出力する。
	 * @param message ログメッセージ
	 */
	private static void log(String message) {
		System.out.println(message);
	}
	
	/**
	 * MosP設定情報ファイルパスのリストを取得する。<br>
	 * 下位ディレクトリ参照要の場合、一つ下のディレクトリのみを参照する。<br>
	 * @param dirPath MosP設定情報ファイルが配置されている実際のパス
	 * @param containDir 下位ディレクトリ参照要否(true：要、false：不要)
	 * @return MosP設定情報ファイルパスリスト
	 */
	private static List<String> getPathList(String dirPath, boolean containDir) {
		// ファイルパスリスト準備
		List<String> propertyList = new ArrayList<String>();
		// ファイルリスト取得
		File[] files = new File(dirPath).listFiles();
		// ファイルリスト確認
		if (files == null) {
			return propertyList;
		}
		// ファイル毎に処理
		for (File file : files) {
			// ディレクトリ確認
			if (file.isDirectory()) {
				// 下位ディレクトリ参照(ファイルパスリストに追加)
				propertyList.addAll(getPathList(file.getPath(), false));
			}
			// ファイル確認
			if (file.isFile() == false) {
				continue;
			}
			// ファイル名取得及び確認
			String fileName = file.getName();
			if (fileName.endsWith(SUFFIX_PROPERTY_FILE) == false || fileName.contains(FILE_PROPERTY)) {
				continue;
			}
			// ファイルパスリストに追加
			propertyList.add(file.getPath());
		}
		// ソート
		Collections.sort(propertyList);
		return propertyList;
	}
	
}
