package com.ftinc.si.assist.run.plugins;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
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.run.Messages;
import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.FakeMethodRecord;
import com.ftinc.si.assist.test.Fson;
import com.ftinc.si.assist.test.Fson.NotSupportedClassException;
import com.ftinc.si.assist.test.ObjectRecord;
import com.ftinc.si.assist.test.Record;
import com.ftinc.si.assist.test.TestCaseRecord;
import com.ftinc.si.assist.test.TestCommandRecord;
import com.ftinc.si.assist.test.Tool;
import com.ftinc.si.assist.test.gui.ClassReadDialog;

import javassist.CannotCompileException;
import javassist.NotFoundException;

public class ObjectTestCreator extends TestCaseCreator {
	@Override
	public int command(HashMap<String, Object> map) {
		String cmd = map.get("verb").toString(); //$NON-NLS-1$
		switch (cmd) {
			case "#outline": return getClassOutline(map); //$NON-NLS-1$
			case "#xmlmap": return getXMLStructure(map); //$NON-NLS-1$
			default:return super.command(map);
		}
	}


	//-class:className
	private int getClassOutline(HashMap<String, Object> map) {
		String t_result = ""; //$NON-NLS-1$
		String cname = ""; //$NON-NLS-1$
		if (map.containsKey("class") && map.get("class") != null) { //$NON-NLS-1$ //$NON-NLS-2$
			cname = map.get("class").toString(); //$NON-NLS-1$
		}
		String mname = null;

		TestCommandRecord cmd = new TestCommandRecord(2);
		cmd.className = cname;
		ClassReadDialog m_dial = new ClassReadDialog(null, Tool.target_dirs, (TestCommandRecord)cmd); //$NON-NLS-1$
		m_dial.setVisible(true);

		//cancelの時にはclassName=nullになっている。
		if (m_dial.className != null && m_dial.methodName != null) {
			t_result = "<class name=\"" + m_dial.className + "\"/>"; //$NON-NLS-1$ //$NON-NLS-2$

			//m_info：①static ②メソッド名 ③可変引数の位置
			//①③は省略可能
			String[] m_info = m_dial.methodName.split(" ", -1); //$NON-NLS-1$
			boolean isStatic = false;
			Integer v_argpos = -1;
			if (m_info.length == 1) {
				//メソッド名のみ
				mname = m_info[0];
			} else if (m_info.length == 2) {
				if (m_info[0].matches("^.*static")) { //$NON-NLS-1$
					//可変引数ではない
					isStatic = true;
					mname = m_info[1];
				} else if (m_info[0].equals("^final")) { //$NON-NLS-1$
					//可変引数ではない
					mname = m_info[1];
				} else {
					//可変引数
					mname = m_info[0];
				}
			} else if (m_info.length == 3) {
				//staticであり可変引数
				if (m_info[0].matches("^.*static")) { //$NON-NLS-1$
					isStatic = true;
				}
				mname = m_info[1];
				v_argpos = Integer.valueOf(m_info[2]);
			}
			t_result += "<method name=\"" + mname + "\""; //$NON-NLS-1$ //$NON-NLS-2$
			if (isStatic) {
				t_result += " static=true"; //$NON-NLS-1$
			}
			if (v_argpos >= 0) {
				//可変引数の位置
				t_result += " vargpos=" + v_argpos.toString(); //$NON-NLS-1$
			}
			t_result += "/>"; //$NON-NLS-1$
			int a_size = m_dial.objs.length;

			//initArgumentsのために情報を格納する。
			for (int i = 0; a_size > i; i++) {
				String obj_rep = m_dial.objs[i];   // オブジェクト表現はDialogから取る。$n=ClassName
				if (obj_rep.startsWith("$_")) { //$NON-NLS-1$
					//返却値の型を設定
					t_result += "<$_ type=\"" + Tool.replaceB(obj_rep, "$_=", "") + "\"/>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				} else if (obj_rep.startsWith("$0")) { //$NON-NLS-1$
					//$0がある場合に記入。但し、staticやコンストラクタの場合、t_dial.objs[]に$0はない。
					t_result += "<$0 type=\"" + m_dial.className + "\"/>"; //$NON-NLS-1$ //$NON-NLS-2$
				} else {
					//$[1-9]+の場合
					String num = obj_rep.replaceAll("^\\$([0-9]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
					Integer n = Integer.valueOf(num);
					t_result += "<$" + n.toString() + " type=\"" + obj_rep.replaceAll("^\\$[0-9]+=(.*)$", "$1") + "\"/>"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
				}
			}
		}
		//引き渡されるファイル(オブジェクトを生成するソース)からオブジェクトを生成し、xmlにしてファイルに保存する。
		File t_f = Tool._libfile("$LIBDIR\\_object.req"); //$NON-NLS-1$
		if (!t_f.exists()) {
			Tool.logForTesting(null, Messages.getString("ObjectTestCreator.26")); //$NON-NLS-1$
			return -1;
		}


		//保存先はjarのlibの中。
		File t_resfile = Tool._libfile("$LIBDIR\\_object.res"); //$NON-NLS-1$
		if (t_result != null && t_result.length() > 0) {
			renameReqfile(t_f, "req", "doing");//処理が始まったことを知らせる。
			try {
				if (!t_resfile.exists()) {
					t_resfile.createNewFile();
				}
			} catch (IOException e1) {
				Tool.alertMSG(null, e1.getMessage());
				return -1;
			}
			FileWriter t_w;
			try {
				t_w = new FileWriter(t_resfile);
				t_w.write(t_result);
				t_w.close();
			} catch (IOException e1) {
				Tool.alertMSG(null, e1.getMessage());
				return -1;
			}
			renameReqfile(t_f, "doing", "done");//処理が終わったことを知らせる。
		} else {
			renameReqfile(t_f, "req", "error");//処理が終わったことを知らせる。
		}
		return 0;
	}

	//-class:className
	private int getXMLStructure(HashMap<String, Object> map) {
		File t_f = Tool._libfile("$LIBDIR\\_object.req"); //$NON-NLS-1$

		Class<?> x_c = null;
		Object t_obj = null;
		try {
			String cname = map.get("class").toString(); //$NON-NLS-1$
			if (Tool.isFake(cname)) {
				//Fakeである。
				t_obj = Tool.getFakeInstance(cname);

				ArrayList<Object> t_ans = Fson.valueByJsonPath(t_obj, "$"); //$NON-NLS-1$
				if (t_ans != null && t_ans.size() > 0) {
					@SuppressWarnings({"unchecked" })
					HashMap<String, Object> v_map = (HashMap<String, Object>)t_ans.get(0);
					HashMap<String, Object> v2_map = new HashMap<String, Object>();
					for (Entry<String, Object> entry : v_map.entrySet()) {
						v2_map.put(entry.getKey(), entry.getValue());
					}

					HashMap<String, String> f_map = FakeMethodRecord.getVFields(cname);
					for (Entry<String, String> entry : f_map.entrySet()) {
						if (!v2_map.containsKey(entry.getKey())) {
							//getTInstanceでできていない属性を再現しようとする。
							if (Tool.hasDefaultConstructor(entry.getValue(), true)) {
								//デフォルトコンストラクタがあれば作る。
								Class<?> fc = Tool.forName(entry.getValue());
								Object f_obj = Tool.newObject(fc, new Class<?>[]{}, new Object[]{});
								v2_map.put(entry.getKey(), f_obj);
							} else {
								v2_map.put(entry.getKey(), null);
							}
						}
					}

					//fakeの場合、v2_mapをJsonにする。
					t_obj = v2_map;
					x_c = HashMap.class;
				}
			} else {
				x_c = Tool.forName(cname);//creatorクラス名 //$NON-NLS-1$

				if (!cname.endsWith("[]") && Tool.isPrimitive2(x_c)) { //$NON-NLS-1$
					//基本型や文字列なら構造は持たないので抜ける。
					Tool.logForTesting(null, "ERROR:" + cname + Messages.getString("ObjectTestCreator.34")); //$NON-NLS-1$ //$NON-NLS-2$
					return -1;
				}
				String buf, t_src;
				t_src = ""; //$NON-NLS-1$

				//引き渡されるファイル(オブジェクトを生成するソース)からオブジェクトを生成し、xmlにしてファイルに保存する。
				if (t_f.exists()) {
					try {
						BufferedReader t_br = new BufferedReader(new InputStreamReader(new FileInputStream(t_f),Tool.charset));
						while((buf = t_br.readLine()) != null){
							t_src += buf + "\n"; //$NON-NLS-1$
						}
						t_br.close();
					} catch (IOException e1) {
						Tool.logForTesting(null, e1.getMessage());
						return -1;
					}
				} else if (Tool.tryDefaultConstructor(x_c) == null && t_src.length() == 0) {
					Tool.logForTesting(null, Messages.getString("ObjectTestCreator.27")); //$NON-NLS-1$
					return -1;
				}
				t_obj = Tool.constructObject(cname, t_src);
			}
			String t_xml = Messages.getString("ObjectTestCreator.35"); //$NON-NLS-1$
			if (t_obj != null) {
				Integer _depth = 5;
				if (map.containsKey("depth")) { //$NON-NLS-1$
					_depth = Integer.valueOf(map.get("depth").toString()); //$NON-NLS-1$
				}

				t_xml = Fson.toXML(t_obj, x_c, _depth);
			}

			//保存先はjarのlibの中。
			File t_result = Tool._libfile("$LIBDIR\\_object.res"); //$NON-NLS-1$
			if (t_xml != null) {
				renameReqfile(t_f, "req", "doing");//処理が始まったことを知らせる。
				try {
					if (!t_result.exists()) {
						t_result.createNewFile();
					}
				} catch (IOException e1) {
					Tool.alertMSG(null, e1.getMessage());
					return -1;
				}
				FileWriter t_w;
				try {
					t_w = new FileWriter(t_result);
					t_w.write(t_xml);
					t_w.close();
				} catch (IOException e1) {
					Tool.alertMSG(null, e1.getMessage());
					return -1;
				}
				renameReqfile(t_f, "doing", "done");//処理が終わったことを知らせる。
			} else {
				renameReqfile(t_f, "req", "error");//処理が終わったことを知らせる。
			}
			return 0;
		} catch (ClassNotFoundException | SecurityException | IllegalArgumentException | InstantiationException | IllegalAccessException
				| NoSuchMethodException | InvocationTargetException | CannotCompileException | NotFoundException | NoSuchFieldException
				| NotSupportedClassException e) {
			//登録時にエラーが起きたら、できるだけ手がかりを残す。
			String e_msg = "msg=" + e.getMessage() + Tool.getStackMessage(e, 0, 10); //$NON-NLS-1$ //$NON-NLS-2$
			Tool.alertMSG(null, e_msg);
		}
		renameReqfile(t_f, "req", "error");//処理が終わったことを知らせる。
		return -1;
	}


	//Excelから生成したファイルを元に、新たなテストケースを作成する。
	//①Snapshot再生型
	//<testcase name class method arg_types>json</testcase>
	//      $[0-9] \t {} ,\n
	//<cmd>
	// $[0-9_]//? path:json ,\n
	// //?dbset ... ,\n
	// //?exec before ... ,\n
	// //objtest? path:json ,\n
	// //?dbtest ... ,\n
	// //?exec after ... ,\n
	//</cmd> ,\t
	//②CodeFairy型
	//<testcase name class method="メソッド名?" arg_types>source</testcase>
	@Override
	protected int createTestRecords(String s) {
		TestCaseRecord n_case = null;
		String class_name = ""; //$NON-NLS-1$
		String method_name = ""; //$NON-NLS-1$
		String arg_types = ""; //$NON-NLS-1$
		String t_json = ""; //$NON-NLS-1$
		String rtype = ""; //$NON-NLS-1$

		final Pattern pat_case = Pattern.compile("<testcase\\s+name=\"([^\"]+)\"\\s+class=\"([^\"]+)\"\\s+method=\"([^\"]+)\"\\s+arg_types=\"([^\"]*)\"\\s+rtype=\"([^\"]+)\"\\s+include=\"([^\"]*)\">([\\s\\S]*?)</testcase>"); //$NON-NLS-1$
		Matcher t_m = pat_case.matcher(s);
		if (t_m.find()) {
			String case_name = t_m.group(1);

			//<testcase>...</testcase>の後の<cmd>json</cmd>を取得し、TestCommandRecordの数を確定させる。
			ArrayList<String> str_cmds = new ArrayList<String>();
			final Pattern pat_cmd = Pattern.compile("<cmd>([\\s\\S]+?)</cmd>"); //$NON-NLS-1$
			Matcher t_m2 = pat_cmd.matcher(s);
			while (t_m2.find()) {
				str_cmds.add(t_m2.group(1));
			}

			HashMap<Integer, TestCaseRecord> t_casemap = lookupTestCaseWithLength(case_name, str_cmds.size());
			n_case = t_casemap.get(0);
			if (n_case == null) {
				n_case = t_casemap.get(1);//既存に収まらなければ新規作成。
			}
			n_case.changed();
			n_case.include = t_m.group(6);

			class_name = t_m.group(2);
			method_name = t_m.group(3);
			arg_types = t_m.group(4);
			rtype = t_m.group(5);
			t_json = t_m.group(7);//共通の初期化

			//入れ物を作る。
			HashMap<Integer, TestCommandRecord> t_cmds = new HashMap<Integer, TestCommandRecord>();
			HashMap<String, ObjectRecord> t_objs = new HashMap<String, ObjectRecord>();
			HashMap<Integer, AssertRecord> t_asserts = new HashMap<Integer, AssertRecord>();

			if (method_name.endsWith("?")) { //$NON-NLS-1$
				//メソッド名の最後が?ならCodeFairyによるメソッド書き換えである。
				if (createFairyCase(n_case, class_name, method_name, arg_types, t_json, t_cmds, t_objs) < 0) {
					return -1;
				}
			} else {
				HashMap<String, Object> v_map = new HashMap<String, Object>();
				String[] init_cmds;

				//DBの接続定義は、ここで抽出し、createSubmitCaseで埋め込む。
				final Pattern dbadd_pat = Pattern.compile("<dbadd>([\\s\\S]+?)</dbadd>"); //$NON-NLS-1$
				t_m = dbadd_pat.matcher(s);
				if (t_m.find()) {
					//書式 uid:=\w+,pwd:=\w+,jdbc:=.*?:[tablename…]
					init_cmds = t_m.group(1).split("\n", -1); //$NON-NLS-1$

					//DBの接続定義の埋め込み.コマンド毎に埋め込むが、最初のコマンドの設定が使用される。
					for (int i = 0; i < init_cmds.length; i++) {
						//	 uid:=\w+,pwd:=\w+,jdbc:=.*?:[tablename…]
						//	↓
						//	 dbAdd “[?(@.uid=’ユーザID’)][?(@ .pwd=’パスワード’)] [?(@.jdbc=’接続文字列’)]”: [“テーブル名”...]

						String v_key = "dbadd "; //$NON-NLS-1$
						final Pattern db_pat1 = Pattern.compile("(\\w+):=[\"']?([^,\\[=]+?)[\\\"']?(,|:\\[)"); //$NON-NLS-1$
						t_m = db_pat1.matcher(init_cmds[i]);
						while (t_m.find()) {
							String t_key = t_m.group(1);
							String t_val = t_m.group(2);

							v_key += "[?(@." + t_key + "='" + t_val + "')]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						}

						final Pattern db_pat2 = Pattern.compile(":(\\[.*)\\s*$"); //$NON-NLS-1$
						t_m = db_pat2.matcher(init_cmds[i]);
						if (t_m.find()) {
							v_map.put(v_key, Tool.getObjectfromJSON(ArrayList.class, t_m.group(1)));
						}
					}
				}

				int t_id = 0;
				//DB接続の準備コマンド
				if (v_map.size() != 0) {
					//入れ物を生成する。
					TestCommandRecord t_cmd = new TestCommandRecord(2);
					t_cmd.id = n_case.begin + t_id;
					t_cmd.className = "com.ftinc.si.assist.run.VCentral"; //$NON-NLS-1$
					t_cmd.enable = true;
					t_cmd.methodName = "static prepare"; //$NON-NLS-1$
					t_cmd.subject = "null"; //$NON-NLS-1$
					t_cmd.setArgTypes("[[java.util.HashMap,$1]]"); //$NON-NLS-1$
					t_cmd.argStatus = "[true,true,true]"; //$NON-NLS-1$
					t_cmd.returnType = "void"; //$NON-NLS-1$
					t_cmd.testCase = case_name;
					t_cmds.put(t_id, t_cmd);

					//$1
					ObjectRecord t_obj = new ObjectRecord(2);
					t_obj.className = "java.util.HashMap"; //$NON-NLS-1$
					t_obj.id = Integer.toString(t_cmd.id) + "$1"; //$NON-NLS-1$
					t_obj.stack = "$1"; //$NON-NLS-1$
					t_obj.testCase = case_name;
					t_obj.testID = t_cmd.id;
					t_obj.jSON = Tool.getJSONfromObject(v_map);  //DBAddの埋め込み
					t_objs.put(t_obj.id, t_obj);

					//Assert
					AssertRecord t_ast = new AssertRecord(2);
					t_ast.enable = false;
					t_ast.id = t_cmd.id;
					t_asserts.put(t_cmd.id, t_ast);

					t_id++;
				}

				TestCommandRecord snap = new TestCommandRecord(2);
				snap.id = n_case.begin + t_id;
				snap.testCase = n_case.name;
				snap.className = class_name;
				snap.returnType = rtype;
				snap.methodName = method_name;//static method vpos
				snap.subject = "$0";//staticであろうがなかろうが$0は作る。Excelには現れない。 //$NON-NLS-1$

				//スナップショットを予約。
				n_case.snapshot = snap.id;

				String[] types_list = arg_types.split(",", -1); //$NON-NLS-1$
				String t_types = ""; //$NON-NLS-1$
				for (int i = 0; i <  types_list.length; i++) {
					if (i > 0) {
						t_types += ","; //$NON-NLS-1$
					}
					t_types += "[" + types_list[i] + "," + "$" + Integer.toString(i + 1) + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
				}
				snap.setArgTypes("[" + t_types + "]"); //$NON-NLS-1$ //$NON-NLS-2$

				//argTypesができてからでないとStatusをセットできない。
				for (int i = 0; i <  types_list.length; i++) {
					snap.setArgStatus(i + 1, true);
				}
				snap.setArgStatus(0, true);

				//Assertの分
				snap.setArgStatus(types_list.length + 1, true);

				//新しいTestCommandRecordとして登録。
				t_cmds.put(snap.id, snap);


				//そのObjectRecordを登録,t_objsに生成したObjectRecordを格納する。t_jsonはtestcaseタグ内の内部テキスト
				//※実際にTestCaseの内部テキストになるのはメソッド変換の場合なので、下の一行はあまり意味がない。念のため残す。
				makeObjectRecordsOfSnapshot(snap, t_json, t_objs);

				//<testcase>...</testcase>の後の<cmd>json</cmd>を処理する。末端のObjectを作る。各cmd単位でのパラメータの変更。
				for (int i = 0; i < str_cmds.size(); i++) {
					//str_cmdsはcmdタグ内のjson
					makeSnapCommandRecord(snap, i, str_cmds.get(i), t_cmds, t_objs);
				}
			}

			//入れ物にオブジェクトを格納する。
			try {
				ArrayList<Record> removings = null;
				if (t_casemap.containsKey(0)) {//クリアする必要がある。
					//既存テストケースの配下を削除
					Tool._db().clearCase(n_case, true);
				} else if (t_casemap.containsKey(-1)) {//削除する必要がある。
					removings = new ArrayList<Record>();
					removings.add(t_casemap.get(-1));

					Tool._db().clearCase(n_case, true);
				}

				//TestCaseの更新。配下は新規として登録する。
				if (!Tool._db().saveTestCase(n_case, removings, t_cmds, t_objs, t_asserts)) {
					return -1;
				}
//				Tool.alertMSG(null, Messages.getString("ObjectTestCreator.58")); //$NON-NLS-1$
			} catch (SQLException e) {
				Tool.alertMSG(null, "ERROR:failed to rollback. " + e.getMessage());
				return -1;
			}
			return 0;
		}
		return -1;
	}

	private static int createFairyCase(TestCaseRecord n_case, String class_name, String method_name, String arg_types, String src, HashMap<Integer, TestCommandRecord> cmds, HashMap<String, ObjectRecord> objs) {
		method_name = method_name.replace("?", ""); //$NON-NLS-1$ //$NON-NLS-2$
		String[] o_types = (String[])Tool.getObjectfromJSON(String[].class, "[" + arg_types + "]"); //$NON-NLS-1$ //$NON-NLS-2$

		FakeMethodRecord fr = Tool._db().getMethodSource(class_name, method_name, o_types, Tool.version);
		if (fr == null) {
			//既存にないので登録する。
			fr = new FakeMethodRecord(2);
			fr.className = class_name;
			fr.methodName = method_name;
			fr.argTypes = o_types;
		}
		try {
			fr.source = src;
			fr.changed();
			Tool._db().updateRecordBySQL(fr.getUpdateSQL(), true);
		} catch (SQLException e) {
			Tool.alertMSG(null, Messages.getString("ObjectTestCreator.63") + fr.getUpdateSQL() + "\nmsg=" + e.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$
			return -1;
		}
		n_case.loggable = false; //ASSERTをしない。

		TestCommandRecord fairy = new TestCommandRecord(2);
		fairy.id = n_case.begin;
		fairy.className = "com.ftinc.si.assist.run.CodeFairy"; //$NON-NLS-1$
		fairy.returnType = "void"; //$NON-NLS-1$
		fairy.methodName = "prepare";//static method vpos //$NON-NLS-1$
		fairy.subject = "null"; //$NON-NLS-1$
		fairy.argTypes = new String[][] {{"java.lang.String", "$1"}}; //$NON-NLS-1$ //$NON-NLS-2$
		fairy.testCase = n_case.name;

		ObjectRecord o_rec = new ObjectRecord(2);
		o_rec.id = Integer.toString(fairy.id) + "$1"; //$NON-NLS-1$
		o_rec.testID = fairy.id;
		o_rec.className = "java.lang.String"; //$NON-NLS-1$
		o_rec.testCase = fairy.testCase;
		o_rec.jSON = class_name;
		o_rec.isPOJO = true;
		o_rec.stack = "$1"; //$NON-NLS-1$

		cmds.put(fairy.id, fairy);
		objs.put(o_rec.id, o_rec);
		return 0;
	}

	//SnapshotのObjectRecordを作る。
	// \\??\\$[0-9] \t {} ,\n
	private static void makeObjectRecordsOfSnapshot(TestCommandRecord cmd, String json, HashMap<String, ObjectRecord> objs) {
		String[] jsons = json.split(",\n", -1); //$NON-NLS-1$

		// キー：$n     値:そのJson
		HashMap<String, String> t_map = new HashMap<String, String>();
		final Pattern t_pat1 = Pattern.compile("^(\\??\\$\\d+)\t([\\s\\S]+)\\s*\\Z");  //$[0-9] \t {} //$NON-NLS-1$
		Matcher t_m;
		for (int i = 0; i < jsons.length; i++) {
			t_m = t_pat1.matcher(jsons[i]);
			if (t_m.find()) {
				String t_key = t_m.group(1);
				t_map.put(t_key, t_m.group(2));
			}
		}

		String[][] argTypes = cmd.argTypes;//[[ClassName,[0-9]+$[0-9_]],[ClassName,$[0-9]+,...]
		HashMap<String, String> arg_infos = new HashMap<String, String>();
		for (int i = 0; i < argTypes.length; i++) {
			String[] info = argTypes[i];
			arg_infos.put(info[1],  info[0]);
		}

		for (Entry<String, String> entry : t_map.entrySet()) {
			String t_stack = entry.getKey();
			String t_json = entry.getValue();

			String t_id = Integer.toString(cmd.id) + t_stack;
			boolean isPOJO = !t_stack.startsWith("?"); //$NON-NLS-1$
			t_stack = t_stack.replace("?", ""); //$NON-NLS-1$ //$NON-NLS-2$

			ObjectRecord o_rec = objs.get(t_id);
			if (o_rec == null) {
				o_rec = new ObjectRecord(2);
				o_rec.id = Integer.toString(cmd.id) + t_stack;
				o_rec.testID = cmd.id;
				o_rec.testCase = cmd.testCase;
				o_rec.jSON = ""; //$NON-NLS-1$
				o_rec.isPOJO = isPOJO;
				o_rec.stack = t_stack;
				o_rec.className = arg_infos.get(t_stack);
				if (o_rec.className == null || o_rec.className.length() == 0) {
					if (t_stack.equals("$0")) {
						o_rec.className = cmd.className;
					}
				}
			}
			if (o_rec.jSON.length() > 0) {
				o_rec.jSON += "\n" + t_json;
			} else {
				o_rec.jSON = t_json;

			}
			objs.put(o_rec.id, o_rec);
		}
	}

	//Snapshotを再利用するコマンドを作って登録する。
	private static void makeSnapCommandRecord(TestCommandRecord snap,int m, String json, HashMap<Integer, TestCommandRecord> cmds, HashMap<String, ObjectRecord> objs) {
		int n = snap.id + m + 1;

		//TestCommandを作る。
		TestCommandRecord t_cmd = snap._dup(n);//IDを指定してコピーを作る。
		t_cmd.setClassName("com.ftinc.si.assist.run.SnapshotCommandRecord"); //$NON-NLS-1$
		t_cmd.id = n;

		//static=false, varpos=0
		t_cmd.setMethod("$replay(" + Integer.toString(snap.id) + ")", false, 0); //$NON-NLS-1$ //$NON-NLS-2$
		t_cmd.setReturnType("void"); //$NON-NLS-1$
		cmds.put(n, t_cmd);

		//ObjectRecordを作る。
		String[] jsons = json.split(",\n", -1); //$NON-NLS-1$
		for (int i = 0; i < jsons.length; i++) {
			if (jsons[i] != null && jsons[i].trim().length() > 0) {
				makeObjectRecords(snap.className, t_cmd, jsons[i], objs);
			}
		}
	}

	// \\$[0-9_]//? path:json ,\n
	// //\\?dbset ... ,\n
	// //\\?exec before ... ,\n
	// //\\?objtest path:json ,\n
	// //\\?dbtest ... ,\n
	// //\\?exec after ... ,\n
	private static void makeObjectRecords(String cname, TestCommandRecord cmd, String json, HashMap<String, ObjectRecord> objs) {
		String[] jsons = json.split(",\n", -1); //$NON-NLS-1$

		// キー：$n     値:そのJson
		HashMap<String, String> t_map = new HashMap<String, String>();

		//※ $の前に?があるのはfakeのパターンである。
		final Pattern t_pat0 = Pattern.compile("^(\\??\\$\\d+)\\?([\\s\\S]+?)\\s*\\Z");
		final Pattern t_pat1 = Pattern.compile("^(\\??\\$\\d+)(//\\?[\\s\\S]+?)\\s*\\Z");  // _$[0-9]//?... //$NON-NLS-1$
		final Pattern t_pat2 = Pattern.compile("^(//\\?[\\s\\S]+?)\\s*\\Z");  // その他のコマンド //$NON-NLS-1$
		Matcher t_m;
		String t_str0;
		for (int i = 0; i < jsons.length; i++) {
			t_m = t_pat0.matcher(jsons[i]);
			if (t_m.find()) {
				String t_key = t_m.group(1);
				t_str0 = t_m.group(2);
				if (t_map.containsKey(t_key)) {
					t_str0 = t_map.get(t_key) + "\n" + t_str0;
				}
				t_map.put(t_key, t_m.group(2));
			} else {
				t_m = t_pat1.matcher(jsons[i]);
				if (t_m.find()) {
					String t_key = t_m.group(1);
					t_str0 = t_m.group(2);
					if (t_map.containsKey(t_key)) {
						t_str0 = t_map.get(t_key) + "\n" + t_str0;
					}
					t_map.put(t_key, t_m.group(2));
				} else {
					//DBやコマンド
					t_m = t_pat2.matcher(jsons[i]);
					if (t_m.find()) {
						t_str0 = t_map.get("$0");//$0はFakeではないはずである。Fakeを試験しても意味がない。 //$NON-NLS-1$
						if (t_str0 == null) {
							t_str0 = t_m.group(1);
						} else {
							t_str0 += "\n" + t_m.group(1); //$NON-NLS-1$
						}
						t_map.put("$0", t_str0); //$NON-NLS-1$
					}
				}
			}
		}

		String[][] argTypes = cmd.argTypes;//[[ClassName,[0-9]+$[0-9_]],[ClassName,$[0-9]+,...]
		HashMap<String, String> arg_infos = new HashMap<String, String>();
		for (int i = 0; i < argTypes.length; i++) {
			String[] info = argTypes[i];
			arg_infos.put(info[1], info[0]);
		}

		for (Entry<String, String> entry : t_map.entrySet()) {
			String t_stack = entry.getKey();
			t_stack = t_stack.replace("?", ""); //$NON-NLS-1$ //$NON-NLS-2$

			String t_json = entry.getValue();
			String t_id = Integer.toString(cmd.id) + t_stack;

			ObjectRecord o_rec = objs.get(t_id);
			if (o_rec == null) {
				boolean isPOJO = !t_stack.startsWith("?"); //$NON-NLS-1$

				o_rec = new ObjectRecord(2);
				o_rec.id = t_id;
				o_rec.jSON = ""; //$NON-NLS-1$
				o_rec.testID = cmd.id;
				o_rec.testCase = cmd.testCase;
				o_rec.isPOJO = isPOJO;
				o_rec.stack = t_stack;
				o_rec.className = arg_infos.get(t_stack);
				if (o_rec.className == null || o_rec.className.length() == 0) {
					if (t_stack.equals("$0")) {
						o_rec.className = cname;
					}
				}
			}
			if (o_rec.jSON.length() > 0) {
				o_rec.jSON += "\n" + t_json;
			} else {
				o_rec.jSON = t_json;

			}
			objs.put(o_rec.id, o_rec);
		}
	}

}
