package com.ftinc.si.assist.test;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.ftinc.si.assist.test.Fson.NotSupportedClassException;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.NotFoundException;


public class CodeProcessor {
	public static String checkSyntaxOfCode(String src) {
		String err_msg = "";
		String warning_msg = "";
		int err_num = 0;
		int warning_num = 0;
		String jst_version = Tool.getPackageVersion(CtClass.class);
		jst_version = jst_version.replaceFirst("^(\\d+\\.\\d+)\\..*", "$1");
		Float n_version = Float.valueOf(jst_version);

		if (src == null) {
			return "ERROR: Source is null.\n";
		}
		String t_src = src;
		if (!t_src.startsWith("{")) {
			t_src = "{" + t_src + "}";
		}

		String str_break = "";
		Pattern pat_break = Pattern.compile("(break|continue)(\\s*\\w+\\s*;)");
		Matcher t_m = pat_break.matcher(t_src);
		while (t_m.find()) {
			if (str_break.length() > 0) {
				str_break += "\n\t";
			}
			str_break += t_m.group(1) + t_m.group(2) + "@" + t_m.start(1);
			err_num++;
		}
		if (str_break.length() > 0) {
			err_msg += "label not supported>>" + str_break + "\n";
		}
		String str_enum = "";
		Pattern pat_enum = Pattern.compile("enum\\s+[^\\s]+\\s*\\{");
		t_m = pat_enum.matcher(t_src);
		while (t_m.find()) {
			if (str_enum.length() > 0) {
				str_enum += "\n\t";
			}
			str_enum += t_m.group() + "@" + t_m.start();
			err_num++;
		}
		if (str_enum.length() > 0) {
			err_msg += "enum not supported.>>" + str_enum + "\n";
		}

		String str_array = "";
		Pattern pat_array = Pattern.compile("(=|new\\s+[\\w\\.]+\\[\\])\\s*\\{\\s*\\{");
		t_m = pat_array.matcher(t_src);
		while (t_m.find()) {
			if (str_array.length() > 0) {
				str_array += "\n\t";
			}
			str_array += t_m.group() + "@" + t_m.start();
			err_num++;
		}
		if (str_array.length() > 0) {
			err_msg += "initialization of array with >1 dimension not supported.>>" + str_array + "\n";
		}

		//FQNの検証にあたっては、import文を展開して問題があれば行う。
		String t_src2 = preProcess4Import(t_src);

		String str_decl = "";
		Pattern pat_decl = Pattern.compile("[\\s\\};\\{][A-Z]\\w+(\\[\\]|\\w)\\s+\\w[\\w\\[\\]]*\\s*(=[^;]+;|;)");
		t_m = pat_decl.matcher(t_src2);
		if (t_m.find()) {
			t_m = pat_decl.matcher(t_src);
			while (t_m.find()) {
				if (str_decl.length() > 0) {
					str_decl += "\n\t";
				}
				str_decl += t_m.group() + "@" + t_m.start();
				err_num++;
			}
			if (str_decl.length() > 0) {
				err_msg += "FQN is required.>>" + str_decl + "\n";
			}
		}

		String str_cast = "";
		Pattern pat_cast = Pattern.compile("\\([^\\(\\)\\s]+\\[\\]\\s*\\)\\s*VGET\\([^\\)]*\\)");
		t_m = pat_cast.matcher(t_src);
		while (t_m.find()) {
			if (str_cast.length() > 0) {
				str_cast += "\n\t";
			}
			str_cast += t_m.group() + "@" + t_m.start();
			warning_num++;
		}
		if (str_cast.length() > 0) {
			warning_msg += "Trying to cast Object to Array>>" + str_cast + "\n";
		}

		//旧版での制限
		if (n_version < 2.2) {
			//finally
			String str_final = "";
			Pattern pat_final = Pattern.compile("\\}\\sfinally\\s*\\{");
			t_m = pat_final.matcher(t_src);
			while (t_m.find()) {
				if (str_final.length() > 0) {
					str_final += "\n\t";
				}
				str_final += t_m.group(1) + t_m.group(2) + "@" + t_m.start(1);
				warning_num++;
			}
			if (str_final.length() > 0) {
				warning_msg += "'} finally{' was not supported.>>" + str_final + "\n";
			}

			//initialization of Array
			String str_array2 = "";
			Pattern pat_array2 = Pattern.compile("(=|new\\s+[\\w\\.]+\\[\\])\\s*\\{\\s*\\{[^\\{\\}]*\\}\\s*;");
			t_m = pat_array2.matcher(t_src);
			while (t_m.find()) {
				if (str_array2.length() > 0) {
					str_array2 += "\n\t";
				}
				str_array2 += t_m.group() + "@" + t_m.start();
				warning_num++;
			}
			if (str_array2.length() > 0) {
				warning_msg += "initialization of array with 1 dimension not supported.>>" + str_array2 + "\n";
			}

			//Catch
			String str_catch = "";
			Pattern pat_catch = Pattern.compile("\\}\\s*catch\\s+\\([^\\)+]\\)\\s*\\{");
			t_m = pat_catch.matcher(t_src);
			while (t_m.find()) {
				if (str_catch.length() > 0) {
					str_catch += "\n\t";
				}
				str_catch += t_m.group() + "@" + t_m.start();
				warning_num++;
			}
			if (str_catch.length() > 0) {
				warning_msg += "'catch' was not supported.>>" + str_catch + "\n";
			}

			//synchronized
			String str_sync = "";
			Pattern pat_sync = Pattern.compile("^.*syncronized[^\\(]+");
			t_m = pat_sync.matcher(t_src);
			while (t_m.find()) {
				if (str_sync.length() > 0) {
					str_sync += "\n\t";
				}
				str_sync += t_m.group() + "@" + t_m.start();
				warning_num++;
			}
			if (str_sync.length() > 0) {
				warning_msg += "'synchronized' was not supported.>>" + str_sync + "\n";
			}
		}

		//warningをlogに書き込む。
		String msg = "";
		if (err_num > 0) {
			msg += Integer.toString(err_num) + " errors found. " + err_msg;
		}
		if (warning_num > 0) {
			msg += Integer.toString(warning_num) + " warnings found. " + warning_msg;
		}
		if (msg.length() > 0) {
			Tool.logForTesting(null, msg);
		}
		if (err_num == 0) {
			return null;
		}
		return "ERROR:" + Integer.toString(err_num) + " errors found. " + err_msg + "(Warnings may be in log file.)";
	}

	//ソース中のコメントからインポート文の情報を格納
	public static HashMap<String, String> _importList = null;

	//特殊構文で規定されたimport文をソースに展開する。
	public static String preProcess4Import(String src) {
		if (_importList == null) {
			_importList = new HashMap<String, String>();
		}
		_importList.clear();

		String t_src = src;

		//import文を見つける
		Pattern t_importPT = Pattern.compile("import\\s+([a-z][\\w\\.]+)\\.([A-Z\\w\\[\\]]+);");//行頭、行末
		Matcher t_m = t_importPT.matcher(src);
		while (t_m.find()) {
			String w_str = t_m.group();
			String s_name = t_m.group(2);
			String c_name = t_m.group(1) + "." + s_name;

			_importList.put(s_name, c_name);
			t_src = t_src.replaceFirst(Pattern.quote(w_str), "");
		}

		src = t_src;

		//単純クラス名を置き換える。\\[は配列を考慮。
		Pattern t_sclass = Pattern.compile("([\\{\\;\\s\\(])([A-Z][\\w\\[\\]]+)(\\s*[\\)\\(\\.\\[]?\\w?)");
		t_m = t_sclass.matcher(src);
		while (t_m.find()) {
			String w_str = t_m.group();
			String t_h = t_m.group(1);
			String s_class = t_m.group(2);
			String s_tail = t_m.group(3);

			//次の$$$は以後のマッチングを回避するため。
			if (_importList.containsKey(s_class)) {
				t_src = t_src.replaceFirst(Pattern.quote(w_str), Matcher.quoteReplacement(t_h + _importList.get(s_class) + "$$$" + s_tail));
			}
		}

		//int, float, double, booleanの変更。
		String prim_int = "^(\\{\\;[\\s\\(])int(\\s*[\\)\\(\\.\\[]*\\w?)$";
		t_src = t_src.replaceAll(prim_int, "$1 Integer $2");

		String prim_float = "^(\\{\\;[\\s\\(])float(\\s*[\\)\\(\\.\\[]*\\w?)$";
		t_src = t_src.replaceAll(prim_float, "$1 Float $2");

		String prim_w = "^(\\{\\;[\\s\\(])double(\\s*[\\)\\(\\.\\[]*\\w?)$";
		t_src = t_src.replaceAll(prim_w, "$1 Double $2");

		String prim_b = "^(\\{\\;[\\s\\(])boolean(\\s*[\\)\\(\\.\\[]*\\w?)$";
		t_src = t_src.replaceAll(prim_b, "$1 Boolean $2");

		return t_src.replaceAll(Pattern.quote("$$$"), "");
	}

	//publicなのはメソッド編集ダイアログで使用するため。仮想メンバ変数名とクラス名の組。
	public static HashMap<String, String> _memVars = new HashMap<String, String>();

	//ソース文中にある仮想メンバ変数名とクラス名の対照表を作る。headは仮想変数の冠。
	public static void preProcess4MemInfos(String src) {
		_memVars.clear();

		//Primitiveなクラス宣言を拾う
		Pattern t_sclass0 = Pattern.compile("[\\{\\;\\s,\\(/]field\\s+([\\w]+)\\s+([\\w\\$]+)\\s*[;=,\\)\\[]");
		Matcher t_m = t_sclass0.matcher(src);
		while (t_m.find()) {
			String c_name = t_m.group(2);
			String v_name = t_m.group(3);

			_memVars.put(v_name, Tool.primitiveToFQN(c_name));
		}

		//FQNのクラス宣言を拾う。fieldの前の/は」コメント
		Pattern t_sclass1 = Pattern.compile("[\\{;\\s/]field\\s+([a-z][\\w\\.]+\\.[A-Z][\\w\\[\\]]+)\\s+([\\w\\$]+)\\s*[;=,\\)\\[]");
		t_m = t_sclass1.matcher(src);
		while (t_m.find()) {
			String c_name = t_m.group(1);
			String v_name = t_m.group(2);

			_memVars.put(v_name, c_name);
		}
	}

	//仮想メンバ変数はHashMapの_tableで管理しているのだが、仮想メンバ変数を使用している箇所を置き換える。
	private static String preProcess4MemUse(String src) {
		String t_src = src;
		//セット処理をし、返却値を返す。
		String fValueStr = "(CNAME)com.ftinc.si.assist.test.Tool#fieldValue(_parent,\"VNAME\",";

		//親を使用しているなら親にアクセスできるようにする。
		Pattern t_parent = Pattern.compile("/\\*\\s+parent=(\\$[0-9]+)\\s+\\*/");
		Matcher t_m = t_parent.matcher(t_src);
		if (t_m.find()) {
			t_src = t_m.replaceFirst(Matcher.quoteReplacement("java.lang.Object _parent=com.ftinc.si.assist.run.VCentral#getValue(\"" + t_m.group(1) + "\");"));
		} else {
			if (_memVars.size() > 0) {
				t_src = t_src.replaceFirst("\\s*\\{", Matcher.quoteReplacement("{java.lang.Object _parent=this;"));
			}
		}

		//宣言文だけの場合
		Pattern t_pat0 = Pattern.compile("([\\{;\\s/])field\\s+(\\w+\\.[\\.\\w\\[\\]]+)\\s+(\\w+)\\s*;");
		t_m = t_pat0.matcher(t_src);
		if (t_m.find()) {
			//宣言文のみを消す
			t_src = t_m.replaceAll("$1");
		}

		//宣言文と同時のセットの場合
		Pattern t_pat1 = Pattern.compile("([\\{;\\s/])field\\s+([\\.\\w\\[\\]]+)\\s+([\\w\\$]+)\\s*=\\s*(.+?)\\s*;");
		t_m = t_pat1.matcher(t_src);
		while (t_m.find()) {
			//宣言部を除き、値のセットのみに変える。
			String cname = Tool.primitiveToFQN(t_m.group(2));
			String vname = t_m.group(3);
			String repl = fValueStr.replace("CNAME", cname).replace("VNAME", vname);
			t_src = t_m.replaceFirst("$1" + Matcher.quoteReplacement(repl) + "$4);");

			t_m = t_pat1.matcher(t_src);
		}

		//セットしている箇所を抽出。
		String set_reg = "([;\\s\\}\\{/])(\\w+)\\s*=\\s*([^=].*?)\\s*;";
		Pattern set_pat = Pattern.compile(set_reg);
		t_m = set_pat.matcher(t_src);
		while (t_m.find()) {
			//t_srcの検索ヒットした箇所を、一か所ことに丁寧に処理し、t_srcに反映する。
			String t_key = t_m.group(2);
			if (_memVars.containsKey(t_key)) {
				//field宣言された変数にセットされている箇所では、変数名を消し、セットの処理を直接書く。
				String cname = _memVars.get(t_key);

				String target = t_m.group();
				String head = t_m.group(1);
				String repl = fValueStr.replace("CNAME", cname).replace("VNAME", t_key);
				String val = t_m.group(3);

				t_src = t_src.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement(head + repl + val + ");"));

				t_m = set_pat.matcher(t_src);
			}
		}

		//括弧で囲まれた内部でセットしている箇所を抽出。例：if ((a=new XXX())!=null) {
		String set_reg2 = "\\(\\s*(\\w+)\\s*=\\s*([^=].*?)\\)";
		Pattern set_pat2 = Pattern.compile(set_reg2);
		t_m = set_pat2.matcher(t_src);
		while (t_m.find()) {
			//t_srcの検索ヒットした箇所を、一か所ことに丁寧に処理し、t_srcに反映する。
			int start = t_m.start();
			String target = Tool.getClosedBlock(t_src, start, "(", ")");

			final Pattern set_pat3 = Pattern.compile("^\\(\\s*(\\w+)\\s*=\\s*([^=].*?)\\s*\\)$");
			Matcher t_m2 = set_pat3.matcher(target);
			if (t_m2.find()) {
				String t_key = t_m2.group(1);
				if (_memVars.containsKey(t_key)) {
					//field宣言された変数にセットされている箇所では、変数名を消し、セットの処理を直接書く。
					String cname = _memVars.get(t_key);
					String repl = fValueStr.replace("CNAME", cname).replace("VNAME", t_key);
					String val = t_m2.group(2);

					t_src = t_src.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement("(" + repl + val + "))"));
				}
			}
			t_m = set_pat2.matcher(t_src);
		}

		//参照している箇所
		String ref_reg = "([\\s=\\+\\-\\*/\\(,\\)><=])(\\w+)([!\\s\\.\\+\\-\\*/<>\\);,\\[])";
		Pattern ref_pat = Pattern.compile(ref_reg);
		t_m = ref_pat.matcher(t_src);
		while (t_m.find()) {
			//srcの検索ヒットした箇所を、一か所ことに丁寧に処理し、t_srcに反映する。
			String t_key = t_m.group(2);

			if (_memVars.containsKey(t_key)) {
				String cname = _memVars.get(t_key);
				String target = t_m.group();
				String head = t_m.group(1);
				String expr = t_m.group(3);

				String repl = fValueStr.replace("CNAME", cname).replace("VNAME", t_key);
				t_src = t_src.replaceFirst(Pattern.quote(target), Matcher.quoteReplacement(head + "(" + repl + "null))" + expr));

				t_m = ref_pat.matcher(t_src);
			}
		}

		return t_src;
	}

	//第一引数内の文字列に対して、第二引数の文字("",')を第三引数で置き換える。
	//置き換えた結果を0番目、１番目以降は出現した文字列。
	public static ArrayList<String> escDQStrings(String src, String repl) {
		return escDQStrings2(src, repl, -1);
	}

	public static ArrayList<String> escDQStrings2(String src, String repl, int n) {
		if (repl.matches("!!|##")) {
			Tool.alertMSG(null, repl + " includes bad string!!");
		}

		//文字列の中の単なる文字としての「\\"」を_BACK_DQT_に退避する。混同を避けるため。
		//①バックスラッシュ文字の直後に文字の"がある場合。
		src = Tool.regReplaceAll(src, "(?<!\\\\)" + Pattern.quote("\\\""), "_BACKDQT_");

		//②通常のバックスペースをエスケープ。①で見分けを付けたため。
		String t_src = Tool.replaceB(src, "\\\\", "$" + repl + "_BQ$");//\\をエスケープ。

		//④文字列をエスケープ
		// Javaの場合、"aaa"bbb"ccc"となったら、"aaa","bbb","ccc"がマッチする。whileの最後でも再度マッチさせる。
		String dq_reg ="\"(.*?)\"";

		ArrayList<String> t_list = new ArrayList<String>();
		Integer i = 1;

		final Pattern t_p = Pattern.compile(dq_reg);
		Matcher t_m = t_p.matcher(t_src);
		while (t_m.find()) {
			//srcの検索ヒットした箇所を、一か所ことに丁寧に処理し、t_srcに反映する。

			String w_str = t_m.group(0);
			String w_str2 = Tool.replaceB(t_m.group(1), "$" + repl + "$", "\\\"");//修復用ブロックでは、文字としての￥"に戻す。
			if (w_str2.indexOf("!##!") >= 0) {
				Tool.alertMSG(null, w_str2 + " is strange.");
			}
			if (n > 1 && w_str2.length() > n) {
				//nが指定されていたらカットする。大きな文字列の処理を少しでも早くするため。
				w_str2 = w_str2.substring(0, n);
			}
			t_list.add(w_str2);

			//置き換え文字列=""で囲んだrepl+番号を振っておく。
			t_src = Tool.replaceB(t_src, w_str, "!##!" + repl + i.toString() + "!##!");
			t_m = t_p.matcher(t_src);
			i++;
		}

		//④残った''の囲み領域をエスケープ
		String dq_reg2 ="'(.*?)'";

		final Pattern t_p2 = Pattern.compile(dq_reg2);
		t_m = t_p2.matcher(t_src);
		while (t_m.find()) {
			//srcの検索ヒットした箇所を、一か所ことに丁寧に処理し、t_srcに反映する。

			String w_str = t_m.group(0);
			String w_str2 = Tool.replaceB(t_m.group(1), "$" + repl + "$", "\\\"");//修復用ブロックでは、文字としての￥"に戻す。

			//元の文字列に戻す。
			w_str2 = restoreDQString(w_str2, repl, t_list);
			t_list.add(w_str2);

			//置き換え文字列=""で囲んだrepl+番号を振っておく。
			t_src = Tool.replaceB(t_src, w_str, "!##!" + repl + i.toString() + "!##!");
			t_m = t_p2.matcher(t_src);
			i++;
		}
		t_src = Tool.replaceB(t_src, "!##!", "\"");
		t_list.add(0, t_src);
		return t_list;
	}

	//エスケープした文字列(第一引数)を元に戻す。第四引数はescDQStrinsの結果。
	public static String restoreDQString(String wstr, String repl, ArrayList<String> list) {
		String t_str = wstr;

		for (int i = 1; i < list.size(); i++) {
			String t_repl = list.get(i);
			t_str = Tool.regReplaceAll(t_str, "\"" + Pattern.quote(repl) + Integer.toString(i) + "\"", "\"" + Matcher.quoteReplacement(t_repl) + "\"");
		}
		//文字としてのtargetを回復する。
		t_str = Tool.replaceB(t_str, "$" + repl + "＿BQ$", "\\\\");
		t_str = Tool.replaceB(t_str, "_BACKDQT_", Matcher.quoteReplacement("\\\""));

		return t_str;
	}


	//c：test.implのメソッドのソースを前処理する。
	public static String preProcessMain(CtClass c, CtBehavior b, String src) {
		//最初にサポート外の構文の有無をチェックする。
		String msg = checkSyntaxOfCode(src);
		//ERRORの時は、これ以上の処理を行わない。
		if (msg != null) {
			//null以外はエラーが返る。
			return null;
		}

		//{}に囲まれていない場合は、補完する。
		if (!src.startsWith("{")) {
			src = "{" + src + "}";
		}

		String str_q = "|_%%_|";

		//一旦、ソース中の固定文字列をstr_qでエスケープする。
		ArrayList<String> t_list = escDQStrings(src, str_q);

		String t_src = t_list.get(0);
		//import文を取り込む。
		t_src = preProcess4Import(t_src);

		//マクロの展開：VCentralへの自動アクセス。Fakeの生成。
		t_src = preProcess4Macro(t_src);

		//仮想メンバ変数のリストを作成する。
		//Assertでは仮想メンバ変数は利用できないので除外。
		preProcess4MemInfos(t_src);

		//仮想メンバ変数のプリコンパイル。
		//Assertでは仮想メンバ変数は利用できないので除外。
		t_src = preProcess4MemUse(t_src);

		//エスケープした文字列を元に戻す。
		return restoreDQString(t_src, str_q, t_list);
	}

	//マクロ変換表
	private static final String[][] reg_macros = {
			{"VGET\\(", "com.ftinc.si.assist.run.VCentral#getValue("},
			{"VSET\\(", "com.ftinc.si.assist.test.Tool#fieldValue("},
			{"MOCK\\(", "com.ftinc.si.assist.run.VCentral#getFakeObject("},
			{"XROOT\\(", "com.ftinc.si.assist.run.VCentral#loadXML("},
			{"XELM\\(", "com.ftinc.si.assist.run.VCentral#xmlContent("},
			{"OUT\\(", "com.ftinc.si.assist.run.VCentral#logging("},
			{"ASSERTING\\(", "asserting("},
		};

	private static final Pattern s_aliasMacro = Pattern.compile("\\$([^\\(]+)\\(");

	//①VCentralへの便利アクセスのための仕掛け:　VGET(String name)
	//②Fakeの生成：　MOCK(String classname)
	private static String preProcess4Macro(String src) {
		String t_src = src;

		Matcher t_m;
		for (int i = 0; i < reg_macros.length; i++) {
			String[] temp_reg = reg_macros[i];

			src = t_src;
			Pattern dec_pat = Pattern.compile(temp_reg[0]);
			t_m = dec_pat.matcher(src);
			if (t_m.find()) {
				t_src = t_m.replaceAll(Matcher.quoteReplacement(temp_reg[1]));
			}
		}

		//別名に登録されたマクロをコンパイル可能な形式にする。但し、静的なメソッドに限る。
		src = t_src;
		t_m = s_aliasMacro.matcher(src);
		while (t_m.find()) {
			String w_str = t_m.group();
			String t_repl = Tool.convert2Alias(t_m.group(1));
			if (!t_m.group(1).equals(t_repl)) {
				//別名が登録されている。
				t_src = t_src.replaceFirst(Pattern.quote(w_str), Matcher.quoteReplacement(t_repl + "("));
			}
		}

		return t_src;
	}



	//////////////////////////////////////////////////////
	//ソースを解析し、文字列とクラスの対照表を作成する。
	//最初は$*、二番目はField、三番目以降はpublicメソッド
	//第二引数は、現在のラウンドで見つかった文字列。()の中は.*になっている。
	//返却値は、その時点でのオブジェクト文字列とクラスの対照表。副作用をmapに残さないためクローンを作る。
	// $n.field.xxx(yyy).xxx(yyy)
	// xxx = [$n, xxx, (キャスト)].....;
	// 括弧の中の文字列を抽出する。
	// (class name)[$n, xxx]...()：その式の最後にかかる。式が括弧で囲まれている場合も同じにあつかう。（今は放置）
	public static HashMap<String, Class<?>> analyzeVarToClass(StringBuilder sb_src, HashMap<String, Class<?>> map) throws ClassNotFoundException,
				SecurityException, NoSuchFieldException, NoSuchMethodException, NotFoundException, InstantiationException,
				IllegalArgumentException, IllegalAccessException, InvocationTargetException, ParseException, NotSupportedClassException {
		String src = sb_src.toString();

		//コメントの削除
		src = src.replaceAll("^//.*?\n$", "");

		if (!src.startsWith("{")) {
			src = "{" + src + "}";
		}

		@SuppressWarnings("unchecked")
		HashMap<String, Class<?>> t_res = (HashMap<String, Class<?>>)map.clone();

		//マクロを考慮する。(MOCK|VGET)+\\(.+?\\)はObjectクラスである。
		//MOCK/VGET(...)をシンボルに変える。クラスマップへは登録済み。
		int num = 0;
		Matcher t_m = _atPat.matcher(src);
		while (t_m.find()) {
			String at_str = t_m.group();

			t_res.put(at_str, Object.class);
		}

		//XELMはStringクラスである。
		t_m = _atStrFnPat.matcher(src);
		while (t_m.find()) {
			String __var = t_m.group();
			t_res.put(__var, String.class);
		}
		//宣言部分から、変数とクラスを探す。_memVarsにvnameとcnameが入る。java.lang配下のsimpleNameはFQNになっている。
		//★★宣言部分は置き換えをしない。
		preProcess4MemInfos(src);//仮想メンバ変数を探すのではないので、第二引数は空文字。

		//ソース中のメソッドを含む文字列「[a-z_][\\w\\.]+\\.[a-z_]\\w+(.*)」をすべてシンボルvar[0-9]+に置き換える。
		//そのための入れ物である。
		HashMap<String, String> var_dic = new HashMap<String, String>();

		//宣言部分から変数名とクラスのマップを作る。
		for(Entry<String, String> entry : _memVars.entrySet()) {
			String cname = entry.getValue();
			String vname = entry.getKey();

			if (!var_dic.containsKey(vname)) {
				String t_var = "var" + Integer.toString(num);
				try {
					Class<?> t_c = null;

					//宣言していたのは配列かどうかを見極め、クラスをt_cに代入する。
					if (cname.endsWith("[]")) {
						Class<?> comp_c = Tool.forName(cname.replaceFirst(Pattern.quote("[]"), ""));
						t_c = Array.newInstance(comp_c, 0).getClass();
					} else {
						t_c = Tool.forName(cname);
					}
					num ++;
					var_dic.put(t_var, vname);//変数名マップ
					var_dic.put(vname, vname);//宣言済みの変数なので、この変数に限り、変換する必要なし。
					t_res.put(vname, t_c);//クラスマップ
				} catch (ClassNotFoundException e) {
					Tool.logIfDebug(e, "@CodeProcessot#checkReturnType");
				}
			}
		}

		//メソッド内のソースを解析する。
		//メソッド呼び出し、メンバ変数を抽出し、元の文字列とシンボルのマップを作る。元文字列はシンボルに置き換える。
		//宣言部分は、元文字列とクラスの対応を追加する。
		ArrayList<String> t_declared = new ArrayList<String>();
		String t_src = analyzeMethod(src, num, var_dic, t_res, t_declared);
		//退避していたvar[0-9]+の宣言部分を復元する。
		for (int i = 0; i < t_declared.size(); i++) {
			t_src = t_src.replaceFirst("__DECLARED__" + Integer.toString(i) + "__" , Matcher.quoteReplacement(t_declared.get(i)));
		}
		sb_src.replace(0, sb_src.length(), t_src);//StringBuilderに格納する。

		//②すべてのシンボルに対して、シンボル辞書をたどって、元の文字列を求め、それに対してクラスを割り当てる。
		//メソッドの特定は最初のメソッドでよい。要するに名前だけ。
		//valueには、$n, $n.field もしくは xxx.yyy(...)が入っている。
		for(Entry<String, String> entry : var_dic.entrySet()) {
			String t_var = entry.getKey();
			if (t_var.matches("^var[0-9]+$")) {
				//t_varなら必ずクラスに結びついている。
				String t_value = restoreSymbol(entry.getValue(), var_dic);//第一引数はシンボル化した文字列。返却値は本来の文字列。
				if (t_value != null) {
					if (!t_res.containsKey(t_value)) {
						//本来の文字列
						t_res.put(t_value, t_res.get(t_var));
					}
				}
			}
		}
		//本来の文字列が必ずクラスに結びついている。
		return t_res;
	}

	//MOCK/VGET(...)のパターン
	private static final Pattern _atPat = Pattern.compile("(MOCK|VGET)\\([^\\)\\(]+?\\)");

	//XELMのパターン
	private static final Pattern _atStrFnPat = Pattern.compile("XELM\\(.+?\\)");

	//宣言のパターン。
	private static final Pattern _declarePat = Pattern.compile("([\\}\\{;]\\s*)([a-z_]\\w+\\.[\\w\\.\\$\\[\\]]+)(\\s+[\\w\\$]+[;=])");

	//obj.fieldのパターン。後ろは括弧ではない。
	private static final Pattern _fieldPat = Pattern.compile("([\\}\\{\\s,\\+\\-\\*/\\(])([\\$\\w]+)\\.([a-z_]\\w+)([\\.;=\\-\\+\\*/\\s\\[])");

	//オブジェクトやメソッドは小文字から始まる。
	private static final Pattern _methodBraPat = Pattern.compile("([\\{\\};\\s\\(,=\\+\\-\\*/])([a-z_\\$][_\\w]*)\\.([a-z_]\\w+)\\(");

	//括弧で全体を囲むパターン。その後がピリオドでもよい。
	//メソッドの変数化は終わっているとする。((class)xxx.vv())
	private static final Pattern _cast1Pat = Pattern.compile("\\(([a-z][\\w\\.]+\\.[A-Z][\\w_\\[\\]]+)\\)(.*?)[\\s;\\+\\-\\*]");
	private static final Pattern _cast2Pat = Pattern.compile("\\(\\(([a-z][\\w\\.]+\\.[A-Z][\\w_\\[\\]]+)\\).*\\)");

	//機能：シンボルとクラスのマップを作る。これにより、フィールド、メソッド呼び出しのクラスを逆に引けるようになる。
	//前提：このメソッドを呼ぶ前に余計な空白を除くこと。(),=+などの前後
	//シンボル化：ソース内のフィールド、メソッドをシンボルに置き換える。要するに「.」のない表現にする。
	//　　　num：シンボルの名前付けに使う。再帰呼び出しがあるため。返却値は再帰呼び出しの結果を再処理するため。
	private static String analyzeMethod(String src, int num, HashMap<String, String> var_map, HashMap<String, Class<?>> cmap, ArrayList<String> declared_list) throws ClassNotFoundException,
				NoSuchMethodException, SecurityException, NotFoundException, InstantiationException, IllegalArgumentException, IllegalAccessException,
				InvocationTargetException, ParseException, NotSupportedClassException {
		if (src == null || src.length() == 0) {
			//空文字なら何もしない。
			return src;
		}
		String t_src = src;//後で比較するために保持。

		if (declared_list.size() == 0) {
			//最初の処理として、宣言部のクラス名をフィールドと間違えないようにする。宣言文解析は終わっていてマップには登録済み。
			Matcher t_m2 = _declarePat.matcher(src);
			if (t_m2.find()) {
				String i_src = t_m2.group(2);
				src = t_m2.replaceFirst("$1__DECLARED__" + Integer.toString(declared_list.size()) + "__$3");
				declared_list.add(i_src);
				t_m2 = _declarePat.matcher(src);
			}
		}

		//ここに来る前にStringなどjava.lang配下のFQN化は終わっている。
		//フィールドよりメソッドの判定を先に行う。(の前をフィールドと誤判断する場合を避けるため。
		//メソッド呼び出しをシンボルに置き換える。
		Matcher t_m = _methodBraPat.matcher(src);
		if (t_m.find()) {
			String t_obj = t_m.group(2);
			String t_methodName = t_m.group(3);
			int begin = t_m.end() - 1;
			String w_str = Tool.getClosedBlock(src, begin, "(", ")");//括弧を含む内側
			String w_str2 = w_str.substring(1,  w_str.length() - 1).replaceAll("\\s", "");
			String t_repl = t_obj + "." + t_methodName + w_str;

			String z_str = "";
			if (w_str2.length() > 0) {
				z_str = analyzeMethod(w_str2, num, var_map, cmap, declared_list);//入れ子になっているメソッドの引数を置き換えた
			}

			String t_var = "var" + Integer.toString(num);
			num ++;

			//括弧の中も含めた文字列をシンボルとして管理する。
			src = src.replaceFirst(Pattern.quote(t_repl), t_var);
			var_map.put(t_var, t_obj + "." + t_methodName + "(" + z_str + ")");

			//引数の型を導いて、メソッドを確定する。z_strは､でつながっている引数。
			Class<?>[] a_clist = new Class<?>[] {};
			if (z_str.length() > 0) {
				ArrayList<String> t_4restore = Fson.escapeBraket(z_str, null);

				String[] t_args = t_4restore.get(0).split(",");
				if (t_args.length > 0) {
					ArrayList<Class<?>> c_list = new ArrayList<Class<?>>();
					for (int j = 0; j < t_args.length; j ++) {
						Class<?> a_c = cmap.get(Fson.restoreBraket(t_args[j], t_4restore));
						if (a_c != null) {
							c_list.add(a_c);
						} else {
							Object temp = Fson.fromJson(Fson.restore(t_args[j]), null);
							if (temp != null) {
								c_list.add(temp.getClass());
							}
						}
					}
					a_clist = c_list.toArray(new Class<?>[c_list.size()]);
				}
			}

			Class<?> t_c = cmap.get(t_obj);
			if (t_c != null) {
				Method ms = Tool.getMethod(t_c, t_methodName, a_clist);
				if (ms != null) {
					Class<?> t_class = ms.getReturnType();//overloadがあったとしても返却型は同じと信じる!!!!
					cmap.put(t_var, t_class); //マッチした構文が表すクラスを登録

					String t_cname = t_class.getName();
					if (t_class.isArray()) {
						t_cname = t_class.getComponentType().getName() + "[]";
					}
					src = insertDeclaredPartToSrc(src, t_cname, t_var, t_obj + "." + t_methodName + "(" + z_str + ")", declared_list);
				}
			}
			//キャストの前にメソッドとフィールドに関する処理を終える。
			src = analyzeMethod(src, num, var_map, cmap, declared_list);
		}

		//フィールド呼び出し部分をシンボルに置き換える。
		t_m = _fieldPat.matcher(src);
		if (t_m.find()) {
			String o_str = t_m.group();
			String head_str = t_m.group(1);
			String obj_str = t_m.group(2);
			String f_str = t_m.group(3);
			String last_str = t_m.group(4);

			//先頭がオブジェクトでない限りフィールドアクセスはできない。
			//var_mapに登録されている（このメソッド内で抽出した可能性あり）。そのValueがKeyと同じなら、既に宣言済みなので、以下の処理は不要。
			if (var_map.containsKey(obj_str) && cmap.containsKey(obj_str)) {
				Class<?> t_c = cmap.get(obj_str);
				String t_cname = t_c.getName();
				if (!t_c.equals(Object.class)) {
					//Objectクラスは最上位なのでそれ以上は解析しない。
					String t_var = "var" + Integer.toString(num);
					num ++;
					var_map.put(t_var, obj_str + "." + f_str);

					src = src.replaceFirst(Pattern.quote(o_str), Matcher.quoteReplacement(head_str + t_var + last_str));

					//identifySymbolで属性のクラスを識別して登録する（!!!）ので、ここでクラスをmapに登録する必要はない。
					Field t_f = Tool.getField(t_c, f_str);
					if (t_f != null) {
						Class<?> r_c = t_f.getType();
						if (r_c.isArray()) {
							Class<?> comp_c = r_c.getComponentType();
							t_cname = comp_c.getName() + "[]";
						}
						cmap.put(t_var, r_c);
						String i_src = "(" + t_cname +")com.ftinc.si.assist.test.Tool.getFieldValue(" + obj_str + ",\"" + f_str + "\")";
						src = insertDeclaredPartToSrc(src, t_cname, t_var, i_src, declared_list);
					} else {
						//fakeのフィールドにアクセスしようとしているかどうかは、ここでは不明。
						ArrayList<FakeMethodRecord> fakes = Tool._db().getFakeMethods(t_c.getName(), Tool.version);
						if (fakes.size() > 0) {
							//fakeである。
							t_cname = Tool.getFieldTypeNameForFake(t_c.getName(), f_str);
							if (t_cname == null) {
								Tool.alertMSG(null, t_c.getName() + " has not field of " + f_str);
							} else {
								cmap.put(t_var, Tool.forName(t_cname));
								String i_src = "(" + t_cname +")com.ftinc.si.assist.test.Tool.getFieldValue(" + obj_str + ",\"" + f_str + "\")";
								src = insertDeclaredPartToSrc(src, t_cname, t_var, i_src, declared_list);
							}
						}
					}
					//キャストの前にメソッドとフィールドに関する処理を終える。
					src = analyzeMethod(src, num, var_map, cmap, declared_list);
				}
			}
		}

			//【重要】以上で全ての変数、メソッドはシンボルに置き換わっている。
		//Castとは言っても、最後が；なので、何かにキャストして代入するパターンである。
		//cast部分をvar_mapとクラスマップに登録する。
//		src = src.replaceAll("\\((var[0-9]+)\\)", "$1");//castの範囲を括弧で括っていて、シンボル化済みなら、括弧をとる。

		t_m = _cast1Pat.matcher(src);//括弧のくくりのパターン１
		if (t_m.find()) {
			String w_str;
			String c_name = t_m.group(1);

			w_str = "(" + c_name+ ")" + t_m.group(2);

			String t_var = "var" + Integer.toString(num);
			num ++;

			//castしていたのは配列かどうかを見極め、クラスをt_cに代入する。
			Class<?> t_c = null;
			if (c_name.endsWith("[]")) {
				Class<?> comp_c = Tool.forName(c_name.replaceFirst(Pattern.quote("[]"), ""));
				t_c = Array.newInstance(comp_c, 0).getClass();
			} else {
				t_c = Tool.forName(c_name);
			}
			src = src.replaceFirst(Pattern.quote(w_str), t_var);

			var_map.put(t_var, w_str);
			if (!cmap.containsKey(t_var)) {
				cmap.put(t_var, t_c);
				src = insertDeclaredPartToSrc(src, c_name, t_var, w_str, declared_list);
			}
		}

		t_m = _cast2Pat.matcher(src);//括弧のくくりの別パターン。
		if (t_m.find()) {
			String w_str = t_m.group();
			String c_name = t_m.group(1);

			//キャストがかかる範囲
			w_str = Tool.getClosedBlock(w_str, 0, "(", "");

			String t_var = "var" + Integer.toString(num);
			num ++;

			//castしていたのは配列かどうかを見極め、クラスをt_cに代入する。
			Class<?> t_c = null;
			if (c_name.endsWith("[]")) {
				Class<?> comp_c = Tool.forName(c_name.replaceFirst(Pattern.quote("[]"), ""));
				t_c = Array.newInstance(comp_c, 0).getClass();
			} else {
				t_c = Tool.forName(c_name);
			}

			src = src.replaceFirst(Pattern.quote(w_str), t_var);
			var_map.put(t_var, w_str);

			if (!cmap.containsKey(t_var)) {
				cmap.put(t_var, t_c);
				src = insertDeclaredPartToSrc(src, c_name, t_var, w_str, declared_list);
			}
		}

		if (!src.equals(t_src)) {
			t_src = analyzeMethod(src, num, var_map, cmap, declared_list);
		}

		return t_src;
	}

	//フィールドアクセス可能なソースに改変する過程で、追加した変数についての宣言を挿入する。
	//varは追加した変数名。
	private static String insertDeclaredPartToSrc(String src, String cname, String var, String val, ArrayList<String> declared_list) {
		Pattern _forInsert = Pattern.compile("([\\s\\S]*[\\{\\};]\\n*?)([^;\\}\\{]*?" + var + "[^0-9])");//varはmethod()/if()の中にもある。
		Matcher t_m = _forInsert.matcher(src);
		if (t_m.find()) {
			String i_src = cname + " " + var + "=" + val;
			String num = Integer.toString(declared_list.size());
			src = t_m.replaceFirst("$1__DECLARED__" + num + "__;\n$2");
			declared_list.add(i_src);
		}
		return src;
	}


	//引数がシンボルを含むかどうか判定する。
	private static Pattern p_symbol = Pattern.compile("var[0-9]+");

	private static String restoreSymbol(String vname, HashMap<String, String> var_map) {
		Matcher t_m = p_symbol.matcher(vname);
		if (t_m.find()) {
			String w_str = t_m.group();
			String l_name = var_map.get(w_str);
			vname = vname.replaceFirst(w_str, Matcher.quoteReplacement(l_name));
			//まだ完全に回復していない場合に備える。
			return restoreSymbol(vname, var_map);
		}

		return vname;//回復完了
	}


	//返却値のチェック。怪しい部分が見つかれば、それを返す。
	// rtype：返却すべき型名(配列の場合、Class#getNameで取ってはNG)、src：チェックするソース、map：引数やメンバ変数の名前とクラスのマップ
	// 基本的に、「return」を見つけて、その後の文字列情報からクラスを割り出す。
	public static String checkReturnType(String rtype, String src, HashMap<String, Class<?>> map) {
		if ("void".equals(rtype)) {
			//void型ならば調べる必要はない。
			return null;
		}

		HashMap<String, Class<?>> t_cmap = null;
		try {
			t_cmap = CodeProcessor.analyzeVarToClass(new StringBuilder(src), map);
		} catch (ClassNotFoundException | SecurityException | NoSuchFieldException | NoSuchMethodException
				| NotFoundException | InstantiationException | IllegalArgumentException | IllegalAccessException |
				InvocationTargetException | ParseException | NotSupportedClassException e) {
			Tool.logIfDebug(e, "@CodeProcessot#checkReturnType");
			return null;
		}

		//比べるのはオブジェクト型なので変更する。
		String rtype2 = Tool.primitiveToFQN(rtype);

		boolean t_found = false;
		Pattern t_pat = Pattern.compile("[\\s\\}\\{;]?return\\s+([@\\w\\$#\\.,\\+\\-\\*/\"\\(\\)\\s\\[\\]]+);");
		Matcher t_m = t_pat.matcher(src);
		while (t_m.find()) {
			t_found = true;

			String w_str = t_m.group(1);
			Class<?> t_c = t_cmap.get(w_str.replaceAll("\\s", ""));
			if (t_c != null) {
				//returnの後が一つの塊の場合。
				//オブジェクトがマップにある場合。
				String tc_name = t_c.getName();
				if (!rtype2.equals(tc_name)) {
						return w_str + " : WRONG RETURN TYPE.EXPECTED" + tc_name;//クラスが合致しない箇所を返す。
				}
			} else if ("null".equals(w_str)) {
				//OK
			} else if ((w_str.startsWith("\"") || w_str.endsWith(".toString()")) && rtype.equals("java.lang.String")) {
				//OK。「"」で始まるか、toString()で終わる場合、文字列である。
			} else {
				//returnの後が、ハードコーディング値もしくはオブジェクトを組み合わせた演算である場合。
				String[] temp = w_str.split("[\\-\\s\\*\\+/]");

				if (temp.length == 1) {
					//return の後は一塊の文字列である。以下、ハードコーディンディングかチェックする。

					if (w_str.matches("^[0-9]+$|^-[0-9]+$")) {
						if (!rtype2.equals("java.lang.Integer")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (w_str.matches("^[0-9]+\\.[0-9]+$|^-[0-9]+\\.[0-9]+$")) {
						if (!rtype2.equals("java.lang.Float")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (w_str.matches("^true$|^false$")) {
						if (!rtype2.equals("java.lang.Boolean")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (w_str.matches("^\"[^\"]+\"$")) {
						if (!rtype2.equals("java.lang.String")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else {
						//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
						return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
					}
				}
				//returnの後が複数のオブジェクトもしくはハードコーディング値の演算の場合、先頭を調べる。先頭に括弧があれば外す。
				//※※※※先頭でcastしている場合は、考慮していない。メッセージを見て判断すべし。
				String oname = temp[0];
				Class<?> _c = t_cmap.get(oname);
				if (_c != null) {
					//オブジェクトがマップにある場合。
					if (!rtype2.equals(_c.getName()) && !rtype.equals(_c.getName())) {
						if (rtype2.endsWith("[]") && _c.isArray()) {
							Class<?> a_c = _c.getComponentType();
							if (!rtype2.startsWith(a_c.getName())) {
								return w_str + " : WRONG RETURN TYPE.";//クラスが合致しない箇所を返す。
							}
						} else {
							return w_str + " : WRONG RETURN TYPE.";//クラスが合致しない箇所を返す。
						}
					}
				} else {
					if (oname.matches("^[0-9]+$|^-[0-9]+$")) {
						if (!rtype2.equals("java.lang.Integer") || !rtype2.equals("java.lang.Long") || !rtype2.equals("java.math.BigInteger")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (oname.matches("^[0-9]+\\.[0-9]+$|^-[0-9]+\\.[0-9]+$")) {
						if (!rtype2.equals("java.lang.Float") || !rtype2.equals("java.lang.Double") || !rtype2.equals("java.math.BigDecimal")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (oname.matches("^true$|^false$")) {
						if (!rtype2.equals("java.lang.Boolean")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else if (oname.matches("^\"[^\"]+\"$")) {
						if (!rtype2.equals("java.lang.String")) {
							//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
							return w_str + " : WRONG RETURN TYPE.(not " + rtype + ")";//クラスが合致しない箇所を返す。
						}
					} else {
						//該当するクラスがない。記法の間違いと推定し、該当箇所を返す。
						return w_str + " : MAYBE WRONG RETURN TYPE.(not " + rtype + "?)";//クラスが合致しない箇所を返す。
					}
				}
			}
		}
		if (!t_found) {
			//returnが見つからない。かつ返却型あり。voidだったら、すでに返している。
			return "RETURN STATEMENT not found.";
		}
		return null;
	}
}