package jp.co.headwaters.webappos;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletContextEvent;

import jp.co.headwaters.webappos.cache.ActionCache;
import jp.co.headwaters.webappos.cache.ForeignKeyCache;
import jp.co.headwaters.webappos.cache.bean.ActionBean;
import jp.co.headwaters.webappos.cache.bean.BindBean;
import jp.co.headwaters.webappos.cache.bean.CaseBean;
import jp.co.headwaters.webappos.cache.bean.ConditionBean;
import jp.co.headwaters.webappos.cache.bean.ExecuteBean;
import jp.co.headwaters.webappos.cache.bean.FunctionBean;
import jp.co.headwaters.webappos.cache.bean.ResultBean;
import jp.co.headwaters.webappos.common.AppConstants;
import jp.co.headwaters.webappos.common.WebAppOSUtils;
import jp.co.headwaters.webappos.common.enumation.CrudEnum;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.HTMLWriter;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;

public class WebAppOSGenerator {

	private static final Log logger = LogFactory.getLog(WebAppOSGenerator.class);

	/** アプリケーションAction定義を保持するキャッシュ */
	private ActionCache _actionCache;

	/** foreign key情報を保持するキャッシュ */
	private ForeignKeyCache _foreignKeyCache;

	/** コンテキスト状態変更のイベント */
	private ServletContextEvent _event;

	public void generate(ServletContextEvent event) {
		logger.debug("================= generate start ================= ");

		_event = event;

		// --------------------------------------------------------------------
		// foreign key情報をキャッシュする
		// --------------------------------------------------------------------
		getForeignKeyInfo();

		// --------------------------------------------------------------------
		// action情報をキャッシュする
		// --------------------------------------------------------------------
		// action定義をhtml配下のディレクトリ構成を元に生成する
		_actionCache = ActionCache.getInstance();
		_actionCache.refresh();
		createActionInfo(new File(AppConstants.HTML_BASE_PATH), null);

		// --------------------------------------------------------------------
		// jspファイルの出力および、実行情報をキャッシュする
		// --------------------------------------------------------------------
		outputJspFile();

		// --------------------------------------------------------------------
		// struts2設定ファイルを出力する
		// --------------------------------------------------------------------
		outputStrutsConfig();

		logger.debug("================= generate  end  ================= ");
	}

	private void getForeignKeyInfo(){
		// JDBC経由でFK情報を取得し、キャッシュする
		Connection conn = null;
		try {
			_foreignKeyCache = ForeignKeyCache.getInstance();

			// TODO:プロパティに設定する
			Class.forName("org.postgresql.Driver");
//			conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/testdb", "testuser", "testuser");
			conn = DriverManager.getConnection("jdbc:postgresql://localhost:5432/matching", "postgres", "postgres");

			DatabaseMetaData meta = conn.getMetaData();
			String types[] = { "TABLE" };
			ResultSet result = meta.getTables(null, null, "%", types);
			while (result.next()) {
				String tableName = result.getString("TABLE_NAME");
				System.out.println("--------- " + tableName + " ---------");

				// 自分が親の子を保持
				ResultSet exportResult = meta.getExportedKeys(null, "public", tableName);
				while (exportResult.next()) {
					if (exportResult.getString("pktable_name") == null){
						continue;
					}
					_foreignKeyCache.addExportTable(exportResult.getString("pktable_name"), exportResult.getString("fktable_name"));
				}

				// 自分が子の親を保持
				ResultSet importeResult = meta.getImportedKeys(null, "public", tableName);
				while (importeResult.next()) {
					if (importeResult.getString("fktable_name") == null){
						continue;
					}
					_foreignKeyCache.addImportTable(importeResult.getString("fktable_name"), importeResult.getString("pktable_name"));
				}
			}
		} catch (Exception e) {
			// TODO ログはく
			e.printStackTrace();
			return;
		} finally {
			if (conn != null) {
				try {
					conn.close();
				} catch (SQLException e) {
					// ignore
				}
			}
		}
	}

	private void createActionInfo(File dir, String parent) {
		File[] files = dir.listFiles();
		if (files == null) {
			return;
		}

		String actionName = null;
		String htmlPath = null;
		for (File file : files) {
			if (file.isDirectory()) {
				// svn管理フォルダの場合、除外する
				if (".svn".equalsIgnoreCase(file.getName())) {
					continue;
				}

				// サブディレクトリの場合、ディレクトリ名を結合した文字列をAction名とする(CamelCase)
				if (parent != null){
					actionName = parent + StringUtils.capitalize(file.getName());
				}else{
					actionName = file.getName();
				}

				// htmlファイルのhtmlディレクトリからの相対パスを取得する
				htmlPath = file.getAbsolutePath().replace(AppConstants.HTML_BASE_PATH, "");
				_actionCache.getActionMap().put(actionName, new ActionBean(actionName, htmlPath));

				// 再起的に処理する
				createActionInfo(file, actionName);
			}
		}
	}

	private void outputJspFile(){
		// --------------------------------------------------------------------
		// jsp格納ディレクトリを生成する
		// --------------------------------------------------------------------
		createJspDirectory();

		// --------------------------------------------------------------------
		// ディレクトリ、ファイル別に処理を行う
		// --------------------------------------------------------------------
		SAXReader reader = new SAXReader();
		String jsprRlativeFilePath = null;
		String jspFilePath = null;
		String jspWorkFilePath = null;
		for (ActionBean actionBean : _actionCache.getActionMap().values()) {
			// html格納ディレクトリ内のhtmlファイルを処理対象とする
			String dir = AppConstants.HTML_BASE_PATH + actionBean.getHtmlPath();
			File[] htmlFiles = new File(dir).listFiles(WebAppOSUtils.getHtmlFileFilter());
			for (File file : htmlFiles) {
				try {
					// --------------------------------------------------------------------
					// html→jspファイル（ワークファイル）
					// --------------------------------------------------------------------
					jsprRlativeFilePath = actionBean.getHtmlPath() +
										System.getProperty("file.separator") +
										WebAppOSUtils.convertExtensionHtmlToJsp(file.getName());
					jspFilePath = AppConstants.JSP_BASE_PATH + jsprRlativeFilePath;
					jspWorkFilePath = AppConstants.JSP_WORK_BASE_PATH + jsprRlativeFilePath;

					// htmlファイルをコピーし、jspファイルを生成する
					WebAppOSUtils.copyFile(file, jspWorkFilePath);
					logger.debug("-----------------" + jspWorkFilePath + " START -----------------");

					// --------------------------------------------------------------------
					// アクション別実行情報の初期化
					// --------------------------------------------------------------------
					// form識別子-実行情報
					Map<String, ExecuteBean> submitExecuteMap = actionBean.getSubmitExecuteMap();
					if (submitExecuteMap == null) {
						submitExecuteMap = new HashMap<String, ExecuteBean>();
						actionBean.setSubmitExecuteMap(submitExecuteMap);
					}
					// 画面ロード-実行情報
					Map<String, ExecuteBean> loadExecuteMap = actionBean.getLoadExecuteMap();
					if (loadExecuteMap == null) {
						loadExecuteMap = new HashMap<String, ExecuteBean>();
						actionBean.setLoadExecuteMap(loadExecuteMap);
					}

					// --------------------------------------------------------------------
					// jsp(html)解析
					// jspを解析し、実行情報等を取得する
					// また、必要な実行に要素を追加する
					// --------------------------------------------------------------------
					// jspファイルを読み込み
					Document document = reader.read(jspWorkFilePath);
					document.getRootElement().addNamespace("s", "/struts-tags");

					// --------------------------------------------------------------------
					// 直アクセス可能画面情報取得
					// --------------------------------------------------------------------
					List<?> directNodes = document.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_DIRECT);
					for (Object directNode : directNodes) {
						if (directNode instanceof Attribute){
							Attribute directAttr = (Attribute)directNode;
							Element parentElement = directAttr.getParent();
							String resultName = directAttr.getValue();

							if (actionBean.getDirectAccessMap() == null){
								actionBean.setDirectAccessMap(new HashMap<String, String>());
							}
							if (!actionBean.getDirectAccessMap().containsKey(resultName)){
								actionBean.getDirectAccessMap().put(resultName, System.getProperty("file.separator") + AppConstants.JSP_DIR + jsprRlativeFilePath);
							}
							parentElement.remove(directAttr);
						}
					}

					// --------------------------------------------------------------------
					// 画面ロード時の実行機能情報取得
					// --------------------------------------------------------------------
					// 実行情報を保持するBeanを初期化する
					ExecuteBean loadExecuteInfo = new ExecuteBean();
					loadExecuteInfo.setResult(new ResultBean());
					loadExecuteInfo.setFunctionList(new ArrayList<FunctionBean>());
					// data-load属性を取得
					List<?> loadNodes = document.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_LOAD);
					for (Object loadNode : loadNodes){
						if (loadNode instanceof Attribute){
							// --------------------------------------------------------------------
							// 実行情報を保持するBeanを初期化する
							// --------------------------------------------------------------------
							Attribute loadAttr = (Attribute)loadNode;
							Element element = loadAttr.getParent();
							String funcInfo = loadAttr.getValue();
							// div要素配下の要素から機能情報およびステートメント情報を抽出し、キャッシュする
							FunctionBean function = createFunctionInfo(funcInfo, element, false);
							if (function != null) {
								loadExecuteInfo.getFunctionList().add(function);
							}
						}
					}
					loadExecuteMap.put(System.getProperty("file.separator") + AppConstants.JSP_DIR + jsprRlativeFilePath, loadExecuteInfo);

					// --------------------------------------------------------------------
					// data-const属性の差し替え
					// --------------------------------------------------------------------
					// TODO:定数使用する時にもう一度考える。。
					// Element配下のdata-const属性の内容を取得する
//					List constNodes = document.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_CONST);
//					for (Object constNode : constNodes) {
//						Attribute constAttr = (Attribute)constNode;
//						Element parentElement = constAttr.getParent();
//						// data-const属性を削除する
//						parentElement.remove(constAttr);
//						try {
//							ConstantBean constBean = mapper.readValue(constAttr.getValue(), new TypeReference<ConstantBean>(){});
//							String attrName = constBean.getTarget();
//							String attrValue = null;
//							String category = constBean.getCategory();
//							String key = constBean.getKey();
//							String format = constBean.getFormat();
//
//							if (StringUtils.isEmpty(attrName)) {
//								if (StringUtils.isEmpty(key)) {
//									// TODO:Keyが未指定の場合は、リストで取得し、iteratorで出力。
//								} else {
//									// Keyが設定されている場合、Textに設定
//									if (!StringUtils.isEmpty(category)){
//										parentElement.setText("＜s:property value=”%{@jp.co.headwaters.webappos.cache.SystemConstantCache@getSystemConstant2('" + category + "','" + key + "')}”/＞");
//									} else {
//										parentElement.setText("＜s:property value=”%{@jp.co.headwaters.webappos.cache.SystemConstantCache@getSystemConstant2('" + key + "')}”/＞");
//									}
//								}
//							} else {
//								if (StringUtils.isEmpty(format)) {
//									format = "{0}";
//								}
//								attrValue = MessageFormat.format(format, "＜s:property value=”%{@jp.co.headwaters.webappos.cache.SystemConstantCache@getSystemConstant2('" + key + "')}”/＞");
//								parentElement.addAttribute(attrName, attrValue);
//							}
//						} catch (Exception e) {
//							// TODO:例外処理
//							e.printStackTrace();
//						}
//					}

					// --------------------------------------------------------------------
					// form別処理
					// --------------------------------------------------------------------
					List<?> nodes = document.selectNodes("//form");
					String formId = null;
					for(Iterator<?> i = nodes.iterator(); i.hasNext();) {
						// form要素を取得する
						Element formElement = (Element)i.next();

						// 実行情報を保持するBeanを初期化する
						ExecuteBean submitExecuteInfo = new ExecuteBean();
						submitExecuteInfo.setResult(new ResultBean());
						submitExecuteInfo.setFunctionList(new ArrayList<FunctionBean>());

						// --------------------------------------------------------------------
						// form識別子要素を追加する
						// --------------------------------------------------------------------
						// form識別子を生成し、formの子要素として追加する
						// TODO:バリデーション
						formId = String.valueOf((file.getAbsolutePath() + formElement.attributeValue("id")).hashCode());
						formElement.addElement("input")
								.addAttribute("type", "hidden")
								.addAttribute("name", AppConstants.FORM_ID_NAME)
								.addAttribute("value", formId);
						// form識別子毎に実行情報を保持する
						// submit時に実行機能を識別する為に使用する
						submitExecuteMap.put(formId, submitExecuteInfo);

						// --------------------------------------------------------------------
						// resultタグ情報生成
						// --------------------------------------------------------------------
						// formタグの属性を取得する
						String actionValue = WebAppOSUtils.convertExtensionHtmlToJsp(formElement.attributeValue("action"));
						logger.debug("form action:" + actionValue);
						if (StringUtils.isEmpty(actionValue)){
							continue;
						}
						actionValue  = actionValue.replace("/", System.getProperty("file.separator"));
						String jspFileName;
						if (actionValue.startsWith("..")) {
							// カレントディレクトリ以外
							if (actionValue.length() <= 3){
								// TODO:WARNログを出力する
								continue;
							}
							jspFileName = AppConstants.JSP_DIR + actionValue.substring(3);
						} else if (actionValue.startsWith(".")) {
							// カレントディレクトリ
							jspFileName = AppConstants.JSP_DIR + actionBean.getName() + actionValue.substring(1);
						} else {
							// カレントディレクトリ
							jspFileName = AppConstants.JSP_DIR + actionBean.getName() + System.getProperty("file.separator") + actionValue;
						}

						// result情報を生成し、実行情報に保持する
						ResultBean result = submitExecuteInfo.getResult();
						result.setName("result" + submitExecuteMap.size());
						result.setValue(System.getProperty("file.separator") + jspFileName);

						// --------------------------------------------------------------------
						// action属性を設定する
						// --------------------------------------------------------------------
						formElement.addAttribute("action", actionBean.getName());

						// --------------------------------------------------------------------
						// 機能情報の生成
						// data-func属性の抽出、ステートメント情報の取得
						// --------------------------------------------------------------------
						Attribute funcAttr = (Attribute)formElement.attribute(AppConstants.DATA_ATTRIBUTE_NAME_FUNC);
						String funcInfo = null;
						if (funcAttr != null) {
							funcInfo = funcAttr.getValue();
							// formの属性から機能情報を取得する
							FunctionBean function = createFunctionInfo(funcInfo, formElement, true);
							deleteDataAttribute(formElement);
							if (function != null) {
								submitExecuteInfo.getFunctionList().add(function);
							}
							formElement.remove(funcAttr);
						} else {
							// formの属性に未設定の場合、form配下のdiv要素の属性から機能情報を取得する
							List<?> list = formElement.selectNodes("./div");
							for (Object obj : list) {
								Element divElement = (Element)obj;
								funcAttr = (Attribute)divElement.attribute(AppConstants.DATA_ATTRIBUTE_NAME_FUNC);
								if (funcAttr != null){
									funcInfo = funcAttr.getValue();
									// div要素配下の要素から機能情報およびステートメント情報を抽出し、キャッシュする
									FunctionBean function = createFunctionInfo(funcInfo, divElement, true);
									deleteDataAttribute(divElement);
									if (function != null) {
										submitExecuteInfo.getFunctionList().add(function);
									}
									divElement.remove(funcAttr);
								}
							}
						}
					}

					// --------------------------------------------------------------------
					// Path属性の差し替え
					// href属性、src属性、objectタグdata属性、paramタグvalue属性
					// --------------------------------------------------------------------
					for (Object node : document.selectNodes("descendant::node()/@href")) {
						convertPath((Attribute)node);
					}

					for (Object node : document.selectNodes("descendant::node()/@src")) {
						convertPath((Attribute)node);
					}

					for (Object node : document.selectNodes("descendant::object/@data")) {
						convertPath((Attribute)node);
					}

					for (Object node : document.selectNodes("descendant::object/child::param/@value")) {
						convertPath((Attribute)node);
					}

					// --------------------------------------------------------------------
					// 画面ロード時のdata属性を削除する
					// --------------------------------------------------------------------
					// data-loadを削除する
					for (Object loadNode : loadNodes){
						if (loadNode instanceof Attribute){
							Attribute loadAttr = (Attribute)loadNode;
							Element element = loadAttr.getParent();
							deleteDataAttribute(element);
							element.remove(loadAttr);
						}
					}

					// --------------------------------------------------------------------
					// jspファイルを書き換える
					// TODO:インデント、整形
					// --------------------------------------------------------------------
					OutputFormat outputFormat = new OutputFormat(null, false, "UTF-8");
					// XML文書宣言を省略する
					outputFormat.setSuppressDeclaration(true);
					outputFormat.setExpandEmptyElements(true);
					HTMLWriter xw = null;
					try {
						document.setDocType(null);
						xw = new HTMLWriter(new FileWriter(jspWorkFilePath), outputFormat);
						xw.write(document);
						xw.flush();
					} catch (IOException e) {
						// TODO:WARNログを出力する
						e.printStackTrace();
					}finally{
						if (xw != null) {
							try {
								xw.close();
							} catch (IOException e) {
								// ignore
							}
						}
			        }

					// --------------------------------------------------------------------
					// jspディレクティブを挿入する
					// --------------------------------------------------------------------
					writeJspDirective(jspWorkFilePath, jspFilePath);

				} catch (DocumentException e) {
					// TODO:HTML解析エラー　WARNログを出力する
					e.printStackTrace();
				}
			}
		}

		// --------------------------------------------------------------------
		// ワークファイルを削除する
		// --------------------------------------------------------------------
		WebAppOSUtils.deleteFile(new File(AppConstants.JSP_WORK_BASE_PATH));
	}

	private void createJspDirectory() {
		// jspディレクトリ配下のディレクトリ、ファイルを全て削除する
		WebAppOSUtils.deleteFile(new File(AppConstants.JSP_BASE_PATH));

		// Action情報を元にjspファイル格納ディレクトリを生成する
		for (ActionBean actionBean : _actionCache.getActionMap().values()) {
			File file = new File(AppConstants.JSP_BASE_PATH + actionBean.getHtmlPath());
			file.mkdirs();
		}

		// ワークファイル用
		// jspディレクトリ配下のディレクトリ、ファイルを全て削除する
		WebAppOSUtils.deleteFile(new File(AppConstants.JSP_WORK_BASE_PATH));

		// Action情報を元にjspファイル格納ディレクトリを生成する
		for (ActionBean actionBean : _actionCache.getActionMap().values()) {
			File file = new File(AppConstants.JSP_WORK_BASE_PATH + actionBean.getHtmlPath());
			file.mkdirs();
		}
	}

	private void convertDataCondAttribute(FunctionBean function, Element element, boolean isSubmit)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		// Element配下のdata-cond属性の内容を取得する
		List<?> condNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_COND);
		for (Object condNode : condNodes) {
			Attribute condAttr = (Attribute)condNode;
			Element parentElement = condAttr.getParent();
			String value = condAttr.getValue();
			ConditionBean cond = mapper.readValue(value, ConditionBean.class);

			function.getCondList().add(cond);

			function.getCondMap().put(cond.getTableName() + cond.getColumnName(), cond.getOperator());
			if (isSubmit){
				String paramName = null;
				if (!StringUtils.isEmpty(cond.getTableName())) {
					paramName = function.getResult() + "__cond__" + cond.getTableName() + "__" + cond.getColumnName() + "__";
				} else {
					paramName = function.getResult() + "__cond__" + function.getTarget() + "__" + cond.getColumnName() + "__";
				}
				if (!StringUtils.isEmpty(cond.getOption())) {
					paramName += cond.getOption();
				}

				// name属性を追加する
				parentElement.addAttribute("name", paramName);
				// value属性を追加する
				if (StringUtils.isEmpty(parentElement.attributeValue("value"))) {
					if (!StringUtils.isEmpty(cond.getOption())){
						parentElement.addAttribute("value", "＜s:property value=”resultMap."  + function.getResult() + ".cond." + cond.getColumnName() + "__" + cond.getOption() + "”/＞");
					} else {
						parentElement.addAttribute("value", "＜s:property value=”resultMap."  + function.getResult() + ".cond." + cond.getColumnName() + "”/＞");
					}
				}
			} else {
//				parentElement.getParent().remove(parentElement);
			}
		}
	}

	private void convertDataBindAttribute(FunctionBean function, Element element)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		// Element配下のdata-bind属性の内容を取得する
		List<?> bindNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_BIND);
		for (Object bindNode : bindNodes) {
			Attribute bindAttr = (Attribute)bindNode;
			Element parentElement = bindAttr.getParent();
			String value = bindAttr.getValue();
			List<BindBean> bindList = mapper.readValue(value, new TypeReference<List<BindBean>>(){});

			for (BindBean bind : bindList) {
				String target = bind.getTarget();
				String format = bind.getFormat();
				boolean isEscape = Boolean.valueOf(bind.getEscape());
				String escapeStr = "";
				if (!isEscape){
					escapeStr = " escape=”false”";
				}

				if (!StringUtils.isEmpty(bind.getParamName())) {
					if ("input".equalsIgnoreCase(parentElement.getName()) || "textarea".equalsIgnoreCase(parentElement.getName())) {
						// name属性を追加する
						if (StringUtils.isEmpty(parentElement.attributeValue("name"))) {
							parentElement.addAttribute("name", bind.getParamName());
						}
					}

					if (!StringUtils.isEmpty(target)) {
						if (StringUtils.isEmpty(format)) {
							format = "{0}";
						}
						String ognlRoot = "resultMap.request.";
						String replaceValue = MessageFormat.format(format, "＜s:property value=”" + ognlRoot + bind.getParamName() + "”" + escapeStr + "/＞");

						if ("text".equalsIgnoreCase(target)) {
							String oldText = parentElement.getText();
							if (!oldText.startsWith("＜s:property")) {
								parentElement.setText(replaceValue);
							}
						} else {
							String oldAttr = parentElement.attributeValue(target);
							if (oldAttr == null || !oldAttr.startsWith("＜s:property")) {
								parentElement.addAttribute(target, replaceValue);
							}
						}
					}
				} else if (!StringUtils.isEmpty(bind.getColumnName())) {
					String name = function.getResult() + "__col__" + bind.getColumnName();
					if ("input".equalsIgnoreCase(parentElement.getName()) || "textarea".equalsIgnoreCase(parentElement.getName())) {
						// name属性を追加する
						if (StringUtils.isEmpty(parentElement.attributeValue("name"))) {
							parentElement.addAttribute("name", name);
						}
					}

					if (!StringUtils.isEmpty(target)) {
						if (StringUtils.isEmpty(format)) {
							format = "{0}";
						}

						// 祖先にdata-iteratorが存在するか確認する（存在した場合、resultMap.Result識別子は付与しない）
						Node ancestorIteratorNode = parentElement.selectSingleNode("ancestor-or-self::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
						String ognlRoot = "";
						if (ancestorIteratorNode == null) {
							// ページャーの場合
							if (bind.getColumnName().startsWith("PageInfo")){
								ognlRoot = "resultMap." + function.getResult();
							}else{
								ognlRoot = "resultMap." + function.getResult() + ".";
							}
						}
						String replaceValue = MessageFormat.format(format, "＜s:property value=”" + ognlRoot + bind.getColumnName() + "”" + escapeStr + "/＞");

						if ("text".equalsIgnoreCase(target)) {
							String oldText = parentElement.getText();
							if (!oldText.startsWith("＜s:property")) {
								parentElement.setText(replaceValue);
							}
						} else {
							String oldAttr = parentElement.attributeValue(target);
							if (oldAttr == null || !oldAttr.startsWith("＜s:property")) {
								parentElement.addAttribute(target, replaceValue);
							}
						}
					}

				} else if (bind.getArgList() != null) {
					if (!StringUtils.isEmpty(target)) {
						if (StringUtils.isEmpty(format)) {
							// TODO:argがあるのに、formatが未設定なのはダメ。
							break;
						}

						String[] args = new String[bind.getArgList().size()];

						for (int i = 0; i < bind.getArgList().size(); i++) {
							// 祖先にdata-iteratorが存在するか確認する（存在した場合、resultMap.Result識別子は付与しない）
							Node ancestorIteratorNode = parentElement.selectSingleNode("ancestor-or-self::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
							String ognlRoot = "";
							if (ancestorIteratorNode == null) {
								ognlRoot = "resultMap." + function.getResult() + ".";
							}
							args[i] = "＜s:property value=”" + ognlRoot + bind.getArgList().get(i) + "”" + escapeStr + "/＞";
						}

						String replaceValue = MessageFormat.format(format, args);

						if ("text".equalsIgnoreCase(target)) {
							String oldText = parentElement.getText();
							if (!oldText.startsWith("＜s:property")) {
								parentElement.setText(replaceValue);
							}
						} else {
							String oldAttr = parentElement.attributeValue(target);
							if (oldAttr == null || !oldAttr.startsWith("＜s:property")) {
								parentElement.addAttribute(target, replaceValue);
							}
						}
					}
				} else {
					if ("input".equalsIgnoreCase(parentElement.getName()) || "textarea".equalsIgnoreCase(parentElement.getName())) {
						// name属性を追加する
						if (StringUtils.isEmpty(parentElement.attributeValue("name"))) {
							parentElement.addAttribute("name", bind.getParamName());
						}
					}

					if (!StringUtils.isEmpty(target)) {
						if (StringUtils.isEmpty(format)) {
							format = "{0}";
						}
						String replaceValue = MessageFormat.format(format, "＜s:property /＞");

						if ("text".equalsIgnoreCase(target)) {
							String oldText = parentElement.getText();
							if (!oldText.startsWith("＜s:property")) {
								parentElement.setText(replaceValue);
							}
						} else {
							String oldAttr = parentElement.attributeValue(target);
							if (oldAttr == null || !oldAttr.startsWith("＜s:property")) {
								parentElement.addAttribute(target, replaceValue);
							}
						}
					}
				}
			}
		}
	}

	private void convertDataSortAttribute(FunctionBean function, Element element, boolean isSubmit)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		// Element配下のdata-sort属性の内容を取得する
		Node sortNode = element.selectSingleNode("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_SORT);
		if (sortNode != null) {
			Attribute sortAttr = (Attribute)sortNode;
			String value = sortAttr.getValue();

			List<String> sortList = mapper.readValue(value, new TypeReference<List<String>>(){});
			function.setSortList(sortList);

			if (isSubmit){
				if (sortList != null && sortList.size() > 0) {
					Element sortElement = sortAttr.getParent().addElement("input");
					sortElement.addAttribute("type", "hidden");
					sortElement.addAttribute("name", function.getResult() + "__sort____");
					sortElement.addAttribute("value", StringUtils.join(sortList, ","));
				}
			}
		}
	}

	private void convertDataCaseAttribute(FunctionBean function, Element element)
			throws JsonParseException, JsonMappingException, IOException {
		ObjectMapper mapper = new ObjectMapper();
		// Element配下のdata-case属性を書き換える
		List<?> caseNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_CASE);
		for (Object caseNode : caseNodes) {
			Attribute caseAttr = (Attribute)caseNode;
			Element parentElement = caseAttr.getParent();

			Node ancestorIteratorNode = parentElement.selectSingleNode("ancestor::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
			String ognlRoot = "";
			if (ancestorIteratorNode == null) {
				ognlRoot = "resultMap.";
			}

			// data-func属性に指定されたJSONをparseする
			List<CaseBean> caseList = mapper.readValue(caseAttr.getValue(), new TypeReference<List<CaseBean>>(){});

			StringBuilder testValue = new StringBuilder();
			for (int i = 0; i < caseList.size(); i++) {
				CaseBean caseInfo = caseList.get(i);

				// ページャーの場合
				if (caseInfo.getArgList().get(0).startsWith("PageInfo")){
					ognlRoot = "resultMap." + function.getResult() + "";
				}

				if ("size".equals(caseInfo.getType())) {
					testValue.append(ognlRoot);
					testValue.append(caseInfo.getArgList().get(0));
					testValue.append(".size");
					testValue.append(getOperatorString(caseInfo.getArgList().get(1)));
					testValue.append(caseInfo.getArgList().get(2));
				} else if ("compare".equals(caseInfo.getType())) {
					testValue.append(ognlRoot);
					testValue.append(caseInfo.getArgList().get(0));
					testValue.append(getOperatorString(caseInfo.getArgList().get(1)));
					testValue.append(caseInfo.getArgList().get(2));
				} else if ("instanceof".equals(caseInfo.getType())) {
					testValue.append(ognlRoot);
					testValue.append(caseInfo.getArgList().get(0));
					testValue.append(" instanceof ");
					testValue.append(caseInfo.getArgList().get(1));
				} else if ("!instanceof".equals(caseInfo.getType())) {
					testValue.append("!");
					testValue.append(ognlRoot);
					testValue.append(caseInfo.getArgList().get(0));
					testValue.append(" instanceof ");
					testValue.append(caseInfo.getArgList().get(1));
				}
				if (i < caseList.size() - 1) {
					testValue.append(" || ");
				}
			}

			// if要素を生成する
			Element ifElement = DocumentHelper.createElement("s:if");
			ifElement.addAttribute("test",testValue.toString());

			// if要素をdata-case属性が指定されていた要素位置に追加する
			Element grandParentElement = caseAttr.getParent().getParent();
			@SuppressWarnings("unchecked")
			List<Element> elements = grandParentElement.elements();
			elements.add(elements.indexOf(parentElement), ifElement);

			// data-case属性が指定された要素および子孫要素の階層を変更する
			List<?> childList = parentElement.selectNodes(".");
			grandParentElement.remove(parentElement);
			for (Object child : childList){
				Element childElement = (Element)child;
				ifElement.add(childElement);
			}
		}
	}

	private void convertDataIteratorAttribute(FunctionBean function, Element element) {
		// Element配下のdata-iterator属性を書き換える
		List<?> iteratorNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
		for (Object iteratorNode : iteratorNodes) {
			Attribute iteratorAttr = (Attribute)iteratorNode;
			Element parentElement = iteratorAttr.getParent();

			// 祖先にdata-iteratorが存在するか確認する（存在した場合、多重ループ）
			Node ancestorIteratorNode = parentElement.selectSingleNode("ancestor::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
			String ognlRoot = "";
			if (ancestorIteratorNode == null) {
				ognlRoot = "resultMap.";

				// ページャーの場合
				if (iteratorAttr.getValue().startsWith("PageInfo")){
					ognlRoot += function.getResult() + "";
				} else {
					if (CrudEnum.CRUD_SELECT_ALL_BY_PRIMARYKEY.getType().equals(function.getMethod())) {
						ognlRoot += function.getResult() + ".";
					}
				}
			}

			// iterator要素を生成する
			Element iteratorElement = DocumentHelper.createElement("s:iterator");
			iteratorElement.addAttribute("value", "%{" + ognlRoot + iteratorAttr.getValue() + "}");
			iteratorElement.addAttribute("status", "st");
			iteratorElement.addAttribute("var", "value");

			// iterator要素をdata-iterator属性が指定されていた要素位置に追加する
			Element grandParentElement = iteratorAttr.getParent().getParent();
			@SuppressWarnings("unchecked")
			List<Element> elements = grandParentElement.elements();
			elements.add(elements.indexOf(parentElement), iteratorElement);

			// data-iterator属性が指定された要素および子孫要素の階層を変更する
			List<?> childList = parentElement.selectNodes(".");
			grandParentElement.remove(parentElement);
			for (Object child : childList){
				Element childElement = (Element)child;
				iteratorElement.add(childElement);
			}
		}
	}

	private void deleteDataAttribute(Element element) {
		// data-cond,data-bind,data-sort,data-iterator属性を削除する
		// Element配下のdata-cond属性の内容を取得する
		List<?> condNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_COND);
		for (Object condNode : condNodes) {
			Attribute condAttr = (Attribute)condNode;
			Element parentElement = condAttr.getParent();
			parentElement.remove(condAttr);
		}

		// Element配下のdata-bind属性の内容を取得する
		List<?> bindNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_BIND);
		for (Object bindNode : bindNodes) {
			Attribute bindAttr = (Attribute)bindNode;
			Element parentElement = bindAttr.getParent();
			parentElement.remove(bindAttr);
		}

		// Element配下のdata-sort属性の内容を取得する
		Node sortNode = element.selectSingleNode("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_SORT);
		if (sortNode != null) {
			Attribute sortAttr = (Attribute)sortNode;
			Element parentElement = sortAttr.getParent();
			// data-sort属性を削除する
			parentElement.remove(sortAttr);
		}

		// Element配下のdata-case属性の内容を取得する
		List<?> caseNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_CASE);
		for (Object caseNode : caseNodes) {
			Attribute caseAttr = (Attribute)caseNode;
			Element parentElement = caseAttr.getParent();
			parentElement.remove(caseAttr);
		}

		// Element配下のdata-iterator属性の内容を取得する
		List<?> iteratorNodes = element.selectNodes("descendant::node()/@" + AppConstants.DATA_ATTRIBUTE_NAME_ITERATOR);
		for (Object iteratorNode : iteratorNodes) {
			Attribute iteratorAttr = (Attribute)iteratorNode;
			Element parentElement = iteratorAttr.getParent();
			parentElement.remove(iteratorAttr);
		}
	}

	private FunctionBean createFunctionInfo(String funcInfo, Element element, boolean isSubmit) {
		ObjectMapper mapper = new ObjectMapper();
		FunctionBean function = null;
		try {
			// data-func属性に指定されたJSONをparseする
			function = mapper.readValue(funcInfo, FunctionBean.class);
			if ("func_crud".equals(function.getType())) {
				// CRUDの場合
				function.setCondMap(new HashMap<String, String>());
				function.setCondList(new ArrayList<ConditionBean>());

				convertDataCondAttribute(function, element, isSubmit);

				convertDataBindAttribute(function, element);

				convertDataSortAttribute(function, element, isSubmit);

				convertDataCaseAttribute(function, element);

				convertDataIteratorAttribute(function, element);

			} else if ("func_sendmail".equals(function.getType())) {
				// メール送信の場合
				convertDataBindAttribute(function, element);
			}
		} catch (Exception e) {
			// TODO:WARNログを出力する
			e.printStackTrace();
		}
		return function;
	}

	private void writeJspDirective(String jspWorkFilePath, String jspFilePath) {

		// input
		FileInputStream fsi = null;
		InputStreamReader isr = null;
		BufferedReader br = null;

		// output
		FileOutputStream fos = null;
		OutputStreamWriter osw = null;
		PrintWriter pw = null;
		try {
			fsi = new FileInputStream(jspWorkFilePath);
			isr = new InputStreamReader(fsi, AppConstants.HTML_ENCODING);
			br = new BufferedReader(isr);

			fos = new FileOutputStream(new File(jspFilePath));
			osw = new OutputStreamWriter(fos, AppConstants.HTML_ENCODING);
			pw = new PrintWriter(osw);

			// jspディレクティブを出力する
			pw.println("<%@ page contentType=\"text/html; charset=UTF-8\" pageEncoding=\"" + AppConstants.HTML_ENCODING +"\"%>");
			pw.println("<%@ taglib prefix=\"s\" uri=\"/struts-tags\"%>");
			pw.println("<!DOCTYPE html>");
			pw.println("<html>");

			// ワークファイルをそのまま出力する
			String line = null;
			while ((line = br.readLine()) != null) {
				if (line.startsWith("<html")){
					continue;
				}
				pw.println(line.replaceAll("＜", "<").replace("＞", ">").replaceAll("”", "\"").replaceAll("＆", "&"));
			}
		} catch (Exception e1) {
			// TODO ログ出力とか
			e1.printStackTrace();
		} finally {
			if (pw != null) {
				pw.close();
			}
			if (osw != null){
				try {
					osw.close();
				} catch (IOException e) {
					// ignore
				}
			}
			if (fos != null){
				try {
					fos.close();
				} catch (IOException e) {
					// ignore
				}
			}
			if (br != null) {
				try {
					br.close();
				} catch (IOException e) {
					// ignore
				}
			}
			if (isr != null){
				try {
					isr.close();
				} catch (IOException e) {
					// ignore
				}
			}
			if (fsi != null){
				try {
					fsi.close();
				} catch (IOException e) {
					// ignore
				}
			}
		}
	}

	private void outputStrutsConfig() {
		SAXReader reader = new SAXReader();
		try {
			File file = new File(AppConstants.CONFIG_TEMPLATE_FILE_PATH).getAbsoluteFile();
			Document document = reader.read(file);
			Element packageElement = (Element) document.selectSingleNode("//package");

			// package配下の要素を一旦削除する
			Iterator<?> iterator = packageElement.elementIterator();
			while (iterator.hasNext()) {
				Element childElement = (Element) iterator.next();
				packageElement.remove(childElement);
			}

			for (ActionBean actionBean : _actionCache.getActionMap().values()) {
				// actionタグを追加する
				Element actionElement = packageElement.addElement("action");
				actionElement.addAttribute("name", actionBean.getName());
				actionElement.addAttribute("class", AppConstants.GENERIC_ACTION_NAME);

				// default result(success)
				Element defaultResultElement = actionElement.addElement("result");
				defaultResultElement.addAttribute("name", "success");
				defaultResultElement.addText(System.getProperty("file.separator") + AppConstants.JSP_DIR + actionBean.getHtmlPath() + AppConstants.DEFAULT_RESULT_FILE_NAME);

				if (actionBean.getSubmitExecuteMap() != null) {
					for (ExecuteBean execInfo: actionBean.getSubmitExecuteMap().values()){
						ResultBean resultInfo = execInfo.getResult();
						// resultタグを追加する
						Element resultElement = actionElement.addElement("result");
						resultElement.addAttribute("name", resultInfo.getName());
						resultElement.addText(resultInfo.getValue());
					}
				}

				if (actionBean.getDirectAccessMap() != null) {
					for (Entry<String, String> entry : actionBean.getDirectAccessMap().entrySet()){
						// resultタグを追加する
						Element resultElement = actionElement.addElement("result");
						resultElement.addAttribute("name", entry.getKey());
						resultElement.addText(entry.getValue());
					}
				}
			}

			OutputFormat outputFormat = new OutputFormat("\t", true, "UTF-8");
			XMLWriter xw = null;
			try {
				xw = new XMLWriter(new FileWriter(new File(AppConstants.CONFIG_FILE_PATH).getAbsoluteFile()), outputFormat);
				xw.write(document);
				xw.flush();
			} catch (IOException e) {
				// TODO:WARNログを出力する
				e.printStackTrace();
			} finally {
				if (xw != null) {
					try {
						xw.close();
					} catch (IOException e) {
						// ignore
					}
				}
			}
		} catch (DocumentException e) {
			// TODO:WARNログを出力する
			e.printStackTrace();
		}
	}

	private String getOperatorString(String operator) {
		String result = "=";
		if ("eq".equals(operator)) {
			result = " == ";
		} else if ("ne".equals(operator)) {
			result = " != ";
		} else if ("gt".equals(operator)) {
			result = " ＞ ";
		} else if ("ge".equals(operator)) {
			result = " ＞= ";
		} else if ("lt".equals(operator)) {
			result = " ＜ ";
		} else if ("le".equals(operator)) {
			result = " ＜= ";
		}
		return result;
	}

	private void convertPath(Attribute attr){
		if (attr == null || attr.getValue() == null) {
			return;
		}
		String value = attr.getValue();
		if (value.indexOf(AppConstants.WEB_ROOT_DIR) < 0){
			return;
		}
		String replaceValue = _event.getServletContext().getContextPath() +
				value.substring(value.indexOf(AppConstants.WEB_ROOT_DIR) + AppConstants.WEB_ROOT_DIR.length());
		attr.getParent().addAttribute(attr.getName(), replaceValue);
	}
}
