package com.ftinc.si.assist.test;

import java.awt.Component;
import java.awt.Font;
import java.awt.Window;
import java.awt.event.KeyEvent;
import java.awt.event.MouseWheelEvent;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.sql.SQLException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import com.ftinc.si.assist.run.FakeEditorOnRun;
import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.run.TestCommander;
import com.ftinc.si.assist.run.TestMainGate;
import com.ftinc.si.assist.run.TestSubMain;
import com.ftinc.si.assist.run.VCentral;
import com.ftinc.si.assist.test.Fson.NotSupportedClassException;
import com.ftinc.si.assist.test.gui.AssertStatusWindow;
import com.ftinc.si.assist.test.gui.EnvironmentData;
import com.ftinc.si.assist.test.gui.EnvironmentDialog;
import com.ftinc.si.assist.test.gui.Plugin;
import com.ftinc.si.assist.test.gui.plugins.CodeFairyPlugin;
import com.ftinc.si.assist.test.gui.plugins.DefaultPlugin;
import com.ftinc.si.assist.test.gui.plugins.DictionaryPlugin;
import com.ftinc.si.assist.test.gui.plugins.SnapshotPlugin;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtField;
import javassist.CtMethod;
import javassist.CtNewConstructor;
import javassist.CtNewMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.annotation.Annotation;
import javassist.bytecode.annotation.StringMemberValue;


public class Tool {
	public static HashMap<String, Object> instance_table; // 以前のコマンドで生成したインスタンスを格納。主語、引数ともにクラスに一つだけ格納可能。
	  //testID -> Object(Real or Fake): そのIDの返却値のオブジェクトを登録する。

	public static String fake_dir; //Fakeの保存用

	private static volatile ClassPool c_pool;
	private static volatile DBService s_db = null;
	private static boolean editmode = true;//初期化前にAlertMSGを呼びたい場合があるため。
	public static boolean debug_mode = false;

	public static String curCase;//現在のTestCase
	public static int curID;//現在のTestID。publicにしたのはVCentralから使うケースがあるため。

	private static String connect_str = null;
	private static String password;
	private static String user;
	public static String charset;

	public static String group_code = ""; //$NON-NLS-1$
	public static String version = "";	//起動時に指定できるとよい。★★★★★★★★ //$NON-NLS-1$

	private static AssertStatusWindow s_statuswin = null; //テスト結果を表示する画面。

	public static boolean process_mode = false; //テストコマンド実行を別プロセスで行うかどうかのフラグ
	public static boolean child_mode = false; //trueは子プロセス内。
	private static volatile PrintStream log_stream = null;
	public static String logFolder = null;
	private static String logStack = null;
	private static PrintStream s_out = null;

	public static ArrayList<String> target_dirs = null;  //読み込むクラスのルートディレクトリのリスト。
	public static volatile ArrayList<Plugin> plugins = null;  //プラグインインスタンスのリスト。

	private static EnvironmentData cur_env = null;
	private static HashMap<String, String> aliasNames;

	//初期化
	public static void init(boolean b) {
		instance_table = new HashMap<String, Object>();
		editmode = b;

		try {
			//コード系をUTF-8に合せる。
			s_out = new PrintStream(System.out, true, "UTF-8"); //$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			s_out = new PrintStream(System.out);
		}

		//jarfileを特定する。
		String str_resource = Tool.class.getPackage().getName().replace('.', '/');
		URL t_url = Tool.class.getClassLoader().getResource(str_resource);
		String str_path = ""; //$NON-NLS-1$
		try {
			//日本語が入ると文字化けするのでデコードする。
			str_path = URLDecoder.decode(t_url.getPath(),Messages.getString("Tool.51")); //$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}

		final Pattern t_pat = Pattern.compile("file:/+(.*?)/([^/]+)\\.jar!/.*$"); //$NON-NLS-1$
		Matcher t_m = t_pat.matcher(str_path);
		if (t_m.find()) {
			//jarから実行した場合。
			s_jarname = t_m.group(2);
			s_userdir = t_m.group(1).replace('/', '\\');
			s_userdir = replaceB(s_userdir, "%20", " ");//urlの中の空白のためのエスケープ文字を変換する。 //$NON-NLS-1$ //$NON-NLS-2$
		} else {
			//実行ファイルjarを外だしした。
			s_jarname = ""; //$NON-NLS-1$
			s_userdir =System.getProperty("user.dir"); //$NON-NLS-1$
			File jfolder = new File(s_userdir); //カレントフォルダでjarを探す。
			File[] f_list = jfolder.listFiles(
					new FilenameFilter() {
						public boolean accept(File dir, String name) {
							final Pattern t_pat = Pattern.compile("^(" + Pattern.quote(s_jarname) + ".*)\\.jar");  //$NON-NLS-1$ //$NON-NLS-2$
							Matcher t_m = t_pat.matcher(name);
							if (t_m.find()) {
								return true;
							}
							return false;
						}});
			if (f_list != null && f_list.length > 0) {
				s_jarname = replaceB(f_list[0].getName(), ".jar", ""); //$NON-NLS-1$ //$NON-NLS-2$
			} else {
				//見つからない。但し、「デバッグモードでしかこのルートはない」のであまり関係ない。
			}
		}
	}

	//library群を格納しているフォルダを返す。
	private static String _libdir() {
		if (s_jarname.length() > 0) {
			return s_userdir + "\\" + s_jarname + "_lib"; //$NON-NLS-1$ //$NON-NLS-2$
		}
		//デバッグ用の設定フォルダ
		return s_userdir + "\\_LIB"; //$NON-NLS-1$ //$NON-NLS-2$
	}

	//実行モードかどうか。全てのクラスやJSONができていない場合、Editモードで動かして、都度、設定していく。
	public static boolean editMode() {
		return editmode;
	}

	public static void setEditMode(boolean b) {
		editmode = b;
	}

	//現在のテストケース名
	public static void setCurID(String caseName, int id) {
		curCase = caseName;
		curID = id;
	}

	//TestCaseが終了したら、関連情報をクリアする。
	public static void clearCase() {
		if (instance_table != null) {
			instance_table.clear();
		}
		VCentral.clear();
	}

	//後始末処理。
	public static void destroy() {
		logIfDebug(null, "<ftmsg msg=\"deconstructing\"/>"); //$NON-NLS-1$

		//念のため。
		clearCase();

		//普通の掃除
		if (s_statuswin != null) {
			//これが呼ばれるのは、子プロセスでテスト実行している場合である。
			//editmodeの時は、次から次へと実行したい。
			s_statuswin.setVisible(false);
			s_statuswin = null;
		}
		if (log_stream != null) {
			log_stream.append("</xml>");//ログのタグを閉じる。 //$NON-NLS-1$
			log_stream.flush();
			log_stream.close();
			log_stream = null;
		}
		if (s_db != null) {
			s_db.destroy();
			s_db = null;
		}
		if (plugins != null) {
			//pluginのお掃除。必要なら実装されているはず。
			for (int i = 0; i < plugins.size(); i++) {
				plugins.get(i).destroy();
			}

			plugins.clear();
			plugins = null;
		}
	}

	private static TestCommander cur_commander;

	//stopping用
	public static void curCommander(TestCommander tester) {
		cur_commander = tester;
	}

	//DBServiceへのアクセスメソッド
	public static DBService _db() {
		return s_db;
	}

	//日付のフォーマット
	static public final String DATE_PATTERN ="yyyy-MM-dd"; //$NON-NLS-1$


	//ファイルのパスを受け取り、Fileを返す。$LIBDIRを解釈する。
	public static File _libfile(String fpath) {
		if (fpath == null) {
			return null;
		}

		File t_file = null;
		if (fpath.startsWith("$LIBDIR")) { //$NON-NLS-1$
			String t_path = replaceB(fpath, "$LIBDIR", _libdir()); //$NON-NLS-1$
			String t_path0 = t_path;//展開パス

			//単純ファイル名
			final String fname = t_path.replaceFirst("^(.*)\\\\([^\\\\]+)$", "$2"); //$NON-NLS-1$ //$NON-NLS-2$
			String dirname = t_path.replaceFirst("^(.*)\\\\([^\\\\]+)$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
			File t_dir = new File(dirname);

			File[] t_files = t_dir.listFiles(new FilenameFilter() {
				@Override
				public boolean accept(File dir, String name) {
					String t_reg = fname;
					//ワイルドカードを正規表現に変える。
					t_reg = replaceB(t_reg, ".", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
					t_reg = replaceB(t_reg, "*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
					if(name.matches("^" + t_reg + "$")){ //$NON-NLS-1$ //$NON-NLS-2$
						return true;
					}
					return false;
				}
			});
			if (t_files == null || t_files.length == 0) {
				//$LIBDIRにないなら、親フォルダの_LIBの中を見る。
				t_path = replaceB(fpath, "$LIBDIR", s_userdir + "\\_LIB"); //$NON-NLS-1$ //$NON-NLS-2$
				final String fname2 = t_path.replaceFirst("^(.*)\\\\([^\\\\]+)$", "$2"); //$NON-NLS-1$ //$NON-NLS-2$
				dirname = t_path.replaceFirst("^(.*)\\\\([^\\\\]+)$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
				t_dir = new File(dirname);

				t_files = t_dir.listFiles(new FilenameFilter() {
					@Override
					public boolean accept(File dir, String name) {
						String t_reg = fname2;
						//ワイルドカードを正規表現に変える。
						t_reg = replaceB(t_reg, ".", "\\."); //$NON-NLS-1$ //$NON-NLS-2$
						t_reg = replaceB(t_reg, "*", ".*"); //$NON-NLS-1$ //$NON-NLS-2$
						if(name.matches("^" + t_reg + "$")){ //$NON-NLS-1$ //$NON-NLS-2$
							return true;
						}
						return false;
					}
				});
			}
			if (t_files != null && t_files.length > 0) {
				t_file = t_files[0];
			} else {
				logIfDebug(null, fpath + Messages.getString("Tool.77")); //$NON-NLS-1$
			}
			if (t_file == null && t_path0.indexOf("*") < 0) { //$NON-NLS-1$
				//まだ存在していないが、検索ではないので、Fileを返す。
				t_file = new File(t_path0);
			}
		} else {
			t_file = new File(fpath);
		}
		return t_file;
	}


	//Environment情報を読み込む。
	public static EnvironmentData readEnvironment(boolean bConnectDB) {
		if (s_userdir == null) {
			//-envでコマンドが起動する場合、いきなり読み始めるため、ここで初期化する。
			init(false);
		}

		// EnvironmentDataファイルはLibフォルダにある。
		File t_env = _libfile("$LIBDIR\\tclenv*.json"); //$NON-NLS-1$
		if (t_env != null && t_env.exists()) {
			FileReader t_r;
			try {
				t_r = new FileReader(t_env);
				String t_json = ""; //$NON-NLS-1$
				BufferedReader t_buf = new BufferedReader(t_r);
				String t_line;
				while ((t_line = t_buf.readLine()) != null) {
					t_json += t_line;
				}
				t_buf.close();
				t_r.close();
				if (t_json != null && t_json.length() > 0) {
					cur_env = (EnvironmentData)Tool.getObjectfromJSON(EnvironmentData.class, t_json);

				}
			} catch (IOException e) {
				alertMSG(null, e.getMessage());
			}
		} else {
			alertMSG(null, "$LIBDIR\\tclenv*.json not found.>>$LIBDIR=" + _libdir()); //$NON-NLS-1$
			cur_env = new EnvironmentData();
		}
		if (bConnectDB) {
			setEnvironment(cur_env);
		}
		return cur_env;
	}


	//環境情報を反映する。返却値はDB接続を更新したかどうか。更新したらtrue。
	public static synchronized void setEnvironment(EnvironmentData env) {
		cur_env = env;
		if (env == null) {
			return;
		}

		process_mode = env.process_mode;
		group_code = env.groupCode;
		aliasNames = env.aliasNames;
		version = env.version;
		debug_mode = env.debug_mode;

		//debugモードが規定されてからでないと、出力は意味がない。
		logIfDebug(null,  "<ftmsg msg=\"starting setEnvironment@" + new Date() + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$

		if ((env.logdir != null && env.logdir.length() > 0) && logFolder == null) {
			logFolder = convert2Alias(env.logdir); //$NON-NLS-1$
			logFolder = logFolder.replaceFirst(Pattern.quote("\\") + "$", ""); //末尾の\\は不要。 //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		} else {
			logFolder = s_userdir;
		}

		connect_str = env.jdbcString();
		user = env.uID;
		password = env.pwd;
		charset = env.encode;

		target_dirs = env.rootDirs;
		fake_dir = env.fakedir;

		//Fsonのデフォルトパラメータの取得。デフォルトコンストラクタを持たないクラスにも、以後、対応可能。
		Fson.initDefaultParams();

		logIfDebug(null,  "<ftmsg msg=\"setting ClassPool in setEnvironment\"/>"); //$NON-NLS-1$
		//初期化済でないなら実行。
		c_pool = ClassPool.getDefault();

		try {
			if (target_dirs != null) {
				for (int i = 0; i < target_dirs.size(); i++) {
					c_pool.appendClassPath(target_dirs.get(i));
					logIfDebug(null,  "<ftmsg msg=\"c_pool to append ClassPath =" + target_dirs.get(i) + " \"/>"); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
			if (fake_dir != null) {
				c_pool.appendClassPath(fake_dir);
			}
		} catch (NotFoundException e) {
			e.printStackTrace();
		}

		s_prevPath = env.prevPath;

		logIfDebug(null,  "<ftmsg msg=\"setting database connection in setEnvironment\"/>"); //$NON-NLS-1$
		if (s_db == null) {
//			Tool.alertMSG(null, "before create DBService.");

			//初期化が未実行の場合、接続する。
			s_db = new DBService(env.remote_db, false); //false:外部プロセスの異常時自動終了しない。
			if (!s_db.init(connect_str, user, password)) {
				s_db = null;
				return;
			}
		} else {
			//DB接続済みの場合。
			if (cur_env != null && cur_env.shouldReconnectDB(env)) {
				//DB情報が更新された場合、接続し直す。それ以外は、既存の接続を使う。
				if (s_db != null) {
					s_db.destroy();
				}
				s_db = new DBService(env.remote_db, false);
				if (!s_db.init(connect_str, user, password)) {
					s_db = null;
					return;
				}
			}
		}
		TestCaseRecord.s_unitSize = env.caseSize;//TestCaseのデフォルトの大きさ

		logIfDebug(null,  "<ftmsg msg=\"setting plugins in setEnvironment\"/>"); //$NON-NLS-1$
		plugins = new ArrayList<Plugin>();
		try {
			//組み込みプラグインはじかに登録する。
			plugins.add((Plugin) newObject(DefaultPlugin.class, null, null));
			plugins.add((Plugin) newObject(CodeFairyPlugin.class, null, null));
			plugins.add((Plugin) newObject(DictionaryPlugin.class, null, null));
			plugins.add((Plugin) newObject(SnapshotPlugin.class, null, null));
			for (int j = 0; j < env.plugins.size(); j++) {
				Class<?> t_c;
				String c_name = env.plugins.get(j);
				t_c = forName(c_name);
				Plugin t_plugin = (Plugin) newObject(t_c, null, null);
				plugins.add(t_plugin);

				//必要に応じて、システムプロパティなどの初期化を行う。
				if (env.plugins_properties != null && env.plugins_properties.containsKey(c_name)) {
					t_plugin.initProperties(env.plugins_properties.get(c_name));
				}
			}
		} catch (NoSuchMethodException | IllegalArgumentException | SecurityException | InvocationTargetException |
				IllegalAccessException | InstantiationException | ClassNotFoundException e1) {
			logIfDebug(e1, Messages.getString("Tool.3")); //$NON-NLS-1$
			return;
		}
		return;
	}

	public static void saveEnvironment(EnvironmentData env) {
		logIfDebug(null,  "<ftmsg msg=\"saving Environment.\"/>"); //$NON-NLS-1$

		if (env != null) {
			// EnvironmentDataの編集。
			env.prevPath = Tool.getPrevPath();
			String envJSON = Tool.getJSONfromObject(env);

			if (envJSON == null) {
				alertMSG(null,  "<ftmsg msg=\"Failed to save environment settings.\"/>"); //$NON-NLS-1$
				destroy();
				return;
			}

			//保存先はjarのlibの中。
			File t_env = _libfile("$LIBDIR\\tclenv*.json"); //$NON-NLS-1$
			if (envJSON != null) {
				try {
					if (!t_env.exists()) {
						t_env.createNewFile();
					}
				} catch (IOException e1) {
					alertMSG(null, e1.getMessage());
				}
				FileWriter t_w;
				try {
					t_w = new FileWriter(t_env);
					t_w.write(envJSON);
					t_w.close();
				} catch (IOException e1) {
					alertMSG(null, e1.getMessage());

				}
			}
		}
	}

	public static EnvironmentData openEnvironmentDialog(JFrame frame, EnvironmentData env) {
		EnvironmentDialog t_d = new EnvironmentDialog(frame, env); //$NON-NLS-1$
		t_d.setVisible(true);

		//閉じるボタンを押した時だけ保存する。
		if (t_d.is_ok) {
			EnvironmentData t_data = t_d.getEnvData();
			setEnvironment(t_data);
			return t_data;
		}
		return null;
	}

	///////////////////////////////////////////////////////////////////////
	//ここから自前のLoader。

	//実行形式のjarの名前, 実行時のフォルダ
	public static String s_jarname = null;
	public static String s_userdir = null;

	//Classの属しているPackageの版数を返す。
	public static String getPackageVersion(Class<?> c) {
		Package pkg = c.getPackage();
		return pkg.getSpecificationVersion();
	}


	//JSONから基本型、配列、オブジェクトのどれもを作れるようにする。
	public static Class<?> forName(String cname) throws ClassNotFoundException {

		if ("int".equals(cname)) { //$NON-NLS-1$
			return int.class;
		} else if ("long".equals(cname)) { //$NON-NLS-1$
			return long.class;
		} else if ("float".equals(cname)) { //$NON-NLS-1$
			return float.class;
		} else if ("double".equals(cname)) { //$NON-NLS-1$
			return double.class;
		} else if ("boolean".equals(cname)) { //$NON-NLS-1$
			return boolean.class;
		} else if ("byte".equals(cname)) { //$NON-NLS-1$
			return byte.class;
		} else if ("char".equals(cname)) { //$NON-NLS-1$
			return char.class;
		} else if ("int[]".equals(cname)) { //$NON-NLS-1$
			return int[].class;
		} else if ("float[]".equals(cname)) { //$NON-NLS-1$
			return float[].class;
		} else if ("double[]".equals(cname)) { //$NON-NLS-1$
			return double[].class;
		} else if ("boolean[]".equals(cname)) { //$NON-NLS-1$
			return boolean[].class;
		} else if ("byte[]".equals(cname)) { //$NON-NLS-1$
			return byte[].class;
		} else if ("char[]".equals(cname)) { //$NON-NLS-1$
			return char[].class;
		} else if (cname.endsWith("[]")) { //$NON-NLS-1$
			//配列の場合は、クラスは直接作れない。
			cname = cname.replaceFirst(Pattern.quote("[]"), ""); //$NON-NLS-1$ //$NON-NLS-2$
			Class<?> t_comp = forName(cname);
			Object t_array = Array.newInstance(t_comp, 0);
			return t_array.getClass();
		}
		try {
			return Class.forName(cname, true, TestMainGate.s_classloader);
		} catch (Exception | Error e1) {
			throw new ClassNotFoundException(Messages.getString("Tool.5") + cname + " Error=" + e1.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}


	//第二引数はtoJson用：true=属性をJSON化するもしくはスナップショットに使う。第一argは基本型かデフォルトコンストラクタでなければならない。
	//true:コンストラクタの引数が全てデフォルトコンストラクタを持つ時のみOK。false：引数のコンストラクタ引数もチェックする。
	public static boolean hasDefaultConstructor(String c, boolean bAttr) {
		CtClass tc = null;
		try {
			if (c_pool == null) {
				//RemoteDBの場合、初期化していない属性がある。必要になったので処理する。
				c_pool = ClassPool.getDefault();
				Fson.initDefaultParams();
			}
			tc = c_pool.get(c);
		} catch (NotFoundException e) {
			alertMSG(null, Messages.getString("Tool.6") + e.getMessage()); //$NON-NLS-1$
		}
		if (tc != null) {
			return hasDefaultConstructor(tc, bAttr);
		}
		return false;
	}

	//第二引数はtoJson用：true=属性をJSON化しようとするとき、もしくはスナップショット化可能の判断に使う。第一argは基本型かデフォルトコンストラクタを持たねばならない。
	//true:コンストラクタの引数が全てデフォルトコンストラクタを持つ。false：引数に難あり。
	public static boolean hasDefaultConstructor(CtClass c, boolean bAttr) {
		//基本型、文字列、配列はそのまま作れる。
		if (isPrimitive2(c)) {
			return true;
		}
		//DBに引数が登録済み。
		if (Fson.s_defaultParams.get(c.getName()) != null) {
			return true;
		}

		if (hasDefaultConstructor(c)) {
			return true;
		} else {
			//toJson用
			CtConstructor[] clist = c.getConstructors();
			if (!bAttr) {
				for (int i = 0; i < clist.length; i++) {
					try {
						if (clist[i].getParameterTypes().length == 0) {
							return true;
						}
					} catch (NotFoundException e1) {
						//まず、ありえない。
						alertMSG(null, Messages.getString("Tool.7") + e1.getMessage()); //$NON-NLS-1$
					}
				}
			}
		}

		return false;
	}

	//デフォルトコンストラクタがあるかどうか調べる。
	private static boolean hasDefaultConstructor(CtClass c) {
		CtConstructor t_cons = null;
		try {
			t_cons = c.getConstructor("()V"); //$NON-NLS-1$
		} catch (NotFoundException e) {
			return false;
		}
		return (t_cons != null);
	}

	//defaultconstructorの存在有無。FsonのfromJsonから呼ばれる。（forNameを遠慮なく使ってよい段階）
	public static Object tryDefaultConstructor(Class<?> c) {
		try {
			return c.getDeclaredConstructor().newInstance();
		} catch (Exception e) {
			return null;
		}
	}

	//編集中のCtClassからクラスをロードする。
	public static Class<?> toClass(CtClass tc) throws CannotCompileException {
		try {
			return tc.toClass(TestSubMain.class.getClassLoader(), Tool.class.getProtectionDomain());
		} catch (Error e) {
			//Systemのクラスローダで失敗する場合。一縷の望み。根拠なし。
			return tc.toClass();
		}
	}

	//基本的には、クラスからコンストラクタを取得して、インスタンスを作る。
	//やむを得ない場合は、クラスのnewInstanceを呼ぶ。
	public static Object newObject(Class<?> c, Class<?>[] ac, Object[] as) throws SecurityException, NoSuchMethodException, IllegalArgumentException,
			InstantiationException, IllegalAccessException, InvocationTargetException  {

		Constructor<?> t_con = null;

		try {
			t_con = c.getConstructor(ac);
		} catch (NoSuchMethodException e) {
			if (ac == null || ac.length == 0) {
				//引数がない場合は、特殊かもしれない。
				try {
					t_con = c.getConstructor();
				} catch (NoSuchMethodException e1) {
					try {
						return c.getDeclaredConstructor().newInstance();
					} catch (Exception e2) {
						alertMSG(null, Messages.getString("Tool.8") + c.getName()+ "=newInstance() msg ="  + e2.getMessage() + " @Tool#newObject."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						throw new InstantiationException(Messages.getString("Tool.10") + c.getName()); //$NON-NLS-1$
					} catch (Error e2) {
						alertMSG(null, Messages.getString("Tool.11") + c.getName()+ "=newInstance() msg ="  + e2.getMessage() + " @Tool#newObject."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						throw new InstantiationException(Messages.getString("Tool.12") + c.getName()); //$NON-NLS-1$
					}
				}
			} else {
				alertMSG(null, Messages.getString("Tool.13") + c.getName()+ "#getConstrutor(Class{}) msg ="  + e.getMessage() + " @Tool#newObject."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				throw e;
			}
		}

		t_con.setAccessible(true);
		try {
			return t_con.newInstance(as);
		} catch (IllegalArgumentException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
			Throwable ecause = e.getCause();
			String t_msg = Messages.getString("Tool.14") + c.getName() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
			t_msg += ">>Throwable=" + ecause.getClass().getName() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
			t_msg += ">>msg=" + ecause.getMessage(); //$NON-NLS-1$
			alertMSG(null, t_msg + " @Tool#newObject."); //$NON-NLS-1$
			if (ecause instanceof Error) {
				throw (Error) ecause;
			} else if (ecause instanceof RuntimeException) {
				throw (RuntimeException) ecause;
			}
			throw new RuntimeException(ecause);
		} catch (Error e) {
			alertMSG(null, Messages.getString("Tool.15") + c.getName()+ "\n>>msg ="  + e.getMessage() + " @Tool#newObject."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
			throw new InstantiationException(Messages.getString("Tool.16") + c.getName() + " @Tool#newObject."); //$NON-NLS-1$ //$NON-NLS-2$
		}
	}

	////////////ここからテスト実行系
	//TestCommanderやFakeからのインスタンス取得要求に応じ、適当なオブジェクトを構成して返す。
	//testIDは過去のインスタンスも指定可能にするため。stackの標準形式は$[0-9]+であるが例外（クラス名指定）もある。
	//Fakeから呼ぶ時は、自クラスの代理しているクラス名をstackに使う。この場合、Test実行中は共有されることに注意。
	public static Object getTObject(int testID, String stack) throws SecurityException, ClassNotFoundException,
			InstantiationException, IllegalAccessException, NoSuchFieldException, CannotCompileException, IOException, NotFoundException,
			IllegalArgumentException, NoSuchMethodException, InvocationTargetException, ParseException, NotSupportedClassException {
		if (stack == null || stack.equals("null")) return null;  // null引数の場合 //$NON-NLS-1$

		//既にinstanceが作られていた場合再利用する。主語（クラス名）、第一階層の引数のみが対象。
		//既に作られている場合とは、既存のコマンドで生成された$_、$[0-9]+である。testid$[0-9_]+
		Object t_obj = null;
		ObjectRecord t_arg = null;

		ObjectRecord tempOR = new ObjectRecord(0);
		tempOR.testID = testID;//Fakeの場合は使わないようにする。
		tempOR.stack = stack;
		tempOR.id = Integer.toString(testID) + stack;
		tempOR.testCase = curCase;

		if (instance_table.containsKey(stack)) {
			//再利用のために、stackがtestid$[_0-9]+である場合。
			t_obj = instance_table.get(stack);

			//DBを見てObjectRecord.jSONがあれば、再利用インスタンスを更新する。
			t_arg = s_db.getObjectRecord(tempOR);
			if (t_arg != null && t_arg.jSON != null && t_arg.jSON.length() > 0) {
				//Fakeかどうかで、設定方法が変わる。POJOの場合、素直に更新する。Fakeの場合、VCentralを更新する。
				if (t_arg.isPOJO) {
					//Fakeではないので、素直に更新する。
					if (isPrimitive2(t_obj.getClass())) {
						t_obj = Fson.fromJson(t_arg.jSON, t_obj.getClass());
					} else {
						Fson.updateFromJson(t_obj, t_arg.jSON);
					}
				}
			}
			//大域マップ属性（先頭　//"$.)のみ更新。CodeFairyで改変したメソッドから参照する場合がある。
			if (t_arg != null && t_arg.jSON != null && t_arg.jSON.length() > 0) {
				HashMap<String, Object> _map = getCommentMapFromJson(true, t_arg.jSON);//trueはglobalを拾い出す。
				if (_map != null && _map.size() > 0) {
					VCentral.prepare(_map);
				}
				//変数の属性を、ピンポイントで更新する。
				HashMap<String, Object> _map2 = getCommentMapFromJson(false, t_arg.jSON);//falseはローカルを拾い出す。
				for (Entry<String, Object> entry: _map2.entrySet()) {
					Fson.updateByJsonPath(t_obj, entry.getKey(), entry.getValue().toString());
				}
			}
		} else {
			// それ以外はObjectRecordテーブルの情報を元に、インスタンスをnewする。
			t_obj = getTObjectFromObjectRecord(tempOR);
		}
		return t_obj;
	}


	//ObjectRecordから再生できるオブジェクトはすぐに返す。未登録のオブジェクトは編集モードに限りFakeクラスを生成する。
	private static Object getTObjectFromObjectRecord(ObjectRecord tempOR) throws SecurityException,
			ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException, CannotCompileException,
			IOException, NotFoundException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException, ParseException, NotSupportedClassException {

		//DBを見てObjectRecordが登録済みなら取得する。未登録なら新規のクラスだ。
		ObjectRecord t_arg = s_db.getObjectRecord(tempOR);
		Object t_obj = null;

		if (t_arg != null) {
			// t_argがある場合、クラスファイルもJSONもある。生クラスもしくは定義済みFakeクラスでインスタンスを作る。
			t_obj = deserializeObject(t_arg);

			//JsonPathの末端で生成するソースを与える時に参照する可能性あり。
			addInstance(tempOR.id, t_obj);
		}

		//大域マップ属性（先頭　//"$.)のみ更新。CodeFairyで改変したメソッドから参照する場合がある。
		if (t_arg != null && t_arg.jSON != null && t_arg.jSON.length() > 0) {
			HashMap<String, Object> _map = getCommentMapFromJson(true, t_arg.jSON);
			if (_map != null && _map.size() > 0) {
				VCentral.prepare(_map);
			}
			//変数の属性を、ピンポイントで更新する。
			HashMap<String, Object> _map2 = getCommentMapFromJson(false, t_arg.jSON);
			for (Entry<String, Object> entry: _map2.entrySet()) {
				Fson.updateByJsonPath(t_obj, entry.getKey(), entry.getValue().toString());
			}
		}

		//未定義=nullとする場合もある。
		return t_obj;
	}

	//JSONからオブジェクトを生成する。
	private static Object deserializeObject(ObjectRecord rec)
			throws InstantiationException, IllegalAccessException, SecurityException,
			NoSuchFieldException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException,
			CannotCompileException, IOException, NotFoundException, ClassNotFoundException, ParseException, NotSupportedClassException {
		Object f_obj = null;//, i_obj = null;
		String c_name = rec.className;
		String f_name = null;//, i_name = null;

		if (rec.isPOJO == false) {
			File t_dir = new File(fake_dir);
			if (!t_dir.exists()) {
				alertAndExit(Messages.getString("Tool.17"), null, rec.testID); //$NON-NLS-1$
				return null;
			}

			//既に同一クラスのインスタンスがあるなら、それを使う。外部から設定される場合もある。
			f_obj = instance_table.get(c_name);
			if (f_obj == null) {
				f_name = "test.fake." + c_name; //$NON-NLS-1$

				Class<?> t_c;
				try {
					t_c = forName(f_name);
					f_obj = newObject(t_c, new Class<?>[0], new Object[0]);

				} catch (ClassNotFoundException e) {
					f_obj = createNewFakeClass(rec, true);//trueはインスタンス生成フラグ
				}

				//Fakeの場合、仮想メンバ変数を更新する。
				Fson.updateByJsonPath(f_obj, Messages.getString("Tool.52"), rec.jSON); //$NON-NLS-1$

				//Fakeはsingletonなのでクラス名で登録する。
				instance_table.put(c_name, f_obj);
			}
		} else if (rec.stack.endsWith(")")) { //$NON-NLS-1$
			//stackの最後がメソッドで終わっている。[0-9]+$[_0-9]+.method(type)である。
			//それを、JSONから引数オブジェクトを作って、実行し、結果を返す。
			Pattern t_pat = Pattern.compile("([0-9]+\\$[_0-9])\\.(\\w+)\\(([\\w\\.,\\[\\]]*)\\)"); //$NON-NLS-1$
			Matcher t_m = t_pat.matcher(rec.stack);
			if (!t_m.find()) {
				String t_key = t_m.group(1);
				Object t_obj = getInstance(t_key);

				if (t_obj != null) {
					//引数の型とインスタンスを抽出し、結果を_oList、_c_listに書き込む。
					try {
						Fson.setParameters(Fson.getFieldMapOfJson(rec.jSON));
					} catch (ClassNotFoundException e) {
						alertAndExit(Messages.getString("Tool.18") + t_m.group(2) + Messages.getString("Tool.19") + rec.className + ".", null, rec.testID); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}

					Class<?> t_cl = t_obj.getClass();
					Method t_md = t_cl.getMethod(t_m.group(2), Fson._c_list);
					if (t_md != null) {
						f_obj = t_md.invoke(t_obj, Fson._o_list);
					}
				}
			}
		} else {
			// JSONから復元するだけのPOJOの場合
			Class<?> t_cl;
			try {
				//基本型であれば、クラスに変換して処理を続ける。
				if ("java.lang.Class".equals(rec.className)) { //$NON-NLS-1$
					t_cl = null;
				} else {
					t_cl = forName(primitiveToFQN(rec.className));
				}
				//コメントを除去してからオブジェクトを作る。
				f_obj = getObjectfromJSON(t_cl, removeCommentFromJson(rec.jSON));
			} catch (ClassNotFoundException e) {
				alertAndExit(Messages.getString("Tool.20") + rec.className + ": " + rec.jSON, null, rec.testID); //$NON-NLS-1$ //$NON-NLS-2$
				return null;
			}
		}
		return f_obj;
	}


	//ObjectRecordを元に新クラスを生成する。
	//第二引数がtrueならインスタンスを生成して返す。falseなら、継続して編集するためにCtClassを返す。
	// fakeはクラスとして作るのでtestidを必要としない。
	private static Object createNewFakeClass(ObjectRecord orec, boolean b_newobj)
			throws CannotCompileException, IOException, NotFoundException,
			InstantiationException, IllegalAccessException, SecurityException, NoSuchFieldException,
			IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
		//必要なクラスを動的に作る。
		CtClass t_fake = c_pool.makeClass("test.fake." + orec.className); //$NON-NLS-1$
		CtClass t_impl = c_pool.makeClass("test.impl." + orec.className); //$NON-NLS-1$
		CtClass t_c = c_pool.get(orec.className);

		t_fake.stopPruning(true);
		t_impl.stopPruning(true);

		// interface, super classを設定する。
		if (t_c.isInterface()) {
			t_fake.addInterface(t_c);
			t_impl.addInterface(t_c);
		} else {
			t_fake.setSuperclass(t_c);
			t_impl.setSuperclass(t_c);
		}

		//fakeにimplへのリンクを持たせる。fakeは編集用、implは実働用である。
		t_fake.addField(CtField.make("private " + t_impl.getName() + " _impl;", t_fake)); //$NON-NLS-1$ //$NON-NLS-2$

		// implに属性管理のHashMapを持たせる。メンバ変数をMapで管理する。
		t_impl.addField(CtField.make("private java.util.HashMap _table;", t_impl)); //$NON-NLS-1$

		//Mapはデフォルトの仮想メンバ変数を洗い出すため。
		HashMap<String, String> _fmap = new HashMap<String, String>();
		//impl系のメソッドを実装する。abstractをなくさないとインスタンスができないため。fakeでないので第３引数はfalse。
		String t_msg = makeSkinOfMethods(t_impl, t_c, false, _fmap);
		if (t_msg != null && t_msg.length() > 0) {
			Tool.alertMSG(null, t_msg);
		}

		//implのコンストラクタを作る。(defaultConstructorが悪さをすることはない)
		CtConstructor i_Con = CtNewConstructor.defaultConstructor(t_impl);
		i_Con.setModifiers(javassist.Modifier.PUBLIC);
		t_impl.addConstructor(i_Con);

		String map_json = Fson.escape(getJSONfromObject(_fmap));
		String con_src = Messages.getString("Tool.54"); //$NON-NLS-1$
		con_src += Messages.getString("Tool.56") + map_json + Messages.getString("Tool.57"); //$NON-NLS-1$ //$NON-NLS-2$
		i_Con.setBody("{" + con_src + Messages.getString("Tool.58")); //$NON-NLS-1$ //$NON-NLS-2$

		//t_implのtoStringを作っておく。Objectのものをoverrideする。。
		CtMethod impl_tostr = null;
		try {
			impl_tostr = t_impl.getMethod("toString", "()Ljava/lang/String;"); //$NON-NLS-1$ //$NON-NLS-2$
			makeSkinOfMethod(t_impl, impl_tostr, "{return ($r)_table.toString();}", true, null);	//overrideなのでtrue //$NON-NLS-1$
		} catch (Exception e) {
			alertMSG(null, Messages.getString("Tool.21") + t_impl.getName()); //$NON-NLS-1$
		}

		//【保存①】implをクラスファイルとして保存する。implの作成はここで一段落したので保存する。
		//保存しないと、fakeのコンパイルでimplの呼び出し部分が失敗する。
		t_impl.writeFile(fake_dir);

		//fakeのコンストラクタを作る。(defaultConstructorが悪さをすることはない)
		CtConstructor f_Con = CtNewConstructor.defaultConstructor(t_fake);
		f_Con.setModifiers(javassist.Modifier.PUBLIC);
		t_fake.addConstructor(f_Con);

		//分離しておいた方が、デバッグしやすい。傷害調査の時は、生成をdeserializeで行うことも可能（その中の関係する処理のコメントアウトを外せばよい）。
		String fcon_src = Messages.getString("Tool.59") + t_impl.getName() + Messages.getString("Tool.60"); //$NON-NLS-1$ //$NON-NLS-2$
		f_Con.setBody(fcon_src); //$NON-NLS-1$ //$NON-NLS-2$

		//fake系はimplへの中継ぎ処理を入れるのでtrue。
		t_msg = makeSkinOfMethods(t_fake, t_c, true, null);
		if (t_msg != null && t_msg.length() > 0) {
			Tool.alertMSG(null, t_msg);
		}

		//【保存②】クラスファイルとして保存する。
		t_fake.writeFile(fake_dir);

		if (b_newobj) {
			//この段階ではimplの実体はない。Fakeクラスも新規であり、実態不明のため。また、JSONも未定義。
			Class<?> f_c = toClass(t_fake);
			Object f_obj = newObject(f_c, new Class<?>[]{}, new Object[]{});
			return f_obj;
		}
		return t_impl;//第二引数がfalseの時は、implをこの後で編集するのでCtClassを返す。（reflesh）
	}

	//元のメソッドを退避し、殻だけ作る。Fakeなら、指定のソースを埋め込む。
	//最後のmapはimplの場合に、仮想変数と型を格納するため。それ以外はnullでよい。
	private static String makeSkinOfMethods(CtClass declaring, CtClass original, boolean b_fake, HashMap<String, String> map) {
		//interfaceやabstractもあるので、CtNewMethod.copyは使わない。
		CtMethod[] t_meths = original.getMethods();
		String t_msg = ""; //$NON-NLS-1$

		String t_msgsrc = 	"com.ftinc.si.assist.test.Tool#poppedFake();";//デバッグ用 //$NON-NLS-1$
		for (int i = 0; i < t_meths.length; i++) {
			if (!t_meths[i].getDeclaringClass().getName().equals("java.lang.Object")) { //$NON-NLS-1$
				//Objectクラスのメソッドは作る必要がない。
				String src = null;
				try {
					if (b_fake) {
						//Fake系の場合、implへの中継ぎ処理を入れる。
						src = _defaultSource(original.getName(), t_meths[i].getName());
					}
					CtMethod t_mt = makeSkinOfMethod(declaring, t_meths[i], src, true, map);//trueでoverrideする。
					if (b_fake) {
						t_mt.insertAfter(t_msgsrc);//debug用コードの差し込み
					}
				} catch (Exception e) {
					//finalなどメソッドが作れないものはエラーになるが無視する。
					if (javassist.Modifier.isAbstract(t_meths[i].getModifiers())) {
						t_msg += Messages.getString("Tool.22") + t_meths[i].getName() + " msg=" + e.getMessage() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else if (javassist.Modifier.isFinal(t_meths[i].getModifiers())) {
						t_msg += Messages.getString("Tool.23") + t_meths[i].getName() + " msg=" + e.getMessage() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else if (javassist.Modifier.isNative(t_meths[i].getModifiers())) {
						t_msg += Messages.getString("Tool.24") + t_meths[i].getName() + " msg=" + e.getMessage() + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				}
			}
		}
		return t_msg;
	}

	//元のメソッドを皮だけコピーし、指定のソースを埋め込む。
	//そのクラスで宣言済みなら、そのメソッドを返す。最終引数は、継承する処理（true）か退避処理（false）。
	//　※超重要：スーパークラスのメソッドならコピーを作って、declaredにする。（スーパーに影響を及ぼさないため）
	//返却値は実行時編集で必要。
	//最後のmapはimplの場合に、仮想変数と型を格納するため。それ以外はnullでよい。
	public static CtMethod makeSkinOfMethod(CtClass declaring, CtMethod original, String src, boolean inherited, HashMap<String, String> map) throws CannotCompileException, NotFoundException {
		if (javassist.Modifier.isFinal(original.getModifiers()) || javassist.Modifier.isNative(original.getModifiers())) {
			//finalやNativeはoverrideしてはいけない。
			return null;
		}
		CtClass t_super = original.getDeclaringClass();

		if (!inherited && javassist.Modifier.isAbstract(original.getModifiers())) {
			//サブクラスでなく、かつ、abstructなら退避処理でできないのでパス。サブクラスなら、実装する意味がある。
			return null;
		} else if (inherited && declaring.getClass().getName().equals(t_super.getName())) {
			return null;//引数に矛盾あり
		}

		CtMethod t_target = null;
		CtClass[] params = original.getParameterTypes();
		String method_name = original.getName();

		if (inherited) {
			if (!javassist.Modifier.isInterface(t_super.getModifiers())) {
				//interfaceでないこと。interfaceは継承（override）が不要なため。

				CtMethod temp_mt = null;
				try {
					temp_mt = declaring.getDeclaredMethod(method_name, params);
				} catch (NotFoundException e) {
					//何もしない。宣言されていなかっただけ。
				}
				if (temp_mt == null) {
					//宣言されていない。スーパークラスで宣言されている。
					//①メソッドをコピーする。②名前を変える。abstractであっても、継承関係設定のために必要。
					temp_mt = CtNewMethod.copy(original, method_name, declaring, null);
					declaring.addMethod(temp_mt);
					if (javassist.Modifier.isAbstract(temp_mt.getModifiers())) {
						//Abstractなら中身を入れ替える。
						t_target = temp_mt;
					} else {
						//スーパーで実装されている場合にも、退避する。t_targetはnullのまま。
						temp_mt.setName(method_name + "_$"); //$NON-NLS-1$
					}
				} else {
					//Abstractでなければ、退避するのみ。t_targetはnullのまま。
					temp_mt.setName(method_name + "_$"); //$NON-NLS-1$
				}
			} else {
				//interfaceの場合は、新しく作るのみ。t_target=nullのまま。
			}
		} else {
			//元のメソッドが同じクラスなので、名前を変える。abtractでないことは確定済み。
			original.setName(method_name + "_$"); //$NON-NLS-1$
		}

		CtClass r_type = original.getReturnType();
		CtClass[] exps = original.getExceptionTypes();

		if (t_target == null) {
			//未宣言もしくはAbstractなら作る
			t_target = CtNewMethod.make(r_type, method_name, params, exps, null, declaring);
			declaring.addMethod(t_target);
			t_target.setModifiers(original.getModifiers());
		}
		if (src == null || src.length() == 0) {
			HashMap<String, String> t_map = null;
			if (Modifier.isPublic(t_target.getModifiers())) {
				//publicの時だけ仮想メンバを作成するためのmapを渡す。
				t_map = map;
			}

			src = _defaultSource4Abstract(method_name, r_type.getName(), params, t_map);
		}
		if (map != null) {
			//fakeの場合、第一引数をnullにするのがお約束。仮想メンバ変数抽出のため。
			src = CodeProcessor.preProcessMain(declaring, t_target, src);
		} else {
			src = CodeProcessor.preProcessMain(declaring, t_target, src);
		}

		if (src != null) {
			t_target.setBody(src);
		} else {
			//何かしら具体的なメソッドを作らないとabstractが残り、instanceを生成できない。
			alertMSG(null, Messages.getString("Tool.25")); //$NON-NLS-1$
		}
		return t_target;
	}

	//abstractメソッドのデフォルト実装。何かしらソースがないとabstractのままなので。
	//最後のmapはimplの場合に、仮想変数と型を格納するため。それ以外はnullでよい。
	private static String _defaultSource4Abstract(String mname, String rtype, CtClass[] params, HashMap<String, String> map) {
		//注意!!返却値を明にキャストしないと、VerifyErrorが起こる。
		String t_src = Messages.getString("Tool.61"); //$NON-NLS-1$
		String fname = getImplFieldName(mname);
		String ftype = rtype;

		if (map != null && fname != null) {
			if (rtype.equals(Messages.getString("Tool.62"))) { //$NON-NLS-1$
				//残りはvoid型
				if (params.length == 1) {
					//PrimitiveならObjectに変換する。
					String t_cast0 = Messages.getString("Tool.63"); //$NON-NLS-1$

					String t_type = params[0].getName();
					t_src = Messages.getString("Tool.64") + t_type; //$NON-NLS-1$
					if (params[0].isPrimitive()) {
						t_type = primitiveToFQN(t_type);
						t_src = t_type;
						t_cast0 = Messages.getString("Tool.65"); //$NON-NLS-1$
					}
					ftype = t_type;
					if (t_type.endsWith(Messages.getString("Tool.66"))) { //$NON-NLS-1$
						t_src = Messages.getString("Tool.67"); //$NON-NLS-1$
					}
					t_src += Messages.getString("Tool.68") + fname + Messages.getString("Tool.69") + t_cast0 + Messages.getString("Tool.70"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				} else if (params.length == 2) {
					//PrimitiveならObjectに変換する。
					String t_cast0 = Messages.getString("Tool.71"); //$NON-NLS-1$
					if (params[0].isPrimitive()) {
						t_cast0 = Messages.getString("Tool.72"); //$NON-NLS-1$
					}
					String t_cast1 = Messages.getString("Tool.73"); //$NON-NLS-1$
					if (params[1].isPrimitive()) {
						t_cast1 = Messages.getString("Tool.74"); //$NON-NLS-1$
					}
					ftype = Messages.getString("Tool.75"); //$NON-NLS-1$
					t_src = Messages.getString("Tool.76") + fname + Messages.getString("Tool.78"); //$NON-NLS-1$ //$NON-NLS-2$
					t_src += Messages.getString("Tool.79") + t_cast0 + Messages.getString("Tool.80") + t_cast1 + Messages.getString("Tool.81"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
			} else {
				//何かを返す。
				Class<?> r_c = null;
				try {
					r_c = forName(rtype);
				} catch (ClassNotFoundException e) {
					//ここにきている時点であり得ない例外。スルーする。
				}
				if (params.length == 0) {
					//単なるgetter
					t_src = Messages.getString("Tool.82") + rtype; //$NON-NLS-1$
					if (r_c.isArray()) {
						ftype = Messages.getString("Tool.83"); //配列のキャストはObject型かたのみ可能。 //$NON-NLS-1$
						t_src = Messages.getString("Tool.84") + fname + Messages.getString("Tool.85"); //$NON-NLS-1$ //$NON-NLS-2$
						t_src += Messages.getString("Tool.86") + rtype + Messages.getString("Tool.87") + fname + Messages.getString("Tool.88"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					} else if (r_c.isPrimitive()) {
						ftype = primitiveToFQN(ftype);
						t_src = Messages.getString("Tool.89") + ftype + Messages.getString("Tool.90") + fname + Messages.getString("Tool.91");; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						t_src += returnSrc4Primitive(rtype, fname);
					} else {
						//基本型ではない
						t_src += Messages.getString("Tool.92") + fname + Messages.getString("Tool.93"); //$NON-NLS-1$ //$NON-NLS-2$
						t_src += Messages.getString("Tool.94") + rtype + Messages.getString("Tool.96") + fname + Messages.getString("Tool.97"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				} else if (params.length == 1) {
					//キーから値を取得。

					//PrimitiveならObjectに変換する。
					String t_cast0 = Messages.getString("Tool.99"); //$NON-NLS-1$
					if (params[0].isPrimitive()) {
						t_cast0 = Messages.getString("Tool.100"); //$NON-NLS-1$
					}

					ftype = Messages.getString("Tool.101"); //$NON-NLS-1$
					t_src = Messages.getString("Tool.102") + fname + Messages.getString("Tool.103"); //$NON-NLS-1$ //$NON-NLS-2$
					t_src += Messages.getString("Tool.104") + fname + Messages.getString("Tool.105") + returnSrc4Primitive(rtype, fname +Messages.getString("Tool.106") + t_cast0 + Messages.getString("Tool.107")) + Messages.getString("Tool.108"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
					t_src += fname + Messages.getString("Tool.109"); //$NON-NLS-1$
					t_src += Messages.getString("Tool.110") + primitiveDefaultValue(rtype) + Messages.getString("Tool.111"); //$NON-NLS-1$ //$NON-NLS-2$
				} else {
					t_src = Messages.getString("Tool.112") + primitiveDefaultValue(rtype) + Messages.getString("Tool.113"); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}
		}
		if (t_src.length() > 2 && fname != null && map != null) {
			if (!map.containsKey(fname)) {
				map.put(fname, null);
				map.put(fname + Messages.getString("Tool.114"), ftype); //$NON-NLS-1$
			}
		}
		return "{" + t_src + Messages.getString("Tool.115"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	private static String primitiveDefaultValue(String rtype) {
		//注意!!返却値を明にキャストしないと、VerifyErrorが起こる。
		String r_val = Messages.getString("Tool.116"); //$NON-NLS-1$
		if (rtype.matches("^(int|long|double|float|byte|char)$")) { //$NON-NLS-1$
			r_val = Messages.getString("Tool.117") + rtype + Messages.getString("Tool.118"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (rtype.matches("^.*(Integer|Long|DecimalDouble|Float|Byte|Character)$")) { //$NON-NLS-1$
			r_val = Messages.getString("Tool.119") + rtype + Messages.getString("Tool.120");		//-1はDateを表現するlongではnullに相当。$ //$NON-NLS-1$ //$NON-NLS-2$
		} else if (rtype.matches("^.*(Boolean)$")) { //$NON-NLS-1$
			r_val = Messages.getString("Tool.121") + rtype + Messages.getString("Tool.122"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (rtype.matches("^.*(boolean|Boolean)$")) { //$NON-NLS-1$
			r_val = Messages.getString("Tool.123"); //$NON-NLS-1$
		}
		return r_val;
	}

	//Wrapperから基本型を返すソース
	private static String returnSrc4Primitive(String typeName, String fname) {
		if (Messages.getString("Tool.124").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.125") + fname + Messages.getString("Tool.126"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.127").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.128") + fname + Messages.getString("Tool.129"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.130").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.131") + fname + Messages.getString("Tool.132"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.133").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.134") + fname + Messages.getString("Tool.135"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.136").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.137") + fname + Messages.getString("Tool.138"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.139").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.140") + fname + Messages.getString("Tool.141"); //$NON-NLS-1$ //$NON-NLS-2$
		} else if (Messages.getString("Tool.142").equals(typeName)) { //$NON-NLS-1$
			return Messages.getString("Tool.143") + fname + Messages.getString("Tool.144"); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return Messages.getString("Tool.145") + typeName + Messages.getString("Tool.146") + fname + Messages.getString("Tool.147"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
	}


	//implのための仮想メンバ変数名を合成する。対象はGetter、Setterのみ。
	private static String getImplFieldName(String mname) {
		final Pattern t_pat = Pattern.compile(Messages.getString("Tool.148")); //$NON-NLS-1$
		Matcher t_m = t_pat.matcher(mname);
		if (t_m.find()) {
			return Messages.getString("Tool.149") + t_m.group(1).toLowerCase() + t_m.group(2); //$NON-NLS-1$
		}
		final Pattern t_pat2 = Pattern.compile(Messages.getString("Tool.150")); //$NON-NLS-1$
		t_m = t_pat2.matcher(mname);
		if (t_m.find()) {
			String t_str = t_m.group(2);
			if (t_str != null && t_str.length() > 1) {
				return Messages.getString("Tool.151") + t_m.group(2); //$NON-NLS-1$
			}
		}
		if (mname.equals(Messages.getString("Tool.152")) || mname.equals(Messages.getString("Tool.153")) || mname.equals(Messages.getString("Tool.154")) || mname.equals(Messages.getString("Tool.155")) || mname.equals(Messages.getString("Tool.156"))) { //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
			//メソッド名が単純であり、別の取得名(get/lookup)がある場合。
			return Messages.getString("Tool.157"); //$NON-NLS-1$
		}
		return Messages.getString("Tool.158") + mname; //$NON-NLS-1$
	}


	// Fakeメソッドのソースを出力する。
	private static String _defaultSource(String cname, String MethodName) {
		//終了かどうかは、Toolに任せる。リロードのためである。
		String s_delegate = "{"; //$NON-NLS-1$

		//Fakeの編集不要ならFalseが返り続行する。999で終了した場合、再度プロセスを立ち上げて実行する。
		s_delegate +="com.ftinc.si.assist.test.Tool#pushedFake(\""  //$NON-NLS-1$
				+ cname + "\",\"" + MethodName + "\", $type, _impl, $args);"; //$NON-NLS-1$ //$NON-NLS-2$

		//fakeにimpleへの参照リンクがある。「_impl」である。
		s_delegate += "return _impl." + MethodName + "($$);"; //$NON-NLS-1$ //$NON-NLS-2$
		s_delegate += "}"; //$NON-NLS-1$
		return s_delegate;
	}


	// Implに属性fnameを値oで追加する。
	@SuppressWarnings("unchecked")
	public static void addFieldObj(Object impl, String fname, Object o) {
		Class<?> c_obj = impl.getClass();
		Field t_f;
		try {
			t_f = getField(c_obj, "_table"); //$NON-NLS-1$
			t_f.setAccessible(true);
			HashMap<String, Object> t_h = (HashMap<String, Object>) t_f.get(impl);
			t_h.put(fname, o);
		} catch (SecurityException e) {
			alertMSG(null, e.getMessage());
		} catch (IllegalArgumentException e) {
			alertMSG(null, e.getMessage());
		} catch (IllegalAccessException e) {
			alertMSG(null, e.getMessage());
		}
	}


	// 引数はObjectRecordのID(主役)	。編集するためのWindowを開く。idはObjectRecordeを射す。
	// あくまでも未定義のimplのメソッドを編集モードで検知して編集する。
	// ※追加：CodeFairyのソースを編集できる機能にも利用することにした。
	public static void pushedFake(String cname, String mname, Class<?> vtype, Object subj, Object[] objs) {
		//Fakeの中でエラーが起きた場合の手がかりを記録
		String t_mstr = cname + "#" + mname + "(" + getJSONfromObject(objs) +")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
		if (subj != null && subj.getClass().getName().startsWith("test.")) { //$NON-NLS-1$
			//Fakeである。
			cur_commander.m_fakeStack.add(t_mstr);
			cur_commander.m_lastCallofFake = t_mstr;
		}

		int testId = curID;

		//実行時でない場合、かつ、methodがtoString()の場合には、エディタは開かない。eclipseで状態を見ようとするときにも呼ばれるため。
		//実行時ならcurCaseが指定されているはず。
		if ((curCase == null || curCase.length() == 0) && mname.equals("toString")) { //$NON-NLS-1$
			return;
		}

		//エディタを開いて、Implクラス（実態はHashMap）を編集する。
		//implにメソッドが実装されていればエディタは開かない。未実装なら開く。
		//editmodeでない場合、未実装なら、alertを出してExit。

		FakeEditorOnRun t_dlg;

		//最終的に設定しなかった場合、Exitするかどうか問うメッセージ。
		String e_msg = ""; //$NON-NLS-1$

		// implのメソッドが未実装ならロジックを作成して埋め込む。
		CtClass t_impl = null;

		int phase = 0;
		String src = ""; //$NON-NLS-1$
		try {
//			t_impl = c_pool.get("test.impl." + cname); //$NON-NLS-1$
			if (subj != null) {
				t_impl = c_pool.get(subj.getClass().getName());
			} else {
				//staticメソッドの変更の場合。
				t_impl = c_pool.get(cname); //$NON-NLS-1$
			}

			// implのメソッドにロジックを埋め込むため解凍する。
			t_impl.defrost();

			FakeMethodRecord t_fake = _db().getMethodSource(cname, mname, convClassNameList(objs), version);
			if (t_fake == null) {
				//DB未登録なら
				t_fake = new FakeMethodRecord(2);
				t_fake.className = cname;
				t_fake.methodName = mname;
				t_fake.superName = t_impl.getSuperclass().getName();
				t_fake.argTypes = convClassNameList(objs);
				t_fake.source = _defaultSource4Abstract(mname, vtype.getName(), getCtParams(t_fake.argTypes), null);
			}

			CtMethod t_mt = null;
			try {
				if (!editmode) {
					//編集モードでないので、そのまま返る。
					return;
				}
				t_mt = t_impl.getDeclaredMethod(mname, getCtParams(t_fake.argTypes));

			} catch (NotFoundException e1) {
				//t_mtが見つからない場合がある。初回の呼び出し時にはないはず。
				if (!editmode) {
					//未実装なので、編集モード以外は、継続不可である。
					alertAndExit(cname + "#" + mname + Messages.getString("Tool.26"), null, testId);  //$NON-NLS-1$//$NON-NLS-2$
					return;
				}
			}
			if (t_mt == null) {
				return;
			}

			Exception t_exp = new Exception();//現在の呼び出し位置を表示するために例外を使う。
			t_dlg = new FakeEditorOnRun(t_fake, vtype, subj, objs, t_exp, readAnnotationFromMethod(t_mt, "updated")); //$NON-NLS-1$
			t_dlg.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			t_dlg.setBounds(50 , 100 , 300 , 100);
			t_dlg.setVisible(true);

			//必要に応じてDB更新
			if (t_dlg.isExiting) {
				alertAndStop(Messages.getString("Tool.27")); //$NON-NLS-1$
			} else if (t_dlg.changed()) {
				// fakeのDB更新。
				s_db.updateRecordBySQL(t_fake.getUpdateSQL(), true);

				CtClass t_sup = t_impl.getSuperclass();
				CtClass t_newsup = c_pool.get(t_dlg.getSuperClassName());

				//SuperClassが変更された場合、継承先を変える。(Interfaceの場合を除く)
				if (!t_newsup.isInterface()) {
					if (!t_sup.getName().equals(t_dlg.getSuperClassName())) {
						t_impl.setSuperclass(t_newsup);
					}
				}

				//PreProcesserでプレコンパイル。
				src = CodeProcessor.preProcessMain(t_impl, t_mt, t_dlg.getSource());

				//チェックでエラーになるとnullになる。
				if (src == null) {
					alertMSG(null, cname + "#" + mname + Messages.getString("Tool.28") + t_dlg.getSource());  //$NON-NLS-1$//$NON-NLS-2$
					destroy(); //外部子プロセスもあるのできちんと後始末する。
					System.exit(1000 + testId);
				} else {
					phase = 200;

					//setBodyでバイトコード挿入
					t_mt.setBody(src);
					phase++;
					final SimpleDateFormat t_fmt = new SimpleDateFormat("yyyy/MM/dd (hh:mm:ss)"); //$NON-NLS-1$
					writeAnnotationToMethod(t_mt, "updated",t_fmt.format(new Date())); //$NON-NLS-1$
					phase++;
					t_impl.writeFile(fake_dir);
					phase++;
				}
				alertMSG(null, Messages.getString("Tool.29") + t_mstr + Messages.getString("Tool.30")); //$NON-NLS-1$ //$NON-NLS-2$
				destroy(); //外部子プロセスもあるのできちんと後始末する。
				System.exit(999);//もう一回、実行するためのサイン。
			} else {
				t_mt = t_impl.getDeclaredMethod(mname, getCtParams(t_fake.argTypes));
				if (t_mt == null) {
					//編集ダイアログをキャンセルで閉じたが、メソッドが未定義の場合。
					alertAndStop(mname + Messages.getString("Tool.31")); //$NON-NLS-1$
				} else if (t_dlg.beSkipped) {
					//スルーが指定されたメソッド。
					return;
				} else {
					//編集ダイアログをキャンセルで閉じたが、メソッドが定義済みの場合。
					alertMSG(null, cname + "#" + mname + Messages.getString("Tool.32"));  //$NON-NLS-1$//$NON-NLS-2$
					return;
				}
			}
		} catch (SQLException|IOException|CannotCompileException|NotFoundException e) {
			if (e.getMessage() != null) {
				e_msg += e.getMessage() + "\n"; //$NON-NLS-1$
			}
		} catch (Exception e) {
			//その他の例外
			if (phase >= 200 && phase < 300) {
				if (phase == 200) {
					//setBodyでエラー
					e_msg = Messages.getString("Tool.33") + src + "\n";; //$NON-NLS-1$ //$NON-NLS-2$
					e_msg += "obj = " + getJSONfromObject(subj) + "\n"; //$NON-NLS-1$ //$NON-NLS-2$
				}
			} else if (e.getClass().getSimpleName().equals("RejectedExecutionException")) { //$NON-NLS-1$
				throw (RejectedExecutionException)e;
			}
			if (e_msg.length() == 0 && phase < 200) {
				e_msg = Messages.getString("Tool.34"); //$NON-NLS-1$
			}
		}
		alertAndExit(t_mstr + " msg =" + e_msg, null, testId); //$NON-NLS-1$
	}

	//Fakeの中でエラーが起きた場合の手がかりを削除（用済み）
	public static void poppedFake() {
		if (editmode) {
			int n = cur_commander.m_fakeStack.size();
			if (n > 0) {
				cur_commander.m_fakeStack.remove(n - 1);
			}
		}
	}

	//アノテーションのインタフェース
	public @interface Updated {
		String updated();
	}

	//指定のアノテーションを取得
	public static String readAnnotationFromMethod(CtMethod ctm, String attrName) {
		CtClass t_c = ctm.getDeclaringClass();
		if (t_c.isFrozen()) {
			t_c.defrost();
		}

		MethodInfo t_minfo = ctm.getMethodInfo();

		AnnotationsAttribute t_attr = (AnnotationsAttribute)t_minfo.getAttribute(AnnotationsAttribute.visibleTag);
		if (t_attr == null) {
			return ""; //$NON-NLS-1$
		}
		Annotation t_an = t_attr.getAnnotation("Updated"); //$NON-NLS-1$
		String t_res = null;
		if (t_an != null && attrName != null) {
			t_res = ((StringMemberValue)t_an.getMemberValue(attrName)).getValue();
		}
		if (t_res == null) {
			t_res = ""; //$NON-NLS-1$
		}
		return t_res;
	}

	//メソッドのアノテーションに更新日付などの情報を書き込む
	private static void writeAnnotationToMethod(CtMethod ctm, String attrName, String info) {
		//アノテーション操作に必要なオブジェクトを取得
		ClassFile t_cfile=ctm.getDeclaringClass().getClassFile();
		ConstPool t_constp = t_cfile.getConstPool();

		//アノテーションを生成
		AnnotationsAttribute t_aa = new AnnotationsAttribute(t_constp, AnnotationsAttribute.visibleTag);

		//第一引数はアノテーションのインタフェース名
		Annotation t_ant =new Annotation("Updated",t_constp); //$NON-NLS-1$

		//アノテーションの値をセットする
		t_ant.addMemberValue(attrName, new StringMemberValue(info, t_constp));

		t_aa.setAnnotation(t_ant);
		ctm.getMethodInfo().addAttribute(t_aa);
	}

	//引数情報からクラス名を抽出し、Classの配列を作る。
	public static Class<?>[] getArgClasses(TestCommandRecord cmd) {
		int len = cmd.numArgs();
		int pos = cmd.varArgsPos();

		ArrayList<Object> t_list = new ArrayList<Object>();
		if (pos >= 0) {
			//可変長引数の場合
			String vcname = cmd.getArgClass(pos);
			Class<?> compClass;
			try {
				compClass = Tool.forName(vcname);
				Object t_arry = Array.newInstance(compClass, 0);
				t_list.add(t_arry.getClass());
			} catch (ClassNotFoundException e) {
				alertMSG(null, e.getMessage());
			}
		} else {
			//可変長ではない
			pos = len;
		}

		for (int i = pos - 1; i >= 0; i--) {//引数はlen個ある。
			try {
				String t_str = cmd.getArgClass(i);//$1の場合、i=0,
				t_list.add(0, forName(t_str));
			} catch (ClassNotFoundException e1) {
				alertMSG(null, e1.getMessage());
			}
		}
		return t_list.toArray(new Class<?>[t_list.size()]);
	}


	//カンマ区切りの文字列を分解し、CtClassの配列にする。
	public static CtClass[] getCtParams(String[] atypes) {
		ArrayList<CtClass> t_list = new ArrayList<CtClass>();
		for (int i =0; i < atypes.length; i++) {
			try {
				String t_str = atypes[i];
				if (t_str.matches("^.*\\s+[0-9\\$]+$")) { // Stack情報が末尾に付いている場合には除去。 //$NON-NLS-1$
					t_str = t_str.replaceAll("^(.*)\\s+[0-9\\$]+$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
				} else if (t_str.matches("^.*\\s+\\$[0-9]+$")) { //$NON-NLS-1$
					t_str = t_str.replaceAll("^(.*)\\s+\\$[0-9]+$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
				}
				if (t_str.length() > 0) {//空文字は何もしない。
					t_list.add(c_pool.get(t_str));
				}
			} catch (NotFoundException e) {
				alertMSG(null, e.getMessage());
			}
		}
		return t_list.toArray(new CtClass[t_list.size()]);
	}



	//////////// CodeFairy系 /////////////////////////////////
	//argtypesが*の場合、最初に一致したものを返す。
	public static CtMethod getCtMethod(CtClass t_c, String cname, String mname, String[] argtypes) throws NotFoundException {
		if (t_c == null) {
			t_c = c_pool.get(cname);
		}
		CtMethod res = null;
		if (argtypes == null || argtypes.length == 0) {
			//引数に特に指定がなければメソッド名だけ探す。
			CtMethod[] t_m = t_c.getDeclaredMethods();
			for (int i = 0; i < t_m.length; i++) {
				if (mname.equals(t_m[i].getName())) {
					res = t_m[i];
					break;
				}
			}
			if (res == null) {
				CtClass s_c = t_c.getSuperclass();
				if (s_c != null) {
					//スーパークラスに活路を見出す。
					return getCtMethod(s_c, s_c.getName(), mname, argtypes);
				}
			}
		} else {
			try {
				res = t_c.getDeclaredMethod(mname, getCtParams(argtypes));
			} catch (NotFoundException e) {
				CtClass s_c = t_c.getSuperclass();
				if  (s_c != null) {
					//スーパークラスに活路を見出す。
					return getCtMethod(s_c, s_c.getName(), mname, argtypes);
				}
			}
		}
		if (res == null) {
			//インタフェースをチェックする。
			CtClass[] ifs = t_c.getInterfaces();
			for (int j = 0; j < ifs.length; j++) {
				res = getCtMethod(ifs[j], ifs[j].getName(), mname, argtypes);
				if (res != null) {
					break;
				}
			}
		}

		return res;
	}


	////////////ここから定義ツール系/////////////////////////////////

	//指定のクラス#メソッドに例外が定義されていれば、そのリストを返す。
	public static String[] getExceptions(TestCommandRecord rec) {
		ArrayList<String> t_list = new ArrayList<String>();
		try {
			CtClass t_c = c_pool.get(rec.className);

			//static修飾子を除去してメソッドを取得。
			CtMethod t_b = t_c.getDeclaredMethod(rec.methodName.replaceFirst("static ", "")); //$NON-NLS-1$ //$NON-NLS-2$

			CtClass[] tc_list = t_b.getExceptionTypes();
			if (tc_list != null) {
				for (int i = 0; i < tc_list.length; i++) {
					t_list.add(tc_list[i].getName());
				}
			}
		} catch (NotFoundException e) {
			alertMSG(null, e.getMessage());
			return null;
		}
		return t_list.toArray(new String[t_list.size()]);
	}

	//インスタンス化されたの引数のクラスの組argtypesにマッチするメソッドを返す。
	public static Method getMethod(Class<?> c, String mname, Class<?>[] argtypes) throws NotFoundException, NoSuchMethodException, SecurityException {
		if (c == null) {
			return null;
		}
		Method t_result = null;
		Method[] t_ms = c.getDeclaredMethods();
		if (t_ms != null && t_ms.length > 0) {
			for (int i = 0; i < t_ms.length; i++) {
				Method t_m = t_ms[i];
				if (mname.equals(t_m.getName())) {
					boolean t_match = true;
					Class<?>[] t_types = t_m.getParameterTypes();
					if (t_types.length == argtypes.length) {
						for (int j = 0; j < t_types.length; j++) {
							//ここがポイント。引数の組が派生ならOK。
							if (argtypes[j] != null && !t_types[j].isAssignableFrom(argtypes[j])) {
								t_match = false;
							}
						}
						if (t_match) {
							t_result = t_m;
						}
					}
				}
			}

			if (t_result == null) {
				Class<?> s_c = c.getSuperclass();
				if (s_c != null) {
					//スーパークラスに活路を見出す。
					t_result = getMethod(s_c, mname, argtypes);
				}
			}
		}
		if (t_result == null) {
			//インタフェースをチェックする。
			Class<?>[] ifs = c.getInterfaces();
			for (int j = 0; j < ifs.length; j++) {
				t_result = getMethod(ifs[j], mname, argtypes);
				if (t_result != null) {
					break;
				}
			}
		}
		return t_result;
	}


	//指定のクラス名の概要オブジェクトを返す。
	public static ClassOutLine getClassOutLine(String cname) {
		if (cname == null || cname.indexOf("%") >= 0) { //$NON-NLS-1$
			//クラス名が与えられていない。
			return null;
		}

		try {
			CtClass t_c = c_pool.get(cname);
			if (t_c != null) {
				return new ClassOutLine(t_c, isFake(cname));
			}
		} catch (NotFoundException e) {
			alertMSG(null, e.getMessage() + Messages.getString("Tool.35")); //$NON-NLS-1$
		}
		return null;
	}

	//指定のクラス名がFakeとして登録済みかどうかを調べる。
	public static boolean isFake(String cname) {
		if (cname.endsWith(Messages.getString("Tool.159"))) { //$NON-NLS-1$
			return true;
		}
		if (cname.startsWith(Messages.getString("Tool.160"))) { //$NON-NLS-1$
			return true;
		}
		if (cname.startsWith(Messages.getString("Tool.161"))) { //$NON-NLS-1$
			return true;
		}

		try {
			//fakeであるかどうか試す。
			if (!cname.startsWith(Messages.getString("Tool.162"))) { //$NON-NLS-1$
				cname = Messages.getString("Tool.163") + cname; //$NON-NLS-1$
			}

			@SuppressWarnings("unused")
			CtClass t_fake = c_pool.get(cname); //$NON-NLS-1$
		} catch (Exception e1) {
			return false;
		}
		return true;
	}

	//オブジェクトの配列を、クラス名の配列に変換する。
	public static <T> String[] convClassNameList(T[] objs) {
		String[] t_list = new String[objs.length];
		for (int i =0; objs.length > i; i++) {
			t_list[i] = objs[i].getClass().getName();
		}
		return t_list;
	}

	// インスタンス登録
	public static void addInstance(String stack, Object obj) {
		if (obj != null) {
			instance_table.put(stack, obj);
		}
	}

	//まとめてインスタンス登録(TestCommanderから呼ばれる)
	public static void addInstanceArgs(String sid, Object[] objs, int vpos) {
		for (int i = 0; i < objs.length; i++) {
			if (i == vpos) {
				//可変長の処理
				Object t_ary = objs[i];
				int n = Array.getLength(t_ary);
				for (int j = 0; j < n; j++) {
					Object t_obj = Array.get(t_ary, j);
					instance_table.put(sid + "$" + Integer.valueOf(i + j + 1).toString(), t_obj); //$NON-NLS-1$
				}
				break;
			} else {
				instance_table.put(sid + "$" + Integer.valueOf(i + 1).toString(), objs[i]); //$NON-NLS-1$
			}
		}
	}

	//インスタンス取得
	public static Object getInstance(String name) {
		return instance_table.get(name);
	}


	//基本データ型名をクラス名に変換する。
	public static String primitiveToFQN(String cname) {
		if ("int".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Integer"; //$NON-NLS-1$
		} else if ("long".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Long"; //$NON-NLS-1$
		} else if ("float".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Float"; //$NON-NLS-1$
		} else if ("double".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Double"; //$NON-NLS-1$
		} else if ("boolean".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Boolean"; //$NON-NLS-1$
		} else if ("byte".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Byte"; //$NON-NLS-1$
		} else if ("char".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Character"; //$NON-NLS-1$
		} else if ("int[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Integer[]"; //$NON-NLS-1$
		} else if ("float[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Float[]"; //$NON-NLS-1$
		} else if ("double[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Double[]"; //$NON-NLS-1$
		} else if ("boolean[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Boolean[]"; //$NON-NLS-1$
		} else if ("byte[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Byte[]"; //$NON-NLS-1$
		} else if ("char[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.Character[]"; //$NON-NLS-1$
		} else if ("String".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.String"; //$NON-NLS-1$
		} else if ("String[]".equals(cname)) { //$NON-NLS-1$
			cname = "java.lang.String[]"; //$NON-NLS-1$
		}
		return cname;
	}

	//拡張プリミティブ判定：Wrap型も含める。
	private static boolean isPrimitive2(CtClass c) {
		if (c.isPrimitive() || c.isArray() || "java.lang.String".equals(c.getName())) { //$NON-NLS-1$
			return true;
		} else {
			final String t_p = "^.*(Boolean|Character|Integer|Long|Float|Double|BigDecimal|BigInteger|AtomicInteger|AtomicLong)$"; //$NON-NLS-1$
			if (c.getName().matches(t_p)) {
				return true;
			}
		}
		return false;
	}

	public static boolean isPrimitive2(Class<?> c) {
		if (c.isPrimitive() || c.isArray() || "java.lang.String".equals(c.getName())) { //$NON-NLS-1$
			return true;
		} else {
			final String t_p = "^.*(Boolean|Character|Integer|Long|Float|Double|BigDecimal|BigInteger|AtomicInteger|AtomicLong)$"; //$NON-NLS-1$
			if (c.getName().matches(t_p)) {
				return true;
			}
		}
		return false;
	}

	//基本型をオブジェクトに、配列をListに変換する。
	public static Object primitive2ToObject(Object o) {
		Class<?> c = o.getClass();
		if (c.isPrimitive()) {
			if (c.equals(int.class)) {
				return (Integer)o;
			} else if (c.equals(long.class)) {
				return (Long)o;
			} else if (c.equals(float.class)) {
				return (Float)o;
			} else if (c.equals(double.class)) {
				return (Double)o;
			} else if (c.equals(boolean.class)) {
				return (Boolean)o;
			} else if (c.equals(byte.class)) {
				return (Byte)o;
			} else if (c.equals(char.class)) {
				return (Character)o;
			}
		} else if (c.isArray()) {
			ArrayList<Object> t_list = new ArrayList<Object>();
			for (int i = 0; i < Array.getLength(o); i++) {
				t_list.add(primitive2ToObject(Array.get(o, i)));
			}
			//Arrayは直接キャストできないのでArrayListで返す。
			return t_list;
		}
		return o;
	}

//////////////////////////////////////////////////////////////////
//JASON関係

	//クラスからJSONを作ろうと試みる。JSONEditorから呼ばれる。テスト実行時には無関係。
	public static String classToJSON(String cname) {
		try {
			Class<?> t_cl = forName(primitiveToFQN(cname));

			if (cname.endsWith("[]")) { //$NON-NLS-1$
				//配列の場合、中身まで見る。
				cname = cname.replaceFirst("\\[\\]", ""); //$NON-NLS-1$ //$NON-NLS-2$
				return "[" + classToJSON(cname) + "]"; //$NON-NLS-1$ //$NON-NLS-2$
			}

			CtClass t_ct = c_pool.get(cname);
			if (hasDefaultConstructor(t_ct)) {
				//デフォルトコンストラクタあり
				return Fson.toDefaultJson(t_cl);
			}
			//他のコンストラクタを見る。
			Class<?>[] clist = getParamsOfJsonableConstructor(t_cl);
			if (clist != null) {
				//Json化できるコンストラクタが見つかった!!
				String t_str = "{"; //$NON-NLS-1$
				for (int j = 0; j < clist.length; j++) {
					t_str += "\"$" + Integer.toString(j + 1) + "(" + clist[j].getName() + "): ???,\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				}
				String json = Fson.toDefaultJson(t_cl);
				if (json != null) {
					return json.replaceFirst("^\\s*\\{", Matcher.quoteReplacement(t_str)); //$NON-NLS-1$
				}
			}
		} catch (ClassNotFoundException e) {
			alertMSG(null, e.getMessage() + Messages.getString("Tool.36")); //$NON-NLS-1$
		} catch (SecurityException e) {
			alertMSG(null, e.getMessage() + Messages.getString("Tool.37")); //$NON-NLS-1$
		} catch (IllegalArgumentException e) {
			alertMSG(null, e.getMessage() + Messages.getString("Tool.38")); //$NON-NLS-1$
		} catch (NotFoundException e) {
			alertMSG(null, e.getMessage() + Messages.getString("Tool.39")); //$NON-NLS-1$
		}
		return null;
	}

	//引数が全てPOJOのコンストラクタを探す。そのパラメータの型（クラス）を返す。
	//汎用ではない。最初に見つけた引数クラスの配列のみを返す。
	private static Class<?>[] getParamsOfJsonableConstructor(Class<?> t_cl) {
		@SuppressWarnings("rawtypes")
		Constructor[] conslist = t_cl.getConstructors();
		for (int i = 0; i < conslist.length; i++) {
			Class<?>[] param_types = conslist[i].getParameterTypes();
			ArrayList<Class<?>> t_list = new ArrayList<Class<?>>();

			boolean jsonable = true;
			for (int j = 0; j < param_types.length; j++) {
				if (!hasDefaultConstructor(param_types[j].getName(), true)) {
					//このコンストラクタは使えない。
					jsonable = false;
					break;
				} else {
					t_list.add(param_types[j]);
				}
			}
			if (jsonable) {
				//見つけた!!
				return t_list.toArray(new Class<?>[t_list.size()]);
			}
		}
		return null;
	}


	//ObjectからJSONを作ろうと試みる。nullや""は出力しない。
	public static String getJSONfromObject(Object obj) {
		return getJSONfromObject(obj, null);
	}

	//上記に加えて、正規表現を指定し、マッチするフィールドのみをJson化するようにする。
	public static String getJSONfromObject(Object obj, String regs) {
		String t_result = null;
		try {
			if (obj != null) {
				t_result = Fson.toJson(obj, obj.getClass(), regs);
			}
		} catch (IllegalAccessException|ClassNotFoundException|SecurityException|NoSuchMethodException | NoSuchFieldException | NotSupportedClassException e) {
			logIfDebug(e, "@Tool#getJSONfromObject"); //$NON-NLS-1$
		}
		return t_result;
	}

	//JSONとクラスからインスタンスを生成する。(nullの扱いと、エラー処理)
	public static Object getObjectfromJSON(Class<?> cl, String json) {
		if (json == null) {
			return null;
		}
		if ("null".equals(json)) { //$NON-NLS-1$
			return null;
		}
		try {
			return Fson.fromJson(json, cl);
		} catch (Exception e) {
			String msg = "fromJson@getObjectfromJSON :" + e.getMessage(); //$NON-NLS-1$
			String str_stack = getStackMessage(e, 0, 10);
			if (str_stack.length() > 0) {
				msg = msg + "\nstacktrace=" + getStackMessage(e, 0, 10);
			}
			logForTesting(e, msg);

			//Test中ならばループから抜ける。
			if (cur_commander != null) {
				cur_commander.is_stopping = true;
				throw new RejectedExecutionException(msg);
			}
		}

		return null;
	}


	//JsonTemplateとしてオブジェクトを保存する。
	//Javaのメソッドテストのためにテンプレートを作るのにも使用される。
	public static void saveJSONfromObject(String name, Object obj, String filterReg) throws SQLException {
		//t_actをJsonTemplateに登録する。

		//元のJSONをPoolから取得しようとする。なければDBから読む。
		JSONRecord t_json = JSONRecord.getJSONRec(obj.getClass().getName(), name);
		if (t_json == null) {
			//DBにないので新規に作る。
			t_json = new JSONRecord(2);
			t_json.className = "com.ftinc.si.assist.test.web.MacroActions"; //$NON-NLS-1$

			//★★ここで、submitとcheckの差が生じる。★★
			t_json.name = name;
		}
		//Objectのfield名でマッチしたものだけ保存する。
		t_json.content = getJSONfromObject(obj, filterReg);
		t_json.changed();

		try {
			Tool._db().updateRecordBySQL(t_json.getUpdateSQL(), true);//trueはコミットする指定。

			//JSONPoolに反映する。
			JSONRecord.updatingJSONRec(t_json);
		} catch (SQLException e) {
			throw e;
		}
	}


//////////////////////////////////////////////////////////////////
//ログやメッセージ出力
	//TestCommandのAssert結果の出力
	public static synchronized void logForTesting(Throwable e, String str) {
		if (child_mode) {
			//※process_modeのときログストリームはTestMainで作る。
			s_out.print(str); //親のStatusWinに伝達する
		} else {
			if (log_stream == null) {
				try {
					//ファイルであろうとフォルダであろうと、デフォルトのログファイルを作る。
					File t_log = new File(logFolder);
					File f_log = null;
					if (t_log.isDirectory()) {
						f_log = new File(logFolder + "\\log.txt"); //$NON-NLS-1$
					} else {
						f_log = t_log;
					}
					if (!f_log.exists()) {
						f_log.createNewFile();
					}
					//真っ新のファイルに書く。
					//printStackTraceからは出力しない。
					log_stream = new PrintStream(new FileOutputStream(f_log, false));

					//logfileの先頭を書く
					log_stream.append("<?xml version=\"1.0\" ?>\n"); //$NON-NLS-1$

				} catch (FileNotFoundException e1) {
					alertMSG(null, Messages.getString("Tool.2")); //$NON-NLS-1$
				} catch (IOException e1) {
					alertMSG(null, Messages.getString("Tool.1")); //$NON-NLS-1$
				}
			}
			log_stream.append(str + Messages.getString("Tool.164") + getStackMessage(e, 0, 10) + Messages.getString("Tool.165")); //$NON-NLS-1$ //$NON-NLS-2$
			log_stream.flush();
			if (e != null) {
				e.printStackTrace();
			}

			//結果表示用画面のための処理
			//Processモードでない場合、独自のStackに貯めて、displayResultで出力する。
			if (logStack == null) {
				logStack = ""; //$NON-NLS-1$
			}
			logStack += str;
		}
	}

	//Test状況表示画面の初期化.引数はこの画面を閉じたとき、Sistem.exitするかどうか。
	public static void initStatusWin(boolean ifExit) {
		s_statuswin = new AssertStatusWindow();
		if (ifExit) {
			s_statuswin.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			s_statuswin.ifexit = true;
		}
	}


	//途中経過の表示。第二引数は強制表示の指定。
	public static void displayResult(String msg) {
		if (!child_mode) {
			//子プロセスモードでないとき、すぐに表示する。
			if (s_statuswin == null) {
				s_statuswin = new AssertStatusWindow();
			}
			if (msg != null) {
				s_statuswin.setResult(msg);//子プロセスモードの親(process_modeではない)から呼ぶ場合
			} else {
				s_statuswin.setResult(logStack);

				//役目を終えたので初期化
				logStack = null;
			}
			if (!s_statuswin.isVisible()) {
				s_statuswin.setVisible(true);
			}
		}
	}

	//コマンドラインからの実行の際に、
	public static void waitDisplayEvent() {
		if (!child_mode) {
			if (s_statuswin != null) {
				s_statuswin.subLoop();
			}
		}
	}

	//内部用出力。プロセスモードでは出力しない。
	public static void logIfDebug(Throwable e, String msg) {
		if (debug_mode) {
			//内部機能のデバッグ用。
			//プロセスモードの時、出力しない。デバッグするなら非プロセスモードですること。
			String t_msg = ""; //$NON-NLS-1$
			if (e != null) {
				t_msg += Tool.getStackMessage(e, 0, 10);
			}
			s_out.print(t_msg + msg + "\n"); //$NON-NLS-1$
		}
	}


	//普通のメッセージの出力。
	public static void alertMSG(JDialog comp, String msg) {
		if (!child_mode) {
			JTextArea t_area = new JTextArea(6, 40);
			t_area.setText(msg);
			t_area.setEditable(false);

			JScrollPane s_pane = new JScrollPane(t_area);
			JOptionPane.showMessageDialog(comp, s_pane);

			//デバグモードなら標準出力に流す。
			logIfDebug(null, msg);
		}
	}


	//普通のメッセージの出力。
	public static void alertAndStop(String msg) {
		if (!child_mode) {
			alertMSG(null, msg);
		}
		//Testのループから抜ける。
		if (cur_commander != null) {
			cur_commander.is_stopping = true;
			throw new RejectedExecutionException(msg);
		}
	}


	//メッセージを出力してからexit。但し。editmodeでnoなら継続。
	//実行モードではメッセージはログに出力。
	public static void alertAndExit(String msg, Exception e, int testid) {
		if (e == null) {
			e = new Exception();//stackを取るために作る。
		}
		String er_msg = e.getMessage();
		if (er_msg == null) {
			er_msg = ""; //$NON-NLS-1$
		}

		String t_msg = msg + e.getMessage() + "\n"; //$NON-NLS-1$
		t_msg += getStackMessage(e, 0, 10);

		//デバグモードなら標準出力に流す。
		logIfDebug(null, t_msg);

		int opt = JOptionPane.YES_OPTION;
		if (!child_mode) {
			JTextArea t_area = new JTextArea(6, 40);
			t_area.setText(t_msg);
			t_area.setEditable(false);

			JScrollPane s_pane = new JScrollPane(t_area);
			opt = JOptionPane.showConfirmDialog(null, s_pane, Messages.getString("Tool.41"), JOptionPane.YES_NO_OPTION, JOptionPane.ERROR_MESSAGE); //$NON-NLS-1$
		}
		if (opt == JOptionPane.YES_OPTION) {
			//黙って終わる。
			//ログをTestLoggerから取得し、記録する。
			TestLogger.err(t_msg + " @ "+ Integer.toString(testid) + "\n"); //$NON-NLS-1$ //$NON-NLS-2$
			Tool.logForTesting(null, TestLogger.endTestSuite());

			//プロセスモードなら親プロセスに出力る。
			displayResult(null);
			destroy();
			System.exit(testid + 1000);
		}
	}

	//ExceptionからstackTraceの文字列を作る。
	public static String getStackMessage(Throwable e, int offset, int n) {
		if (!debug_mode) {
			return ""; //$NON-NLS-1$
		}
		//デバッグモードの時、スタックを出力する。

		String t_result = ""; //$NON-NLS-1$
		if (e != null) {
			StackTraceElement[] elms = e.getStackTrace();
			String sps = ""; //$NON-NLS-1$

			for (int i = 0; i < elms.length && i < n; i++) {
				if (i >= offset) {
					t_result += sps + elms[i].toString() + "\n"; //$NON-NLS-1$
					sps += ">"; //$NON-NLS-1$
				}
			}
		}

		return t_result;
	}


	//全てのFakeクラスのbodyを更新する。
	public static boolean refleshFakes() {
		final SimpleDateFormat t_fmt = new SimpleDateFormat("yyyy/MM/dd (hh:mm:ss)"); //$NON-NLS-1$
		HashMap<String, CtClass> t_map = new HashMap<String, CtClass>();

		ArrayList<FakeMethodRecord> all_fakes = _db().getAllFakes(version);
		for (int i =0; i < all_fakes.size(); i++) {
			String m_name = all_fakes.get(i).methodName;
			String c_name = all_fakes.get(i).className;//implのクラス名
			String[] a_types = all_fakes.get(i).argTypes;
			String src = all_fakes.get(i).source;

			try {
				CtClass t_class = t_map.get(c_name);//implの取得。取得済みなら、既存のCtClassからメソッドを取得する。
				if (t_class == null) {
					ObjectRecord t_rec = new ObjectRecord(0);
					t_rec.className = c_name;

					//createNewFakeClassでFakeのガラを作る。返却値はimplである。
					t_class = (CtClass)createNewFakeClass(t_rec, false);//fake系はbodyまでできた。falseはインスタンスを作らない。
					t_map.put(c_name, t_class);
				}
				//impl系の実装
				t_class.defrost();
				CtMethod t_method = null;
				try {
					t_method = t_class.getDeclaredMethod(m_name, getCtParams(a_types));
				} catch (NotFoundException e) {
					//メソッドが未定義なら枠を作る。
					//メソッドの実体がimplにないので継承元からコピーして追加する。
					CtMethod t_orgmt = null;
					t_orgmt = getCtMethod(t_class, c_name, m_name, a_types);
					if (t_orgmt != null) {
						try {
							t_method = makeSkinOfMethod(t_class, t_orgmt, null, true, null);//宣言していないということは、protecteかprivate。継承するのでtrue。
						} catch (Exception e2) {
							alertMSG(null, e2.getMessage() + Messages.getString("Tool.42") + m_name); //$NON-NLS-1$
							throw new CannotCompileException(e2.getMessage() + " : " + m_name); //$NON-NLS-1$
						}
					}
				}
				if (t_method != null) {
					String t_src = CodeProcessor.preProcessMain(t_class, t_method, src);
					if (t_src != null) {
						t_method.setBody(t_src);

						//更新日付をアノテーションに書き込む
						writeAnnotationToMethod(t_method, "updated",t_fmt.format(new Date())); //$NON-NLS-1$
					} else {
						alertMSG(null, Messages.getString("Tool.33") + src);  //$NON-NLS-1$//$NON-NLS-2$
					}
				} else {
					alertMSG(null, c_name + "#" + m_name + Messages.getString("Tool.43"));  //$NON-NLS-1$//$NON-NLS-2$
				}
			} catch (NotFoundException e) {
				alertMSG(null, e.getMessage() + Messages.getString("Tool.44")); //$NON-NLS-1$
				return false;
			} catch (CannotCompileException e) {
				alertMSG(null, e.getMessage() + Messages.getString("Tool.45") + src); //$NON-NLS-1$
				return false;
			} catch (IOException e) {
				alertMSG(null, e.getMessage() + " IOException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (SecurityException e) {
				alertMSG(null, e.getMessage() + " SecurityException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (IllegalArgumentException e) {
				alertMSG(null, e.getMessage() + " IllegalArgumentException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (InstantiationException e) {
				alertMSG(null, e.getMessage() + " InstantiationException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (IllegalAccessException e) {
				alertMSG(null, e.getMessage() + " IllegalAccessException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (NoSuchFieldException e) {
				alertMSG(null, e.getMessage() + " NoSuchFieldException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (NoSuchMethodException e) {
				alertMSG(null, e.getMessage() + " NoSuchMethodException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			} catch (InvocationTargetException e) {
				alertMSG(null, e.getMessage() + " InvocationTargetException@Tool#refleshFakes"); //$NON-NLS-1$
				return false;
			}
		}
		//全てのimplをクラスファイルとして保存する。
		for(Entry<String, CtClass> entry : t_map.entrySet()) {
			try {
				entry.getValue().writeFile(fake_dir);
			} catch (CannotCompileException e) {
				alertMSG(null, e.getMessage() + " CannotCompileException@Tool#refleshFakes:" + entry.getKey()); //$NON-NLS-1$
				return false;
			} catch (IOException e) {
				alertMSG(null, e.getMessage() + " IOException@Tool#refleshFakes:" + entry.getKey()); //$NON-NLS-1$
				return false;
			}
		}
		return true;
	}


	//privateメンバにアクセスするにはgetDeclaredFieldであるが、Superクラスをも対象にしたいがため、再帰で処理する。
	public static Field getField(Class<?> c, String fldname) {
		if (c.getName().startsWith(Messages.getString("Tool.166")) && !fldname.equals(Messages.getString("Tool.167"))) { //$NON-NLS-1$ //$NON-NLS-2$
			//fakeなら[_impl]以外のfieldの実体はない。
			return null;
		}
		if (c.getName().startsWith(Messages.getString("Tool.168")) && !fldname.equals(Messages.getString("Tool.169"))) { //$NON-NLS-1$ //$NON-NLS-2$
			//implなら[_table]以外のfieldの実体はない。
			return null;
		}

		Field t_f = null;
		try {
			t_f = c.getDeclaredField(fldname);
			t_f.setAccessible(true);
		} catch (SecurityException e) {
			return null;
		} catch (NoSuchFieldException e) {
			Class<?> t_s = c.getSuperclass();
			if (t_s != null) {
				if (!t_s.getName().equals("java.lang.Object")) { //$NON-NLS-1$
					return getField(c.getSuperclass(), fldname);
				}
			}
		}
		return t_f;
	}

	//そのクラスに存在する全てのFieldを取得する。。
	public static void getAllFields(Class<?> c, ArrayList<Field> list) {
		Field[] t_fs = null;
		try {
			t_fs = c.getDeclaredFields();
			for (int i = 0; i < t_fs.length; i ++) {
				list.add(t_fs[i]);
			}
			if (Object.class.equals(c.getSuperclass())) {
				getAllFields(c.getSuperclass(), list);
			}
		} catch (SecurityException e) {
			return;
		}
	}


	//「_impl」のmethodに埋め込むソースで使用される便利関数。アプリから求められたインスタンスを返す。必要に応じてFakeクラスを作る。
	//①implが生成済みであれば、インスタンスを作って返す。②静的編集でクラスファイルを作る。
	//①の呼ばれるタイミングはアプリがFakeのメソッドを呼び、Implに中継した時。
	//※ vals[position()>0]については、fieldName=jsonでFakeの属性を指定できるが、現在使用している箇所はなさそう。
	public static Object getFakeInstance(String... vals) {

		Object res_obj = null;
		String t_key = vals[0];
		if (vals.length > 1) {
			//引数が二つ以上あるなら、Fakeインスタンスを二つ以上作る必要があるということ。
			//見分けるために、第二引数を使う。
			//第二引数以降の書式は、”変数名=値”。同一Fakeインスタンスが複数ある場合、第二引数の文字列をキーに追加する。
			t_key += "#" + vals[1]; //$NON-NLS-1$
		}

		final Pattern t_pat0 = Pattern.compile("^([0-9]*)\\$[0-9_]+$"); //$NON-NLS-1$
		Matcher t_m = t_pat0.matcher(t_key);
		if (t_m.find()) {
			if (t_key.startsWith("$")) { //$NON-NLS-1$
				t_key = String.valueOf(curID) + t_key;
			}
		}

		if ((res_obj = getInstance(t_key)) != null) {
			//インスタンスとして登録済みなら、それを返す。
			return res_obj;
		}

		try {
			// インスタンスを作るための引数
			ObjectRecord t_rec = new ObjectRecord(0);
			t_rec.className = vals[0];
			if (t_rec.className.endsWith(Messages.getString("Tool.170"))) { //$NON-NLS-1$
				t_rec.className = t_rec.className.substring(0, t_rec.className.length() - 1);
				t_rec.isPOJO = false;
			}
			t_rec.testCase = Tool.curCase;

			ObjectRecord t_rec2 = _db().getObjectRecord(t_rec);//JSONを得るため。
			if (t_rec2 != null) {
				t_rec = t_rec2;
			}

			//①インスタンスはないが、fakeクラスがある場合
			if (existClass("test.fake." + t_rec.className)) { //$NON-NLS-1$
				res_obj = deserializeObject(t_rec);
			} else {
				//②Fakeクラスがない場合、クラスを作ってから、Fakeインスタンスを作る。（このルートは編集モードであるべき）
				res_obj = createNewFakeClass(t_rec, true);//trueはインスタンス生成フラグ
			}

			//新規インスタンス作成にあたり、第二引数以下の情報があれば仮想メンバ変数として登録する。
			final Pattern t_pat = Pattern.compile("^(\\w+?)=(.*)$"); //$NON-NLS-1$
			for (int i = 1; i < vals.length; i++) {
				t_m = t_pat.matcher(vals[i]);
				if (t_m.find()) {
					String f_name = t_m.group(1); //$NON-NLS-1$
					String json = t_m.group(2);
					String cname = "java.lang.String"; //$NON-NLS-1$

					final Pattern t_pat2 = Pattern.compile("\\(([^\\)]+?)\\)(.+)$"); //$NON-NLS-1$
					t_m = t_pat2.matcher(json);
					if (t_m.find()) {
						cname = t_m.group(1);
						json = t_m.group(2);
					}
					//updateByJsonPathは、Fakeを考慮済み
					Fson.updateByJsonPath(res_obj, "$." + f_name, json); //$NON-NLS-1$
					Fson.updateByJsonPath(res_obj, "$." + f_name + "_class", cname); //$NON-NLS-1$ //$NON-NLS-2$
				}
			}

		} catch (IOException | CannotCompileException | SecurityException | IllegalArgumentException | ClassNotFoundException | InstantiationException | NoSuchMethodException
				| NoSuchFieldException | IllegalAccessException | InvocationTargetException | NotFoundException | ParseException | NotSupportedClassException e) {
			alertMSG(null, e.getMessage() + " @Tool#getTInstance"); //$NON-NLS-1$
		}
		if (res_obj != null) {
			//オブジェクトが生成できたら、オリジナルのクラス名をキーにインスタンスを登録する。
			addInstance(t_key, res_obj);
		}
		return res_obj;
	}


	//////////////////////////////////////////////////////////////////////
	//実行結果を見るためコマンドごとにInspectorのサブクラスを作る
	public static Inspector createInspector(int id, String src) throws CannotCompileException, InstantiationException, IllegalAccessException, ClassNotFoundException, NotFoundException, SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
		//必要なクラスを動的に作る。
		CtClass s_c = c_pool.get("com.ftinc.si.assist.test.Inspector"); //$NON-NLS-1$

		Class<?> t_c = null;
		try {
			//既にクラスがあれば、再利用する。
			t_c = forName("TestInspector_" + Integer.toString(id)); //$NON-NLS-1$
		} catch (ClassNotFoundException e) {
			//クラスが作られていないなら以下で作る。
			CtClass c_ins = c_pool.makeClass("TestInspector_" + Integer.toString(id)); //$NON-NLS-1$
			c_ins.setSuperclass(s_c);
			c_ins.setModifiers(javassist.Modifier.PUBLIC);//JavassistのModifier。この宣言がないとメソッドが見えない?

			//setBodyでキャストなど間違えるとインスタンスが作れなくなる。間違えないためコピーから作る。
			CtMethod t_orgmt = s_c.getDeclaredMethod("inspect", new CtClass[]{}); //$NON-NLS-1$
			CtMethod t_m = CtNewMethod.copy(t_orgmt, c_ins, null);
			c_ins.addMethod(t_m);

			String t_msg = Messages.getString("Tool.46"); //$NON-NLS-1$
			try {
				//プレ変換してからsetBody()
				String t_src = CodeProcessor.preProcessMain(c_ins, t_m, src);
				if (t_src != null) {
					t_m.setBody(t_src);

					t_msg = Messages.getString("Tool.47"); //$NON-NLS-1$
					t_c = toClass(c_ins);
				} else {
					alertMSG(null, Messages.getString("Tool.33") + src);  //$NON-NLS-1$//$NON-NLS-2$
				}
			} catch (CannotCompileException e1) {
				try {
				t_c = forName(c_ins.getName());
				} catch (Exception e2) {
					throw new CannotCompileException(e1.getMessage() + Messages.getString("Tool.48") + t_msg); //$NON-NLS-1$
				}
				//クラスが既存ならtoClassが失敗しても問題ない。snapshotではよくあること。
			} catch (Exception e3) {
				throw new CannotCompileException(e3.getMessage() + Messages.getString("Tool.48") + t_msg); //$NON-NLS-1$
			}
		}
		if (t_c == null) {
			throw new NotFoundException("TestInspector_" + Integer.toString(id) + Messages.getString("Tool.95")); //$NON-NLS-1$ //$NON-NLS-2$
		}

		Class<?>[] clist = new Class<?>[0];
		Object[] olist = new Object[0];

		return (Inspector)Tool.newObject(t_c, clist, olist);
	}


	//実行結果を見るためコマンドごとにInspectorのサブクラスを作る
	public static Object constructObject(String cname, String src) throws CannotCompileException, InstantiationException, IllegalAccessException, ClassNotFoundException, NotFoundException, SecurityException, IllegalArgumentException, NoSuchMethodException, InvocationTargetException {
		AnyConstructor t_const = null;
		if (instance_table.containsKey(cname + "_new")) { //$NON-NLS-1$
			t_const = (AnyConstructor)instance_table.get(cname + "_new"); //$NON-NLS-1$
			return t_const.create();
		}
		src = src.trim();

		//srcがなければ、デフォルトコンストラクタ。
		if (src == null || src.length() < 10) { //10は「return x;」という最小限の処理。
			return newObject(forName(cname), new Class<?>[]{},  new Object[]{});
		}

		//instance_tableにcnameクラスのAnyConstructorは未設定なので作る
		//必要なクラスを動的に作る。
		CtClass s_c = c_pool.get("com.ftinc.si.assist.test.AnyConstructor"); //$NON-NLS-1$
		Class<?> t_c = null;

		//クラスが作られていないなら以下で作る。
		CtClass c_ins = c_pool.makeClass("TestConstructor_" + Long.toString(new Date().getTime())); //$NON-NLS-1$
		c_ins.setSuperclass(s_c);
		c_ins.setModifiers(javassist.Modifier.PUBLIC);//JavassistのModifier。この宣言がないとメソッドが見えない?

		//setBodyでキャストなど間違えるとインスタンスが作れなくなる。間違えないためコピーから作る。
		CtMethod t_orgmt = s_c.getDeclaredMethod("create", new CtClass[]{}); //$NON-NLS-1$
		CtMethod t_m = CtNewMethod.copy(t_orgmt, c_ins, null);
		c_ins.addMethod(t_m);

		try {
			//プレ変換してからsetBody()
			String t_src = CodeProcessor.preProcessMain(c_ins, t_m, src);
			if (t_src != null) {
				t_m.setBody(t_src);

				t_c = toClass(c_ins);
			} else {
				alertMSG(null, Messages.getString("Tool.33") + src);  //$NON-NLS-1$//$NON-NLS-2$
			}
		} catch (CannotCompileException e1) {
			try {
				//既に作られている場合。
				t_c = forName(c_ins.getName());
			} catch (Exception e2) {
				throw new CannotCompileException(e1.getMessage() + "\n" + Messages.getString(Messages.getString("Tool.173"))); //$NON-NLS-1$ //$NON-NLS-2$
			}
			//クラスが既存ならtoClassが失敗しても問題ない。snapshotではよくあること。
		}
		if (t_c == null) {
			throw new NotFoundException("TestConstructor_" + Long.toString(new Date().getTime()) + Messages.getString("Tool.98")); //$NON-NLS-1$ //$NON-NLS-2$
		}

		Class<?>[] clist = new Class<?>[0];
		Object[] olist = new Object[0];

		t_const =  (AnyConstructor)Tool.newObject(t_c, clist, olist);
		instance_table.put(cname + Messages.getString("Tool.174"), t_const); //$NON-NLS-1$

		return t_const.create();
	}


	//Inspectorから呼ばれる。Reflectionの権限がToolにあるため。クラスローダによっては作れないようだ。
	public static Object getFieldValue(Object obj, String fname) throws NoSuchFieldException {
		//インスタンス自体がnullなたnullを返す。
		if (obj == null) return null;

		try {
			Class<?> t_c = obj.getClass();

			if (t_c.isArray()) {
				if ("length".equals(fname)) { //$NON-NLS-1$
					return Array.getLength(obj);
				}
				throw new NoSuchFieldException();
			}

			Field t_f = getField(t_c, fname);//Superクラスも対象にするため。Tool内の便利メソッドを使う。
			if (t_f != null) {
				//注意：fakeであっても、フィールドが存在するなら、その値を返す。

				t_f.setAccessible(true);

				Class<?> t_type = t_f.getType();

				//primitiveやFakeなら翻訳する。
				if (t_type.getName().equals("boolean")) { //$NON-NLS-1$
					return Boolean.valueOf(t_f.getBoolean(obj));
				} else if (t_type.getName().equals("int")) { //$NON-NLS-1$
					return Integer.valueOf(t_f.getInt(obj));
				} else if (t_type.getName().equals("long")) { //$NON-NLS-1$
					return Long.valueOf(t_f.getLong(obj));
				} else if (t_type.getName().equals("double")) { //$NON-NLS-1$
					return Double.valueOf(t_f.getDouble(obj));
				} else if (t_type.getName().equals("float")) { //$NON-NLS-1$
					return Float.valueOf(t_f.getFloat(obj));
				} else if (t_type.getName().equals("byte")) { //$NON-NLS-1$
					return Byte.valueOf(t_f.getByte(obj));
				} else if (t_type.getName().equals("char")) { //$NON-NLS-1$
					return Character.valueOf(t_f.getChar(obj));
				}
				return t_f.get(obj);
			} else if (isFake(t_c.getName())) {
				//Fieldが空の場合、Fakeかも知れない。
				return getFieldValueForFake(obj, fname);
			}
		} catch (SecurityException|IllegalArgumentException|IllegalAccessException e) {
			logIfDebug(e, "@Tool#getFieldValue"); //$NON-NLS-1$
		}
		throw new NoSuchFieldException();
	}


	//objはFakeの実体、fnameは仮想のメンバ変数。返却値は仮想メンバ変数のオブジェクト。
	private static Object getFieldValueForFake(Object obj, String fname) {
		try {
			Class<?> t_c = obj.getClass();
			String cname = t_c.getName();

			Field t_f2 = null;
			Object t_impl = null;
			if (cname.startsWith(Messages.getString("Tool.175"))) { //$NON-NLS-1$
				//fakeである。
				Field t_f1 = getField(t_c, "_impl");//Superクラスも対象にするため。Tool内の便利メソッドを使う。 //$NON-NLS-1$
				t_f1.setAccessible(true);

				t_impl = t_f1.get(obj);
				t_f2 = getField(t_impl.getClass(), "_table");//Superクラスも対象にするため。Tool内の便利メソッドを使う。 //$NON-NLS-1$
				t_f2.setAccessible(true);
			} else {
				//implである。Fakeの「ほぼ完全自動生成」にともないimpl内部からのアクセス処理を追加したため、ここでも処理を追加。
				t_impl = obj;
				t_f2 = getField(t_c, "_table");//Superクラスも対象にするため。Tool内の便利メソッドを使う。 //$NON-NLS-1$
			}

			@SuppressWarnings("unchecked")
			HashMap<String, Object> t_map = (HashMap<String, Object>)(t_f2.get(t_impl));
			if (!t_map.containsKey(fname)) {
				AssertRecord.setLost(t_c.getName(), fname);
				return null;//コンパイルエラーを起こす。
			}

			return t_map.get(fname);
		} catch (SecurityException|IllegalArgumentException|IllegalAccessException e) {
			logIfDebug(e, "@Tool#getInspectSourceForFake"); //$NON-NLS-1$
		}
		return null;
	}

	//classNameはFakeのクラス名、fnameは仮想のメンバ変数。返却値は仮想メンバ変数のクラス名。
	public static String getFieldTypeNameForFake(String className, String fname) {
		if (className.startsWith("test.fake.")) { //$NON-NLS-1$
			className = className.substring(10);
		}

		//第二引数はVersion。空文字は指定なし。
		ArrayList<FakeMethodRecord> fake_list = _db().getFakeMethods(className, Tool.version);

		for (int i = 0; i < fake_list.size(); i++) {
			String t_result = fake_list.get(i).getFieldType(fname);
			if (t_result != null) {
				return t_result;
			}
		}
		return null;
	}

	//指定されたFieldの値を書き替える。多くはVBAでの属性値設定に使用される。
	//objがnullなのはあり得ない。
	//※fakeの中で使われるメソッドである。
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static Object fieldValue(Object obj, String name, Object fobj) {
		if (obj == null) {
			logIfDebug(null, "1st arg==null @Tool#fieldValue"); //$NON-NLS-1$
			return null;
		} else if (name == null || name.length() == 0) {
			logIfDebug(null, "field=empty@Tool#fieldValue"); //$NON-NLS-1$
			return null;
		}
		String fname = name;
		if (fname.startsWith(Messages.getString("Tool.176"))) { //$NON-NLS-1$
			//最初に$.があれば取る。
			fname = fname.substring(2);
		}
		final Pattern t_pat = Pattern.compile(Messages.getString("Tool.177")); //$NON-NLS-1$
		Matcher t_m = t_pat.matcher(fname);
		if (t_m.find()) {
			//JsonPathの場合、末端の一歩手前のオブジェクトを取得し、再帰処理をする。
			String jpath = t_m.group(1);
			String attrName = t_m.group(2);
			ArrayList<Object> t_list = Fson.valueByJsonPath(obj, jpath);
			if (t_list != null && t_list.size() > 0) {
				return fieldValue(t_list.get(0), attrName, fobj);
			}
			logIfDebug(null, "valueByJsonPath @Tool#fieldValue"); //$NON-NLS-1$
			return null;
		}

		//インスタンス自体がnullなたnullを返す。
		if (fobj != null) {
			HashMap t_map;
			Class<?> t_c = obj.getClass();
			if (!isPrimitive2(t_c)) {
				//基本型、文字列、配列には対応しない。
				if (obj instanceof Map) {
					((Map<Object,Object>) obj).put(fname, fobj);
				} else if (t_c.getName().startsWith(Messages.getString("Tool.178"))) { //$NON-NLS-1$
					Object t_impl;
					try {
						t_impl = getFieldValue(obj, Messages.getString("Tool.179")); //$NON-NLS-1$
						t_map = (HashMap)getFieldValue(t_impl, Messages.getString("Tool.180")); //$NON-NLS-1$
						t_map.put(fname, fobj);
					} catch (NoSuchFieldException | IllegalArgumentException e) {
						logIfDebug(e, "getField&set (0)@Tool#fieldValue"); //$NON-NLS-1$
					}

				} else if (t_c.getName().startsWith(Messages.getString("Tool.181"))) { //$NON-NLS-1$

					try {
						//_tableのキーと値を更新する。
						t_map = (HashMap)getFieldValue(obj, Messages.getString("Tool.182")); //$NON-NLS-1$
						t_map.put(fname, fobj);

					} catch (NoSuchFieldException | IllegalArgumentException e) {
						logIfDebug(e, "getField&set (1)@Tool#fieldValue"); //$NON-NLS-1$
					}
				} else {
					try {
						Field t_f = getField(t_c, fname);//Superクラスも対象にするため。Tool内の便利メソッドを使う。
						if (t_f != null) {
							t_f.setAccessible(true);
							t_f.set(obj, fobj);
						}
					} catch (Exception e) {
						logIfDebug(e, "getField&set (2)@Tool#fieldValue"); //$NON-NLS-1$
					}
				}
			}
		} else {
			try {
				return getFieldValue(obj, fname);
			} catch (NoSuchFieldException e) {
				return null;
			}
		}
		return fobj;
	}


	//////////////////////////////////////////////////////////////////////
	public static boolean existClass(String cname) {   //forName衝突の影響はなさそう。
		if (getClassFilePath(cname) != null) {
			return true;
		}
		return false;
	}

	// JFileChooserで選んだ直近のPathを覚えておく。
	private static String s_prevPath = ""; //$NON-NLS-1$
	public static void setPrevPath(String curPath) {
		if (curPath != null) {
			String _sep = File.separator;
			s_prevPath = curPath.replaceAll("^([\\w" + _sep + ":\\s]+" + _sep + "\\w+) " + _sep + "[\\w\\.]+$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$
		}
	}

	//以前使用したパスを取得する。
	public static String getPrevPath() {
		return s_prevPath;
	}

	//クラスファイルを見つけて、クラス名を抽出する。
	public static String findClassName(Component parent, ArrayList<String> dirs) {
		String className = JOptionPane.showInputDialog(null, Messages.getString("Tool.55")); //$NON-NLS-1$

		if (className != null && className.length() > 0) {
			if (getClassOutLine(className) != null) {
				return className;
			} else {
				alertMSG(null, Messages.getString("Tool.49")); //$NON-NLS-1$
			}
		}
		//空文字はキャンセル
		return ""; //$NON-NLS-1$
	}


	//親のWindowを取得する。Swing用である。
	public static Window getParent(Component comp, String cname) {
		if (cname.equals("JDialog")) { //$NON-NLS-1$
			if (comp instanceof JDialog) {
				return (Window)comp;
			} else if (comp.getParent() != null) {
				return getParent(comp.getParent(), cname);
			}
		} else if (cname.equals("JFrame")) { //$NON-NLS-1$
			if (comp instanceof JFrame) {
				return (Window)comp;
			} else if (comp.getParent() != null) {
				return getParent(comp.getParent(), cname);
			}
		}
		return null;
	}

	//パスが設定されたライビラリにクラスファイルがあるかどうか調べる。
	private static String getClassFilePath(String cname) {
		try {
			CtClass t_c = c_pool.get(cname);
			return t_c.getClassFile().getSourceFile();
		} catch (NotFoundException e1) {
			return null;
		}
	}

	//指定されたクラスに指定されたメソッドがあるかどうかを返す。引数はStringの配列とする。
	public static boolean existClassMethod(String cname, String mname) {
		try {
			CtClass t_c = c_pool.get(cname);
			if (t_c.getMethod(mname, "([[Ljava/lang/String)Ljava/lang/String") != null) { //$NON-NLS-1$
				return true;
			}
			return false;
		} catch (NotFoundException e1) {
			return false;
		}
	}

	//文字列(第一引数)+最新の入力文字（第二引数）が整数であることを確認する。max>0なら範囲チェックもする。
	public static boolean checkInteger(String value, KeyEvent evt, int max, int min) {
		if (evt.getKeyCode() == KeyEvent.VK_DELETE || evt.getKeyCode() == 39 || evt.getKeyCode() == 37) {
			//削除,矢印キーはスルー
			return true;
		}

		String t_str = new String(new char[]{evt.getKeyChar()});
		if (t_str.length() > 0 && t_str.matches("^(\b|\t|\n|\r)$")) { //$NON-NLS-1$
			return true;//制御文字なのでtrue
		}
		if (value.matches("^[0-9]*$") && t_str.matches("^[0-9]$")) { //$NON-NLS-1$ //$NON-NLS-2$
			try {
				Integer t_val = Integer.valueOf(value + t_str);
				if (0 == max || (t_val <= max && min <= t_val)) {
					return true;
				}
			} catch (Exception e) {
				//何もしない。Falseを返すので。
			}
		}
		return false;
	}

	/////////////////////////////////////////
	public static Font resizeFont(MouseWheelEvent e, Font f) {
		//マウスホイールの操作により、フォントサイズを変える。
		if (e.isControlDown()) {
			int n = e.getWheelRotation();
			int h = f.getSize();
			if (h > 8 && 33 > h) {
				h += n;
				if (h == 8) {
					h = 9;
				}
				if (h == 33) {
					h = 32;
				}
			}
			return new Font(f.getFontName(), Font.PLAIN, h);
		}
		return null;
	}


	//////////////////////////////////////////////////
	//外部コマンド実行の一般化メソッド
	//第一引数は実行フォルダ
	//※送信と受信のコード系を統一しておかないと文字化けになる。UTF-8に統一する。
	public static int executeCommand(StringBuilder msg, String path, String... cmd) {
		ProcessBuilder t_pb = new ProcessBuilder(cmd);

		if (path != null && path.length() > 0) {
			String l_path = s_userdir + "\\" + s_jarname + "_lib"; //$NON-NLS-1$ //$NON-NLS-2$

			//お約束のlibの下にexeがある場合に展開する。
			String t_path = path.replaceFirst(Pattern.quote("$LIBDIR"), Matcher.quoteReplacement(l_path)); //$NON-NLS-1$

			File t_dir = new File(t_path);
			t_pb = t_pb.directory(t_dir);//①外部プログラムのあるフォルダに移動
		}
		//ftesting系コマンドかどうか確かめるために、コマンド文字列をJsonにしてパターンマッチさせる。
		String cmd_str = getJSONfromObject(cmd);

		//子プロセスのエラー出力を標準出力にマージする。
		t_pb.redirectErrorStream(true);

		String t_buf = ""; //$NON-NLS-1$
		int t_res = 999;//繰り返しの符号。これが続く（editmodeでソース定義している）限り、終わらない。
		while (t_res == 999) {
			Process t_p;

			try {
				t_p = t_pb.start();
				t_p.getOutputStream().close();//暴走抑止。このプロセスからは、これ以上送信しないので閉じる。

				BufferedReader t_input = null;
				if (cmd_str.matches("^.*java.+\\-jar.+" + s_jarname + "\\.jar.+$")) { //$NON-NLS-1$ //$NON-NLS-2$
					//同一プログラム間の通信なのでコード系はUTF-8と分かっている。
					t_input = new BufferedReader(new InputStreamReader(t_p.getInputStream(), "UTF-8")); //$NON-NLS-1$

				} else {
					//文字化けを防止するため、送受信のコード系を合わせたい。自動判別するのを期待する。
					//BufferedReaderに任せれば、きちんと文字列に変換してくれる。はずである。
					t_input = new BufferedReader(new InputStreamReader(t_p.getInputStream(), "JISAutoDetect")); //$NON-NLS-1$
				}

				String t_result = readResultFromStream(t_input, true);//プロセスが終わるまで読み込むのでtrue。
				if (t_result != null) {
					t_buf += t_result;
				}
				//終了
				t_res = t_p.exitValue();
				t_input.close();
				t_p.destroy();

			} catch (IOException e1) {
				alertMSG(null, Messages.getString("Tool.50") + cmd_str); //$NON-NLS-1$

				//例外発生なので999が返るはずがない。
				t_res = -1;
			}
		}
		if (!debug_mode) {
			//デバッグﾓｰﾄﾞでなければJUnit形式以外は出力しない。
			Pattern t_pat1 = Pattern.compile("\\A[^<]*<([\\w\\-]+)[^\\w\\-]"); //$NON-NLS-1$
			Matcher t_m = t_pat1.matcher(t_buf);
			if (t_m.find()) {
				String t_tag = t_m.group(1);
				Pattern t_pat2 = Pattern.compile("\\A[\\s\\S]*?(<" + Pattern.quote(t_tag) + "[\\s\\S]*</" + Pattern.quote(t_tag) + ">)[\\s\\S]*\\z"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				t_m = t_pat2.matcher(t_buf);
				if (t_m.find()) {
					t_buf = t_m.group(1);
				}
			}
		}
		msg.append(t_buf);
		return t_res;
	}

	//起動した外部プロセスのストリームからの文字列を待ち受ける。
	//waitEndはストリームが終了するまで読み続けるかどうかを指定する。falseなら所定の書式の完成形になったら返す。
	public static String readResultFromStream(BufferedReader buf, boolean waitEnd) {
		String t_result = null;
		String t_line = null;
		try {
			//whileを抜ける条件は、t_line==null:ストリームの終了、もしくは、結果が完成形であること。
			while ((t_line = buf.readLine()) != null) {
				if (t_result == null) {
					t_result = ""; //$NON-NLS-1$
				}
				t_result += t_line + "\n";//readLineは最後の改行コードを除去するので補充する。 //$NON-NLS-1$

				if (!waitEnd) {
					//最後まで待たないパターンは回答の書式が完成した時。

					//タグが現れるまでの情報は削除する。
					t_result = t_result.replaceFirst("^[\\s\\S]*(<\\w+>)", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

					//""をエスケープする。その段階で途中の改行はなくなる。
					ArrayList<String> esq_result = Fson.escapeBraket(t_result, "\""); //$NON-NLS-1$

					//先頭のtag名を抽出する。
					final Pattern t_pat0 = Pattern.compile("\\A[\\s\\S]*?<(\\w+)>"); //$NON-NLS-1$
					Matcher t_m = t_pat0.matcher(esq_result.get(0));
					if (t_m.find()) {
						String t_tag = t_m.group(1);
						Pattern t_pat = Pattern.compile("\\A[\\s\\S]*?(<" + Pattern.quote(t_tag) + "[\\s\\S]*</" + Pattern.quote(t_tag) + ">)[\\s\\S]*\\z", Pattern.DOTALL); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
						t_m = t_pat.matcher(esq_result.get(0));
						if (t_m.find()) {
							//前と後ろのタグ名が同じ。
							//結果の完成形(XML)なので抜ける。
							t_result = Fson.restoreBraket(t_m.group(1), esq_result);
							break;
						}
					}
				}

			}
		} catch (IOException | ParseException e) {
			e.printStackTrace();
		}
		return t_result;
	}


	//文字列に別名対象が含まれていたら、置き換える。
	//WebやDBのServerの切り替えに使う。:url,dbserver,path, TestCaseのinclude
	// 書式：　　検索用キー:置き換え用文字列
	public static String convert2Alias(String str) {
		if (str != null && str.length() > 0) {
			if (aliasNames != null) {
				for (Entry<String, String> entry: aliasNames.entrySet()) {
					//entry.getKey()の書式　　　カテゴリ:検索用正規表現
					String key = entry.getKey();
					Pattern xpat = Pattern.compile(Messages.getString("Tool.183") + key); //$NON-NLS-1$
					Matcher xm = xpat.matcher(str);
					if (xm.find()) {
						str = xm.replaceAll(Matcher.quoteReplacement(entry.getValue()));
					}
				}
			}
			if (str.startsWith("$LIBDIR")) { //$NON-NLS-1$
				str = replaceB(str, "$LIBDIR", _libdir()); //$NON-NLS-1$
			}
		}
		return str;
	}

	//大域変数への登録：キーを囲む"は除去して抽出できる正規表現
	//※末尾が$ないのは、Java1.7時点では、末尾に$文字があると、末尾判定のある正規表現が誤作動するため。
	private static Pattern s_globalpat1 = Pattern.compile("//\\?\\s*\"?([^\"\\s]+?)\"?\\s*:\\s*([^\n]+)"); //$NON-NLS-1$

	//DB更新・判定、Object判定、外部プロセス実行：キーを囲む"は除去して抽出できる正規表現
	//※末尾が$ないのは、Java1.7時点では、末尾に$文字があると、末尾判定のある正規表現が誤作動するため。
	private static Pattern s_globalpat2 = Pattern.compile("//\\?\\s*(\\w+?)\\s+\"?([^\"]+?)\"?\\s*:\\s*([^\n]+)"); //$NON-NLS-1$

	//Json文字列から大域変数の設定行を抽出してマップを作る。
	//マッチした部分の先頭が//?なら処理付の特殊コメントである。
	//書式			//?特殊コマンド　キー　：　値
	//特殊コマンド：dbadd,dbset,dbtest,objtest,exec
	//キー：①$.で始まる場合VCentralを更新するJsonPath。
	//        外部プロセス起動の場合は、boforeもしくはafter
	//        before/afterが任意数回呼ばれた場合に備え、局所変数のt_numsで番号付けして登録する。
	//      ②特殊コマンドがなく、$.で始まらない場合、ローカル引数を「JsonPath」でピンポイントで更新する。
	//        通常ならオブジェクト階層全てをJson形式で書かねばならないが、ピンポイントで指定できればより簡潔に書ける。
	//ifGlobal：true>>①のmapを作る。false>>②のmapを作る。valueはJson文字列。
	public static HashMap<String, Object> getCommentMapFromJson(boolean ifGlobal, String json) {
		HashMap<String, Integer> t_nums = new HashMap<String, Integer>();
		HashMap<String, Object> t_list = new HashMap<String, Object>();

		ArrayList<String> t_esqList = null;
		try {
			t_esqList = Fson.escapeBraket(json, "\"'"); //「"」の中だけエスケープ。ObjectRecordの復元の際に、.jSONの内部のコメントや文字列中の改行に過剰反応するため。 //$NON-NLS-1$
		} catch (ParseException e) {
			e.printStackTrace();
			return t_list;
		}

		Integer n;
		String t_src = t_esqList.get(0);
		String[] t_strs = t_src.split("\n"); //$NON-NLS-1$

		for (int i = 0; i < t_strs.length; i++) {
			Matcher t_m = s_globalpat1.matcher(t_strs[i]);
			if (t_m.find()) {
				String key = Fson.restoreBraket(t_m.group(1), t_esqList);

				if (ifGlobal) {
					if (key.startsWith("$.")) { //$NON-NLS-1$
						String value = Fson.restoreBraket(t_m.group(2), t_esqList);
						Object val = getObjectfromJSON(null, value);
						t_list.put(key, val);
					} else if ("$".equals(key)) { //$NON-NLS-1$
						//VCentralにマップそのものを入れる場合、"$";マップのJson
						String value = Fson.restoreBraket(t_m.group(2), t_esqList);
						@SuppressWarnings("unchecked")
						HashMap<String, Object> _gmap = (HashMap<String, Object>)getObjectfromJSON(HashMap.class, value);
						t_list.putAll(_gmap);
					}
				} else {
					if (!key.startsWith("$.") && !key.equals("$")) { //$NON-NLS-1$ //$NON-NLS-2$
						//$.から始まっていないので、大域書式ではない。JsonPathと仮定する。
						String value = Fson.restoreBraket(t_m.group(2), t_esqList);
						t_list.put(key, value);
					}
				}
			} else if (ifGlobal) {
				//stackとは無関係にコマンド実行:　　コマンド（s_globalpat2）は大域的書式である。
				t_m = s_globalpat2.matcher(t_strs[i]);
				if (t_m.find()) {
					String cmd = Fson.restoreBraket(t_m.group(1), t_esqList);
					String key = Fson.restoreBraket(t_m.group(2), t_esqList);  //外部コマンドの場合、実行時には使用しない値。
					String value = Fson.restoreBraket(t_m.group(3), t_esqList);
					Object val = getObjectfromJSON(null, value);
					if (key.matches("^(after|before)$") ){ //$NON-NLS-1$
						if (t_nums.containsKey(key)) {
							n = t_nums.get(key);
							n++;
							t_nums.put(key, n);
						} else {
							n = 0;
							t_nums.put(key, n);
							t_list.put(key + "_" + n.toString(), val); //$NON-NLS-1$
						}
						t_list.put(cmd + " " + key + "_" + n.toString(), val); //$NON-NLS-1$ //$NON-NLS-2$
					} else {
						t_list.put(cmd + " " + key, val); //$NON-NLS-1$
					}
				}
			}
		}
		return t_list;
	}

	//Json中のコメントを除去する。
	private static String removeCommentFromJson(String str) {
		if (str != null) {
			return str.replaceAll("^//[^\n]+$", ""); //$NON-NLS-1$ //$NON-NLS-2$
		}
		return "null"; //$NON-NLS-1$
	}


	//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
	//例外のメッセージにインデントを付ける。
	public static String makeIndent(Throwable e) {
		if (e != null) {
			String t_str = e.getMessage();
			if (t_str != null) {
				if (t_str.startsWith("\t\t")) { //$NON-NLS-1$
					t_str = t_str.replaceAll("\n", Matcher.quoteReplacement("\n   ")); //$NON-NLS-1$ //$NON-NLS-2$
					return "   " + t_str.substring(2); //$NON-NLS-1$
				}
				return t_str;
			}
		}
		return ""; //$NON-NLS-1$
	}

	//既にメッセージとして出ているSeleniumメッセージを削除する。evaluateExceptionで呼ばれる。
	public static String removeRedundancy(Throwable e, String str) {
		String t_str = makeIndent(e) + str;
		t_str = t_str.replaceFirst("Build info: version:[\\s\\S]*Driver info: driver.version: unknown.", ""); //$NON-NLS-1$ //$NON-NLS-2$
		return t_str;
	}

	//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
	//file名から禁止文字を「_」に置き換える。
	public static String repairFileName(String fname) {
		if (fname != null) {
			fname = fname.replaceAll("\\s", "_"); //tab,lfはファイル名に使ってはいけないらしいので_に置き換える。（実験結果による） //$NON-NLS-1$ //$NON-NLS-2$
			String osName = System.getProperty("os.name"); //$NON-NLS-1$
			if ("Linux".equals(osName)) { //$NON-NLS-1$
				String t_result = fname;
				String newC = "_"; //$NON-NLS-1$
				t_result = t_result.replace('/', newC.charAt(0));
				return t_result;
			} else {
				//Windows系
				return fname.replaceAll("(\\\\|/|:|\\*|\\?|\"|<|>)", "_"); //$NON-NLS-1$ //$NON-NLS-2$
			}
		}
		return null;
	}

	//正規表現を用いない場合の置換
	public static String replaceB(String str, String target, String replaced) {
		int offset = 0;
		StringBuilder s_b = new StringBuilder(str.length());
		int idx;
		while ((idx = str.indexOf(target, offset)) >= 0) {
			s_b.append(str, offset, idx);
			s_b.append(replaced);
			offset = idx + target.length();
		}
		s_b.append(str, offset, str.length());
		return s_b.toString();
	}

	//正規表現を用いない場合の置換
	public static String regReplaceAll(String str, String regStr, String replaced) {
		StringBuilder s_b = new StringBuilder(str.length());
		int idx_start = 0;
		int idx_end = 0;

		Pattern t_pat = Pattern.compile(regStr);
		Matcher t_m = t_pat.matcher(str);
		while (t_m.find()) {
			String w_str = t_m.group();
			String w_str2 = w_str.replaceFirst(regStr, replaced);
			idx_end = t_m.start();
			s_b.append(str, idx_start, idx_end);
			s_b.append(w_str2);
			idx_start = t_m.end();
		}
		s_b.append(str, idx_start, str.length());
		return s_b.toString();
	}


	//コマンドオプションのエスケープを行う。但し、タブ、改行は""で囲んでもうまくいかないので特殊エスケープする。
	public static String esq4CMD(String option) {
		if (option != null) {
			String t_str = option;
			//""で囲まれていたら抜き出す。
			t_str = t_str.replaceFirst("\\A\"(.*?)\"\\z", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

			//内部の\をエスケープする。
			t_str = replaceB(t_str, "\\", "\\\\"); //$NON-NLS-1$ //$NON-NLS-2$

			//内部の"をエスケープする。
			t_str = replaceB(t_str, "\"", "\\\""); //$NON-NLS-1$ //$NON-NLS-2$

			//tabの置換
			t_str = replaceB(t_str, "\t", "\\t"); //$NON-NLS-1$ //$NON-NLS-2$

			//改行の置換
			t_str = replaceB(t_str, "\n", "\\n"); //$NON-NLS-1$ //$NON-NLS-2$

			//"で囲う。
			return "\"" + t_str + "\""; //$NON-NLS-1$ //$NON-NLS-2$
		}
		return null;
	}

	//上記のエスケープ処理を元に戻す。
	public static String rest4CMD(String option) {
		if (option != null) {
			String t_str = option;
			//""で囲まれていたら抜き出す。
			t_str = t_str.replaceFirst("\\A\"(.*?)\"\\z", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

			//tabの置換
			t_str = replaceB(t_str, "\\\\", "_BQ%BQ_"); //$NON-NLS-1$ //$NON-NLS-2$

			//tabの置換
			t_str = replaceB(t_str, "\\t", "\t"); //$NON-NLS-1$ //$NON-NLS-2$

			//改行の置換
			t_str = replaceB(t_str, "\\n", "\n"); //$NON-NLS-1$ //$NON-NLS-2$

			//内部の"を回復する。
			t_str = replaceB(t_str, "\\\"", "\""); //$NON-NLS-1$ //$NON-NLS-2$

			//内部の\を回復する。
			t_str = replaceB(t_str, "_BQ%BQ_", "\\"); //$NON-NLS-1$ //$NON-NLS-2$

			return t_str;
		}
		return null;
	}


	//strの中の、kfromIndexの直後のbegintagから、edntagまでの文字列を返す。
	//①閉じタグを検索し、開始タグと閉じタグの数を比較する。
	//②同じ数なら完全とみなし、その区間を切り取って返す。
	//③等しくなければ、次の閉じタグまでの文字列を検索する。
	//※beginTagは\tの後に正規表現を追加できる。beginTag+正規表現できちんと括弧を合わせることができる。
	public static String getClosedBlock(String str, int fromIndex, String beginTag, String endTag) {
		String x_str = str.toLowerCase();

		String tail = ""; //$NON-NLS-1$
		String[] temp = beginTag.split("\t"); //$NON-NLS-1$
		if (temp.length == 2) {
			tail = temp[1]; //正規表現でbeginTagの後に続くパターン
			beginTag = temp[0];
		}
		int n_pos = 0;

		if ((n_pos = x_str.indexOf(beginTag, fromIndex)) >= fromIndex) {
			int x_pos = n_pos; //切り出すXMLの開始位置
			int etag_count = 0;
			int i_pos  = x_str.indexOf(endTag, x_pos + 1);

			while (i_pos >= 0) {
				etag_count++;
				String c_str = x_str.substring(n_pos, i_pos);

				if (c_str.endsWith("\\")) { //$NON-NLS-1$
					//endTagの直前がエスケープ処理なのでもう一回行う。
					etag_count--;
				} else {
					//endTag込みで閉じた文字列を作り、タグの数を数える。
					c_str += endTag;

					final Pattern t_pat = Pattern.compile("(?<=[^\\\\])" + Pattern.quote(beginTag) + tail); //$NON-NLS-1$
					Matcher t_m = t_pat.matcher(c_str);

					int btag_count = 1;
					while (t_m.find()) {
						btag_count++;
					}
					if (etag_count == btag_count) {
						return str.substring(n_pos, i_pos + endTag.length());
					}
				}
				i_pos = x_str.indexOf(endTag, i_pos + endTag.length());
				while (i_pos >= 0 && '\\' == x_str.charAt(i_pos - 1)) {
					i_pos = x_str.indexOf(endTag, i_pos + endTag.length());
				}
			}
		}
		return null;
	}

	//XMLタグがきちんと対をなして続いている範囲を返す。
	public static String getClosedRange(String str) {
		String t_result = str;

		Pattern t_pat = Pattern.compile("^([^<]*)<(/?)([\\w\\-]+)(\\s[^>]*>|/?>)([\\s\\S]*)$"); //$NON-NLS-1$
		Matcher t_m = t_pat.matcher(str);
		if (!t_m.find()) {
			//①次にタグがない。
		} else if (t_m.group(2).equals("/")) { //$NON-NLS-1$
			//②最初に閉じタグなので、はぐれ閉じタグである。対になっているとしたら、getClosedBlockが使われているはず。ここで終了する。
			t_result = t_m.group(1);
		} else if (t_m.group(4).endsWith("/>")) { //$NON-NLS-1$
			//③閉じタグ不要のタグの場合、取り込む。
			t_result = t_m.group(1) + "<" + t_m.group(3) + t_m.group(4) + getClosedRange(t_m.group(5)); //$NON-NLS-1$
		} else {
			String t_block = getClosedBlock("<" + t_m.group(3) + t_m.group(4) + t_m.group(5), 0,"<" + t_m.group(3) + "\t(\\s|>)", "</" + t_m.group(3)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
			if (t_block != null && t_block.length() > 0) {
				t_result = t_m.group(1) + t_block + getClosedRange(str.substring(t_m.end(1)) + t_block.length());
			} else {
				t_result = t_m.group(1);
			}
		}
		return t_result;
	}


	//////////////////////////////////////////////////////////////
	//CreateTable文のリストを作る
	public static ArrayList<String> createTableSQL() {
		ArrayList<String> t_list = new ArrayList<String>();

		//固定名ファイル渡しでcreatetable文を受け取る。なければ、固定のSQLを使う。
		File t_f = _libfile("$LIBDIR\\createtable.sql"); //$NON-NLS-1$
		if (t_f.exists()) {
			String buf;
			try {
				BufferedReader t_br = new BufferedReader(new InputStreamReader(new FileInputStream(t_f),Tool.charset));
				while((buf = t_br.readLine()) != null){
					t_list.add(buf);
				}
				t_br.close();
			} catch (IOException e1) {
				Tool.logForTesting(null, e1.getMessage());
			}
		}
		return t_list;
	}

	//ファイル書き込み：書き込める状態(最大1分)まで待つ。書き込み成功ならtrue、失敗ならfalseを返す。
	public static boolean writeAllLines(String path, String[] xstrs) {
		int i = 0;
		while (i < 600) {
			if (Files.isWritable(Paths.get(path))) {
				FileWriter xfw;
				try {
					xfw = new FileWriter(new File(path), false);
					for(int j = 0; j < xstrs.length; j++) {
						xfw.write(xstrs[j] + "\n");
					}
					xfw.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
				return true;
			} else {
				try {
					//0.１秒待つ
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				i++;
			}
		}
		return false;
	}
}
