package com.ftinc.si.assist.test.gui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import com.ftinc.si.assist.run.Messages;
import com.ftinc.si.assist.test.AssertRecord;
import com.ftinc.si.assist.test.ObjectRecord;
import com.ftinc.si.assist.test.Record;
import com.ftinc.si.assist.test.TestCaseRecord;
import com.ftinc.si.assist.test.TestCommandRecord;
import com.ftinc.si.assist.test.Tool;
import com.ftinc.si.assist.test.gui.plugins.DefaultPlugin;


//ここではDBアクセスはしない。あくまでもメモリ上のオブジェクトの編集である。
//DB更新は親画面で行う。
public class TestCaseEditor extends JDialog {

	protected HashMap<Integer, TestCommandRecord> m_cmds;	//key=testID
	protected HashMap<String, ObjectRecord> obj_table;		//key=testid+$n
	protected HashMap<Integer, AssertRecord> m_assert;
	protected ArrayList<Record> m_removings;

	private String m_desc;
	private int m_begin;
	private int m_end;
	private int m_nextid = 0;
	protected String m_include;
	private boolean m_loggable;

	private TestCmdNode m_root;
	private boolean m_isOK = false;

	private boolean hiddenMode;
	private DefaultTreeModel m_model;
	private JTree tree;
	protected TestCmdNode m_snapshot = null;
	private static String s_snap_sign = "【◎P】"; //$NON-NLS-1$

	public static boolean unableMode = false;

	//tcaseはtestcase, description, begin, endの配列。
	public TestCaseEditor(JFrame owner, TestCaseRecord tcase, boolean hMode) {
		super(owner, true); // modalにする。
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				//リセット。
				ClassReadDialog.s_owner = null;
				ArgumentEditor.s_owner = null;
				JSONEditor.s_owner = null;
				MethodEditor.s_owner = null;
			}
		});
		//編集可能かどうかを決める。
		String t_target = tcase.groupCode;
		if (t_target == null) {
			t_target = ""; //$NON-NLS-1$
		}
		String t_group = Tool.group_code;
		if (t_group != null && t_group.length() > 0) {
			//グループコードが決められているなら管理者ではない。
			//同じグループコードのデータのみ編集できる。
			unableMode = !t_group.equals(t_target);
		} else {
			unableMode = false;
		}

		//後でPluginでモーダルにする際に使う。
		ClassReadDialog.s_owner = this;
		ArgumentEditor.s_owner = this;
		JSONEditor.s_owner = this;
		MethodEditor.s_owner = this;

		setResizable(false);

		if (t_target.length() > 0) {
			t_target = "    edited by GroupCode=" + t_target; //$NON-NLS-1$
		} else {
			t_target = "    edited by GroupCode=NULL"; //$NON-NLS-1$
		}

		setTitle(Messages.getString("TestCaseEditor.0") + t_target); //$NON-NLS-1$
		m_cmds = new HashMap<Integer, TestCommandRecord>();
		obj_table = new HashMap<String, ObjectRecord>();
		m_assert = new HashMap<Integer, AssertRecord>();
		m_removings = new ArrayList<Record>();

		hiddenMode = hMode;
		m_desc = tcase.description;
		m_begin = Integer.valueOf(tcase.begin);
		m_end = Integer.valueOf(tcase.end);
		m_loggable = Boolean.valueOf(tcase.loggable);
		m_include = tcase.include;

		getContentPane().setLayout(null);
		setBounds(owner.getX() + 100, owner.getY() + 100, 800, 500);

		//Treeの初期化
		m_root = makeTreeNodes(tcase);
		m_model = new DefaultTreeModel(m_root);

		JScrollPane scrollPane = new JScrollPane();
		scrollPane.setBounds(0, 0, 784, 415);
		getContentPane().add(scrollPane);

		tree = new JTree(m_model);
		scrollPane.setViewportView(tree);
		tree.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				//右クリックならポップアップを開く。
				if(SwingUtilities.isRightMouseButton(e) && !unableMode){
					//非編集モードではポップアップは出さない。参照はダブルクリックで行う。
					TreePath path = tree.getPathForLocation ( e.getX (), e.getY () );
					if (path != null) {
						//ノードの上
						TestCmdNode targetNode = (TestCmdNode)(path.getLastPathComponent());

						if (targetNode.getNodeName().matches("^TestCase=.*$")) { //$NON-NLS-1$
							goTestCasePopUp(targetNode, e);
						} else if (targetNode.getNodeName().matches("^/*TestID=.*$")) { //$NON-NLS-1$
							goTestIDPopUp(targetNode, e);
						} else if (targetNode.getNodeName().startsWith("$")) { //$NON-NLS-1$
							goObjectPopUp(targetNode, e);
						} else if (targetNode.getNodeName().matches("^null$|^Assert=.*$")) { //$NON-NLS-1$
							goAssertPopUp(targetNode, e);
						}
					} else {
						//空白の上
						goTestCasePopUp(m_root, e);
					}
				} else if (e.getClickCount() == 2) {
					//ダブルクリックの場合
					TreePath path = tree.getPathForLocation ( e.getX (), e.getY () );
					if (path != null) {
						TestCmdNode targetNode = (TestCmdNode)(path.getLastPathComponent());

						TestCommandRecord t_c = m_cmds.get(targetNode.getID());
						TestCaseRecord t_case = new TestCaseRecord(0);
						t_case.name = targetNode.getCaseName();
						t_case.include = m_include;

						if (targetNode.getNodeName().matches("^\\$[0-9]+.*$")) { //$NON-NLS-1$
							String n_name = targetNode.getNodeName().replaceFirst("^(\\$[0-9_]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

							//編集処理したがっているpluginがあるかどうか探す。
							Plugin t_p = null;
							int m = 0;
							for (int k = 0; k < Tool.plugins.size(); k++) {
								Plugin t_plug = Tool.plugins.get(k);
								int n = t_plug.getPriority(t_case, t_c, n_name);
								if (n > m) {
									t_p = t_plug;

									//最大のプライオリティを主張するプラグインを選ぶ。
									m = n;
								}
							}
							if (t_p != null) { //$NON-NLS-1$
								//pluginが処理できるなら、編集を任せる。
								editArgByPlugin(targetNode, t_p, null);
							}
						} else if (targetNode.getNodeName().matches("^null$|^Assert=.*$")) { //$NON-NLS-1$
							//編集処理したがっているpluginがあるかどうか探す。
							Plugin t_p = null;
							int m = 0;
							for (int k = 0; k < Tool.plugins.size(); k++) {
								Plugin t_plug = Tool.plugins.get(k);
								int n = t_plug.getPriority(t_case, t_c, null);
								if (n > m) {
									t_p = t_plug;

									//最大のプライオリティを主張するプラグインを選ぶ。
									m = n;
								}
							}
							if (t_p != null) { //$NON-NLS-1$
								//pluginが処理できるなら、編集を任せる。
								editAssertByPlugin(targetNode, t_p, null);
							}
						}
					}
				}
			}
		});
		tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);

		final JButton btnSave = new JButton(Messages.getString("TestCaseEditor.5")); //$NON-NLS-1$
		btnSave.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (!btnSave.isEnabled()) {
					return;
				}
				m_isOK = true;
				setNextID();
				setVisible(false);
			}

		});
		btnSave.setBounds(281, 425, 91, 21);
		getContentPane().add(btnSave);

		//GroupCodeが異なるものは保存できない。（管理権限除く）
		btnSave.setEnabled(!unableMode);

		JButton btnClose = new JButton(Messages.getString("TestCaseEditor.6")); //$NON-NLS-1$
		btnClose.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				m_cmds.clear();
				obj_table.clear();
				m_assert.clear();
				unableMode = false;//元に戻す。
				setVisible(false);
			}
		});
		btnClose.setBounds(414, 425, 91, 21);
		getContentPane().add(btnClose);

		JButton btnIncompletelist = new JButton(Messages.getString("TestCaseEditor.7")); //$NON-NLS-1$
		btnIncompletelist.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
			}
		});

		//完成したノードを閉じる。
		btnIncompletelist.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {

				for (int i = 0; i < m_root.getChildCount(); i++) {
					TestCmdNode t_node = (TestCmdNode)m_root.getChildAt(i);

					TestCommandRecord t_cmd = m_cmds.get(t_node.getID());
					if (!t_cmd.isComplete()) {
						Object[] t_path = new Object[3];
						t_path[0] = m_root;
						t_path[1] = t_node;
						t_path[2] = t_node.getChildAt(0);
						tree.expandPath(new TreePath(t_path));
					} else {
						Object[] t_path = new Object[2];
						t_path[0] = m_root;
						t_path[1] = t_node;
						tree.collapsePath(new TreePath(t_path));
					}
				}
			}
		});
		btnIncompletelist.setBounds(610, 425, 162, 21);
		getContentPane().add(btnIncompletelist);
		m_model.reload();

		setNextID();
	}

	//最初に画面の樹木構造を構成する。
	private TestCmdNode makeTreeNodes(TestCaseRecord tcase) {
		String t_include = " include NONE"; //$NON-NLS-1$
		Integer n_snapshot = 0;

		if (tcase.snapshot > 0) {
			n_snapshot = tcase.snapshot;
		}

		if (tcase.include != null && tcase.include.length() > 0) {
			t_include = " include " + tcase.include; //$NON-NLS-1$
		}
		t_include += ", loggable " + Boolean.toString(tcase.loggable); //$NON-NLS-1$

		TestCmdNode t_root = new TestCmdNode("TestCase=" + tcase.name + " :" + Integer.toString(tcase.begin) + "-" + Integer.toString(tcase.end) + t_include, 0, tcase.name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$

		ArrayList<TestCommandRecord> tcmds = Tool._db().getTestCmdList(tcase.name, null, null, Tool.version);
		if (tcmds == null) return t_root;

		for (int i = 0; tcmds.size() > i; i++) {
			if (hiddenMode && tcmds.get(i).enable == false) {
				// 【重要】隠しモードの場合、enableでないレコードはスキップする。
				continue;
			}

			TestCommandRecord t_cmd = new TestCommandRecord(0);
			t_cmd.testCase = tcmds.get(i).testCase;
			t_cmd.id = tcmds.get(i).id;
			t_cmd.className = tcmds.get(i).className;
			t_cmd.methodName = tcmds.get(i).methodName;
			t_cmd.returnType = tcmds.get(i).returnType;
			t_cmd.subject = tcmds.get(i).subject;
			t_cmd.argTypes = tcmds.get(i).argTypes;
			t_cmd.enable = tcmds.get(i).enable;
			t_cmd.argStatus = tcmds.get(i).argStatus;

			String snap_sign = ""; //$NON-NLS-1$
			if (t_cmd.id == n_snapshot) {
				snap_sign = s_snap_sign;
			}

			TestCmdNode test = new TestCmdNode("TestID=" + Integer.valueOf(t_cmd.id).toString() + snap_sign, t_cmd.id, tcase.name); //$NON-NLS-1$
			t_root.add(test);

			if (t_cmd.id == n_snapshot) {
				m_snapshot = test;
			}

			TestCmdNode cnameNode = new TestCmdNode(t_cmd.className + " # " + t_cmd.methodName, t_cmd.id, tcase.name); //$NON-NLS-1$
			test.add(cnameNode);

			//$_を追加。(voidでも仕様変更の可能性あり)
			TestCmdNode ret = new TestCmdNode("$_=" + tcmds.get(i).returnType, t_cmd.id, tcase.name); //$NON-NLS-1$
			cnameNode.add(ret);

			//各引数の完成度を表現するカラムがないとき、セットする。
			Boolean[] t_status;
			if (t_cmd.argStatus == null || t_cmd.argStatus.equals("null") || t_cmd.argStatus.length() == 0) { //$NON-NLS-1$
				t_status = new Boolean[t_cmd.numArgs() + 2];//$0, $1,...,Assert(last)
				if (t_cmd.methodName.startsWith("static")) { //$NON-NLS-1$
					t_status[0] = true;//staticの場合、最初から完成とみる。
				} else {
					t_status[0] = false;
				}
				for (int k = 1; k < t_status.length; k++) {
					t_status[k] = false;
				}
				t_cmd.argStatus = Tool.getJSONfromObject(t_status);
			} else {
				t_status = (Boolean[])Tool.getObjectfromJSON(Boolean[].class, t_cmd.argStatus);
			}

			String a_status;
			ObjectRecord t_obj;
			//$0の入れ物を生成。staticかコンストラクタならスルーする。
			if (!t_cmd.methodName.startsWith("static") || (t_cmd.methodName.equals(t_cmd.className))) { //$NON-NLS-1$
				if (t_status[0]) {
					a_status = "(done)"; //$NON-NLS-1$
				} else {
					a_status = ""; //$NON-NLS-1$
				}

				TestCmdNode subj = new TestCmdNode("$0=" + tcmds.get(i).className + " " + tcmds.get(i).subject + a_status, t_cmd.id, tcase.name); //$NON-NLS-1$ //$NON-NLS-2$
				cnameNode.add(subj);

				//-1は仮のObjectRecord。DBを読み込んだ訳ではない。
				t_obj = new ObjectRecord(-1, tcase.name, t_cmd.id, 0, t_cmd.className, "$0");//基本だけ作っておく。新規かどうかはまだ不明なので、0を使う。 //$NON-NLS-1$
				t_obj.completed = t_status[0];
				obj_table.put(Integer.valueOf(t_cmd.id).toString() + t_obj.stack, t_obj);
			}


			//$1から始まる。
			for (int j = 1; t_cmd.numArgs() + 1 > j; j++) {
				//-1は仮のObjectRecord。DBを読み込んだ訳ではない。
				t_obj = new ObjectRecord(-1, tcase.name, t_cmd.id, j, t_cmd.getArgClass(j - 1), t_cmd.getArgStack(j - 1));//$1はstr_args[0]
				t_obj.completed = t_status[j];

				obj_table.put(t_obj.id, t_obj);

				if (t_status[j]) {
					a_status = "(done)"; //$NON-NLS-1$
				} else {
					a_status = ""; //$NON-NLS-1$
				}

				TestCmdNode t_node = new TestCmdNode("$" + Integer.toString(j) + "=" + t_obj.className + " " + t_obj.stack + a_status, t_cmd.id, tcase.name); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				cnameNode.add(t_node);
			}

			m_cmds.put(Integer.valueOf(t_cmd.id), t_cmd);
			test.add(new TestCmdNode("Assert=" + Integer.valueOf(t_cmd.id).toString(), t_cmd.id, tcase.name)); //$NON-NLS-1$

		}
		return t_root;
	}


	private void goTestCasePopUp(TestCmdNode targetNode, MouseEvent e) {
		JPopupMenu t_pop = new JPopupMenu();
		TestCaseRecord t_case = new TestCaseRecord(0);
		t_case.name = targetNode.getCaseName();
		t_case.include = m_include;
		if (m_snapshot != null) {
			//snapshotが同じTestCaseにある場合の処理。(メモリ上で設定されている可能性のため。include中にある場合、pluginの中でDBから情報を取得する)
			t_case.snapshot = m_snapshot.getID();

			ArrayList<ObjectRecord> o_list = new ArrayList<ObjectRecord>();
			ObjectRecord o_rec;
			Integer i_arg = 1;
			while ((o_rec = obj_table.get(Integer.toString(t_case.snapshot) + "$" + i_arg.toString())) != null) { //$NON-NLS-1$
				if (!o_rec.isUpdated()) {
					o_list.add(Tool._db().getObjectRecord(o_rec));
				} else {
					o_list.add(o_rec);
				}
				i_arg++;
			}
			t_case.snapshot_Args = o_list.toArray(new ObjectRecord[o_list.size()]);
			t_case.snapshot_Assert = m_assert.get(t_case.snapshot);
			if (t_case.snapshot_Assert == null) {
				//Assertはメモリ上にない場合がある。
				t_case.snapshot_Assert = Tool._db().getAssert(t_case.snapshot);
			}
		}

		/*
		 * TestCaseEditor.25=テストケース結合
		 * TestCaseEditor.27=ケース名変更
		 * TestCaseEditor.28=ケース名変更
		 * TestCaseEditor.30=説明する0
		 * TestCaseEditor.33=ログ無効化
		 * TestCaseEditor.38=ログ有効化
		 */
		ArrayList<JMenuItem> p_list = new ArrayList<JMenuItem>();
		//pluginを考慮
		PluginAction.s_curNode = targetNode;
		PluginAction.s_this = this;

		TestCommandRecord t_rec = null;//サンプルとして先頭のTestCommandをプラグインに渡す。
		if (m_cmds.size() > 0) {
			t_rec = m_cmds.get(m_begin);
		}

		int m = 1;
		//n==1のものを登録するのが基本だが、１より大きい優越性を主張するプラグインがあれば、最優先のものだけをメニューアイテムのリストに加える。
		for (int k = 0; k < Tool.plugins.size(); k++) {
			Plugin t_plug = Tool.plugins.get(k);
			int n = t_plug.getPriority(t_case, t_rec, null);
			if (n >= m) {
				if (n == m) {
					//pluginによるコマンド追加
					p_list.add(t_plug.getMenuItem("addCommand")); //$NON-NLS-1$
				} else {
					//priorityが2以上の場合、他のアイテムを削除する。
					p_list.clear();

					//pluginによるコマンド追加
					p_list.add(t_plug.getMenuItem("addCommand")); //$NON-NLS-1$
					m = n;
				}
			}
		}

		if (targetNode.getNodeName().startsWith("TestCase=NEW")) { //$NON-NLS-1$
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.27"), this, targetNode))); //$NON-NLS-1$
		} else {
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.28"), this, targetNode))); //$NON-NLS-1$
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.25"), this, targetNode))); //$NON-NLS-1$

			//無効なら有効化、有効なら無効化
			if (m_loggable) {
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.33"), this, targetNode)));  //$NON-NLS-1$
			} else {
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.38"), this, targetNode))); //$NON-NLS-1$
			}

			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.30"), this, targetNode))); //$NON-NLS-1$

			//pluginを考慮
			PluginAction.s_curNode = targetNode;
			PluginAction.s_this = this;

			for (int k = 0; k < p_list.size(); k++) {
				//pluginによるコマンド追加
				t_pop.add(p_list.get(k));
			}
		}
		t_pop.show(e.getComponent(), e.getX(), e.getY());
	}


	//Caseの名前を変える。
	private void renTestCase(TestCmdNode curNode) {
		//まず、現在のTestCaseがincludeされているかを調べる。
		String f_name = curNode.getCaseName();
		ArrayList<String> w_msg = Tool._db().getReferringTestCaseList(f_name, Tool.version);
		if (w_msg != null && w_msg.size() > 0) {
			Tool.alertMSG(null, "ALERT!!:  " + w_msg + Messages.getString("TestCaseEditor.34")); //$NON-NLS-1$ //$NON-NLS-2$
		}
		String usname = null;
		Integer begin = null, end = null;
		if ("NEW".equals(curNode.getCaseName()) && m_cmds.size() == 0) { //$NON-NLS-1$
			//名前を付けると同時に範囲も変えられる。
			String t_data = JOptionPane.showInputDialog(this, Messages.getString("TestCaseEditor.50"), f_name + " " + Integer.toString(m_begin) + " " + Integer.toString(m_end)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

			Pattern t_pat = Pattern.compile("^([\\w\\$]+)\\s+([0-9]+)\\s+([0-9]+)$"); //$NON-NLS-1$
			Matcher t_m = t_pat.matcher(t_data);
			if (t_m.find()) {//書式をチェックし、適合していれば、以下で範囲チェックする。
				usname = t_m.group(1);
				begin = Integer.valueOf(t_m.group(2));
				end = Integer.valueOf(t_m.group(3));

				if (Tool.group_code.length() > 0) {
					//GroupCodeが指定されているので範囲がある。
					Integer t_max = Integer.valueOf(Tool.group_code);
					if (end > (t_max + 1) * 10000000 || t_max * 10000000 > begin) { //１グループの区画10000000．
						//Groupに割り当てられた範囲を逸脱している。
						Tool.alertMSG(null, Messages.getString("TestCaseEditor.52")); //$NON-NLS-1$
						return;
					}
				}

				if (Tool._db().getTestCaseList("%", begin, end, Tool.group_code, Tool.version).size() > 0) { //$NON-NLS-1$
					//衝突しているので、そのまま返る。
					Tool.alertMSG(null, Messages.getString("TestCaseEditor.52")); //$NON-NLS-1$
					return;
				}
				//無事にチェックを通過。
			} else {
				//usname == nullなので、以降、無害に終わる。
			}
		} else {
			//NEW以外は、名前のみ変える。
			usname = JOptionPane.showInputDialog(this, Messages.getString("TestCaseEditor.50"), f_name); //$NON-NLS-1$
		}

		if (usname != null && !f_name.equals(usname) && usname.length() > 0) {
			ArrayList<TestCaseRecord> t_list = Tool._db().getTestCaseList(usname, 0, 0, Tool.group_code, Tool.version);
			if (t_list.size() > 0) {
				//名前が既存の場合、メッセージを出す。
				Tool.alertMSG(null, usname + Messages.getString("TestCaseEditor.35")); //$NON-NLS-1$
			} else {
				String old_name = curNode.getNodeName();
				String n_name = old_name.replaceFirst(Pattern.quote(f_name), Matcher.quoteReplacement(usname));
				curNode.setNodeName(n_name);

				curNode.setCaseName(usname); // 枝葉も含めてCase名を変える。

				if (begin != null) {//領域変更が成功している場合、範囲を設定しなおす。
					m_begin = begin;
					m_end = end;
				}

				for(Entry<Integer, TestCommandRecord> entry1 : m_cmds.entrySet()) {
					entry1.getValue().testCase = usname;
					entry1.getValue().changed();
				}

				HashMap<String, ObjectRecord> t_objRecs = new HashMap<String, ObjectRecord>();
				for(Entry<String, ObjectRecord> entry2 : obj_table.entrySet()) {
					ObjectRecord t_rec = entry2.getValue();

					//中身が仮ならばDBから読み込む。
					if (t_rec.isUnread()) {
						t_rec = Tool._db().getObjectRecord(t_rec);
					}
					t_rec.testCase = usname;
					t_rec.changed();

					t_objRecs.put(entry2.getKey(), t_rec);
				}

				obj_table = t_objRecs;
				m_model.reload(curNode);
			}
		} else if (usname != null && usname.length() == 0) {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.10")); //$NON-NLS-1$
		}
	}

	//Caseの概要を記述する。
	private void describeTestCase(TestCmdNode curNode) {
		String desc = JOptionPane.showInputDialog(this, Messages.getString("TestCaseEditor.51"), m_desc); //$NON-NLS-1$
		if (desc != null) {
			if (!desc.equals(m_desc)) {
				String t_desc = "\"\""; //$NON-NLS-1$
				if (desc.length() > 0) {
					t_desc = "\".+\""; //$NON-NLS-1$
				}

				String t_str = curNode.getNodeName();
				t_str = t_str.replaceAll("^(.*\\{).*(\\}.*)$", "$1" + t_desc + "$2"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
				curNode.setNodeName(t_str);
				m_desc = desc;
				m_model.reload(curNode);
			}
		}
	}

	//自分より若いTestCaseをincludeする。これは、caseの実行時の最初に実行される。
	private void includeTestCase(TestCmdNode curNode) {
		String str = JOptionPane.showInputDialog(this, Messages.getString("TestCaseEditor.26"), m_include); //$NON-NLS-1$
		if (str != null) {
			if (!str.equals(m_include)) {
				//自分より若いことを確認する。
				ArrayList<TestCaseRecord> t_list = Tool._db().getTestCaseList(str, 0, m_begin, Tool.group_code, Tool.version);

				String t_str = curNode.getNodeName();

				String t_loggable = ", loggable " + Boolean.toString(m_loggable); //$NON-NLS-1$
				if (t_list.size() > 0) {
					t_str = t_str.replaceFirst("^(.*?)include.*$", "$1" + "include " + t_list.get(0).include + t_loggable); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					curNode.setNodeName(t_str);
					m_include = t_list.get(0).include;
					m_model.reload();

				} else if (str.length() == 0){
					t_str = t_str.replaceFirst("^(.*?)include.*$", "$1" + "include NONE" + t_loggable); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

					curNode.setNodeName(t_str);
					m_include = "NONE"; //$NON-NLS-1$
					m_model.reload(curNode);
				} else {
					Tool.alertMSG(null, Messages.getString("TestCaseEditor.32")); //$NON-NLS-1$
				}
			}
		}
	}

	//TestCaseにログ有効を設定する。：Assertでログを出力する。
	public void loggableTestCase(TestCmdNode curNode) {
		m_loggable = true;

		String t_str = curNode.getNodeName();
		t_str = t_str.replaceFirst("^(.*, loggable ).*$", "$1true"); //$NON-NLS-1$ //$NON-NLS-2$
		curNode.setNodeName(t_str);
		m_model.reload(curNode);
	}

	//TestCaseにログ無効を設定する。：Assertでログを出力しないことが可能になる。includeされているTestCaseなのでログを出力たくない場合など。
	public void unLoggableTestCase(TestCmdNode curNode) {
		m_loggable = false;

		String t_str = curNode.getNodeName();
		t_str = t_str.replaceFirst("^(.*, loggable ).*$", "$1false"); //$NON-NLS-1$ //$NON-NLS-2$
		curNode.setNodeName(t_str);
		m_model.reload(curNode);
	}


	//★★★★★★PLUGIN＃AddCommand
	//コマンドとして、プラグインから新しいコマンドを作る。
	private void addCommandByPlugin(TestCmdNode curNode, Plugin plug, String prevObj) {
		String tcname = curNode.getCaseName();

		if (m_end > m_nextid) {
			TestCommandRecord t_cmd = new TestCommandRecord(2);
			t_cmd.id = m_nextid;
			t_cmd.testCase = tcname;

			//★★★★★
			int n = plug.initCommand(t_cmd);//nは$0,$1..の数
			if (n == -1) {
				//エラー
				Tool.alertMSG(null, Messages.getString("TestCaseEditor.37")); //$NON-NLS-1$
				return;
			} else if (n < 0) {
				return;//-1未満は普通にキャンセル。
			}

			t_cmd.enable = true;
			m_cmds.put(Integer.valueOf(t_cmd.id), t_cmd);
			setNextID();

			TestCmdNode test = new TestCmdNode("TestID=" + Integer.valueOf(t_cmd.id).toString(), t_cmd.id, tcname); //$NON-NLS-1$
			m_root.add(test);
			TestCmdNode cnameNode = new TestCmdNode(t_cmd.className + " # " + t_cmd.methodName, t_cmd.id, tcname); //$NON-NLS-1$
			test.add(cnameNode);

			//$_のノードを表示する。
			TestCmdNode _node = new TestCmdNode("$_=" + t_cmd.returnType, t_cmd.id, tcname); //$NON-NLS-1$
			cnameNode.add(_node);

			//各引数の完成度を表現するカラムがないので、セットする。
			Boolean[] t_status = new Boolean[n + 1];//Assertがあるのでn+1
			for (int k = 0; k < t_status.length; k++) {
				t_status[k] = false;
			}
			if (t_cmd.methodName.startsWith("static")) { //$NON-NLS-1$
				t_status[0] = true;//staticの場合、最初から完成とみる。
			}
			if (!plug.hasAssert()) {
				//Assertが不要なので。
				t_status[n] = true;
			}
			t_cmd.argStatus = Tool.getJSONfromObject(t_status);


			// $0も含めて、枠を作る。
			ObjectRecord[] t_args = new ObjectRecord[n];
			for (int k = 0; k < n; k++) {//n+1は$0が最初に来るため。
				if (k > 0 || plug.hasSubject()) {
					t_args[k] = new ObjectRecord(2);
					if (k == 0) {
						t_cmd.subject = "$0";//subjectを持つのでTestCommandRecordに記入 //$NON-NLS-1$
					}
				}
			}

			//★★★★★
			//引数情報をセットする。
			plug.initArguments(t_args);

			//引数情報
			String str_args = "["; //$NON-NLS-1$
			for (int j = 0; j < n; j++) {
				if (j > 0 || plug.hasSubject()) {
					t_args[j].testCase = tcname;
					t_args[j].testID = t_cmd.id;
					if (t_args[j].stack == null) {
						t_args[j].stack = "$" + Integer.toString(j); //$NON-NLS-1$
					}
					t_args[j].id = Integer.toString(t_cmd.id) + "$" + Integer.toString(j); //$NON-NLS-1$

					obj_table.put(t_args[j].id, t_args[j]);

					//completed判断
					checkCompleted(t_cmd, t_args[j], null);//Assertと同時のチェックは不可。

					TestCmdNode t_node = null;
					if (prevObj != null && j == 0) {
						t_args[j].stack = prevObj;
						t_cmd.subject = prevObj;
						t_node = new TestCmdNode("$" + Integer.toString(j) + "=" + t_args[j].className + " " + prevObj + "(done)", t_cmd.id, tcname); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
					} else {
						t_node = new TestCmdNode("$" + Integer.toString(j) + "=" + t_args[j].className, t_cmd.id, tcname); //$NON-NLS-1$ //$NON-NLS-2$
					}
					//nodeを追加
					cnameNode.add(t_node);

					// ArgTypeの書式はJSON
					if (j > 0) {
						if (str_args.length() > 1) {
							str_args += ",";//二番目以降をつなぐための接続部分 //$NON-NLS-1$
						}
						str_args += "[" + t_args[j].className + "," + t_args[j].stack + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				}
			}
			//argTypesをセット
			t_cmd.setArgTypes(str_args + "]"); //$NON-NLS-1$


			//AssertNodeを追加
			TestCmdNode t_nodeA = new TestCmdNode("Assert=" + Integer.toString(t_cmd.id), t_cmd.id, tcname); //$NON-NLS-1$
			test.add(t_nodeA); //$NON-NLS-1$

			//Assertを構成する。例外がなければ成功である。
			AssertRecord t_ast = new AssertRecord(2);

			t_ast.id = t_cmd.id;
			t_ast.criteriaType = "Exception"; //$NON-NLS-1$
			t_ast.targetObj = ""; //$NON-NLS-1$
			t_ast.criteriaRex = "$null"; //$NON-NLS-1$

			//★★★★★
			//Assert情報をセットする。
			plug.initAssert(t_ast);
			m_assert.put(t_cmd.id, t_ast);

			//completed判断
			checkCompleted(t_cmd, null, t_ast);//Assertと同時のチェックは不可。


			t_nodeA.setNodeName("Assert=" + t_cmd.id + " & " +t_ast.criteriaType + " & " + t_ast.criteriaRex); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

			m_model.reload(curNode);
		} else {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.1")); //$NON-NLS-1$
		}
	}

	//★★★★★★PLUGIN＃AddArg
	//可変長引数をノードに追加する。
	private void addArgByPlugin(TestCmdNode curNode, Plugin plug, String opt) {
		TestCmdNode parent = null;
		if (curNode.getNodeName().startsWith("$")) { //$NON-NLS-1$
			//引数ノード上でイベント発生
			parent = (TestCmdNode)curNode.getParent();
		} else {
			parent = curNode;
		}

		TestCommandRecord cmd = m_cmds.get(curNode.getID());
		String num = Integer.toString(cmd.numArgs() + 1);//新しい引数の位置

		//新しく作る場合、既存（画面から消去されたまま残っている）がある場合も考慮する。
		String t_id = Integer.toString(cmd.id) + "$" + num; //$NON-NLS-1$

		ObjectRecord t_obj = obj_table.get(t_id);
		if (t_obj == null) {
			// $nを新しく構成する!!!!
			t_obj = new ObjectRecord(2);
			t_obj.testCase = cmd.testCase;
			t_obj.testID = cmd.id;
			t_obj.id = Integer.toString(cmd.id) + "$" + num; //$NON-NLS-1$
			t_obj.stack = "$" + num; //$NON-NLS-1$

			obj_table.put(t_obj.id, t_obj);
		} else {
			//削除取消
			t_obj.removed = false;
			t_obj.changed();
		}
		try {
			plug.initArgument(t_obj, opt);
		} catch (Exception e) {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.31") + e.getMessage() + "\n" + Tool.getStackMessage(e, 0, 10)); //$NON-NLS-1$ //$NON-NLS-2$
			return;
		}

		TestCmdNode t_node = new TestCmdNode("$" + num.toString() + "=" + t_obj.className, cmd.id, cmd.testCase); //$NON-NLS-1$ //$NON-NLS-2$
		parent.add(t_node);

		//argTypeの追加
		cmd.addArg(t_obj.className, t_obj.stack);

		//argStatusの追加:あらかじめ、$0は完成、Asertは未完とセット済。
		Boolean[] blist = (Boolean[]) Tool.getObjectfromJSON(Boolean[].class, cmd.argStatus);

		int n = Integer.valueOf(num);
		Boolean[] nlist = new Boolean[blist.length + 1];
		for (int i = 0; i < nlist.length; i++) {
			if (i < n) {
				nlist[i] = blist[i];
			} else if (i == n) {
				nlist[i] = false;
			} else {
				nlist[i] = blist[i - 1];
			}
		}
		cmd.argStatus = Tool.getJSONfromObject(nlist);
	}

	//★★★★★★PLUGIN＃EditArg
	//引数をプラグインで編集する。
	private void editArgByPlugin(TestCmdNode curNode, Plugin plug, String opt) {
		//ObjectRecordからJSONを取得し、FakeMethodRecordにする。要はJSONの編集である。
		String aName = curNode.getNodeName().replaceAll("^(\\$[0-9_]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

		String t_key = Integer.toString(curNode.getID()) + aName;	//obj_tableのキーを作る。 //$NON-NLS-1$
		ObjectRecord t_rec = obj_table.get(t_key);  //このエディタが持っている辞書からオブジェクトを取得する。

		if ("$_".equals(aName)) {
			//$_はクラス名、つまり返却値の型のみを変更することができる。テスト対象プログラムの仕様変更で返却値を変える場合もあるが、Jsonなどは再利用したいだろう。
			t_rec = new ObjectRecord(0);
			t_rec.testID = curNode.getID();
			t_rec.testCase = curNode.getCaseName();
			t_rec.id = t_key;
			t_rec.stack = aName;
		} else {
			obj_table.put(t_key, t_rec);
		}
		String cname = t_rec.className;

		//仮であれば、DBから最新のObjectRecordを取得する。初期状態で-1（未読）を入れたことが、ここで意味を持つ。
		//$_はUnreadではない。statusが0なので。
		if (t_rec.isUnread()) {
			t_rec = Tool._db().getObjectRecord(t_rec);

			//DBにない場合は、作る。但し、再利用の場合もnullであることに注意。
			if (t_rec == null) {
				//新規の場合
				t_rec = new ObjectRecord(2);
				t_rec.testID = curNode.getID();
				t_rec.stack = aName;
				t_rec.testCase = curNode.getCaseName();
				t_rec.id = t_key;

				//引数にはint,boolean..がありうるがクラスではない。それに変換できるクラスに置き換える。
				t_rec.className = Tool.primitiveToFQN(cname);

				if (Tool.getClassOutLine(t_rec.className).isInterface()) {
					t_rec.isPOJO = false;
				}
			}
		} else {
			//既読なら編集用にコピーする。
			t_rec = t_rec._dup(true);
		}

		String o_stack = t_rec.stack;

		TestCommandRecord t_cmd = m_cmds.get(t_rec.testID);
		String num = aName.replaceFirst("\\$", ""); //$NON-NLS-1$ //$NON-NLS-2$
		Integer n = null;
		if ("_".equals(num)) {
			t_rec.className = t_cmd.returnType;
		} else {
			n = Integer.valueOf(num);
			if (n == 0) {
				t_rec.typeName = t_cmd.className;
			} else {
				t_rec.typeName = t_cmd.getArgClass(Integer.valueOf(num) - 1);
			}
		}

		//Assertもpluginによっては判定条件の自動生成などで編集する可能性があるので、実体化する。
		AssertRecord t_ast = m_assert.get(t_rec.testID);
		if (t_ast == null) {
			t_ast = Tool._db().getAssert(t_rec.testID);
			if (t_ast == null) {
				t_ast = new AssertRecord(2);
				t_ast.id = Integer.valueOf(t_rec.testID);
				plug.initAssert(t_ast);
			}
			m_assert.put(t_rec.testID, t_ast);
		}

		//★★★★★pluginによる編集
		try {
			plug.editArgument(t_rec, t_ast, opt);//optはサブメニューのキー（あれば）
		} catch (Exception e) {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.36") + e.getMessage() + "\n" + Tool.getStackMessage(e, 0, 10)); //$NON-NLS-1$ //$NON-NLS-2$
			return;
		}

		//とにかく、更新されていれば、更新する。
		if (t_rec.isUpdated()) {
			//TestCommandRecordに影響が及ぶ箇所を更新する。
			TestCommandRecord t_c = m_cmds.get(t_rec.testID);

			if (n != null) {
				//$_以外の場合
				String n_stack = t_rec.stack;

				obj_table.put(t_key, t_rec);

				if (!o_stack.equals(n_stack)) {
					//stackに変更有
					if (aName.equals("$0")) { //$NON-NLS-1$
						t_c.subject = t_rec.stack;
					} else {
						Integer pos = Integer.valueOf(aName.replace("$", "")); //$NON-NLS-1$ //$NON-NLS-2$
						t_c.setArgType(pos - 1, t_rec.className, t_rec.stack);//$1は引数の順番としては0番目だからi-1
					}
				}

				//完成度管理
				String a_status = ""; //$NON-NLS-1$
				Boolean[] t_status = (Boolean[])Tool.getObjectfromJSON(Boolean[].class, t_c.argStatus);
				if (t_rec.isCompleted()) {
					t_status[n] = true;
					a_status = "(done)"; //$NON-NLS-1$
				} else {
					t_status[n] = false;
				}
				t_c.argStatus = Tool.getJSONfromObject(t_status);
				curNode.setNodeName(aName + "=" + t_rec.className + Messages.getString("TestCaseEditor.53") + aName + a_status); //$NON-NLS-1$ //$NON-NLS-2$
			} else {
				//$_の場合
				t_c.returnType = t_rec.className;
				curNode.setNodeName(aName + "=" + t_rec.className); //$NON-NLS-1$
			}
			t_c.changed();
			m_model.reload(curNode);

			//completed判断
			checkCompleted(t_c, t_rec, null);//Assertと同時のチェックは不可。
			checkCompleted(t_c, null, t_ast);//Assertと同時のチェックは不可。
		}
	}


	//TestID上のポップアップメニューを表示する。
	private void goTestIDPopUp(TestCmdNode targetNode, MouseEvent e) {
		JPopupMenu t_pop = new JPopupMenu();

		/*
		 * TestCaseEditor.13=テストID変更
		 * TestCaseEditor.55=コマンド再利用
		 * TestCaseEditor.56=コマンド削除
		 * TestCaseEditor.57=コマンド無効化
		 * TestCaseEditor.8=コマンド有効化
		 * TestCaseEditor.39=スナップショット設定
		 */
		if (m_snapshot == targetNode) {
			//reset snapshot：スナップショットの場合には解除以外のメニュー項目は表示しない。
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.17"), this, targetNode)));  //$NON-NLS-1$
		} else {
			if (targetNode.getNodeName().startsWith("//")) {	//disabled //$NON-NLS-1$
				//enable
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.8"), this, targetNode))); //$NON-NLS-1$
			} else {	//enabled
				//copy&add
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.55"), this, targetNode))); //$NON-NLS-1$
				//disable
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.57"), this, targetNode))); //$NON-NLS-1$
				//renum
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.13"), this, targetNode))); //$NON-NLS-1$

				if (targetNode != m_snapshot) {
					//snapshot.同じノードに設定済みの場合はメニューを出さない。
					t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.39"), this, targetNode))); //$NON-NLS-1$
				}
			}

			//remove
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.56"), this, targetNode))); //$NON-NLS-1$
		}

		t_pop.show(e.getComponent(), e.getX(), e.getY());
	}

	//以前のTestCommandを元に新規に作る。
	public void copyAddTest(TestCmdNode curNode) {
		if (m_end > m_nextid) {
			copyPasteCmd(curNode, m_nextid);
			setNextID();
		} else {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.2")); //$NON-NLS-1$
		}
	}



	//TestCmdNode配下のオブジェクトをコピーし、指定したIDで再構築する。
	//単なるコピーなので、内部で既存オプジェクトを参照しても気にしない。
	private void copyPasteCmd(TestCmdNode curNode, int newID) {
		String tcname = curNode.getCaseName();

		TestCmdNode n_test = new TestCmdNode("TestID=" + Integer.toString(newID), newID, tcname); //$NON-NLS-1$
		int n = m_root.getChildCount();
		boolean b_added = false;
		for (int j = 0; j < n; j ++) {
			TestCmdNode t_child = (TestCmdNode)m_root.getChildAt(j);
			if (t_child.getID() > newID) {
				//newIDより大きなIDの前に差し込む
				m_root.insert(n_test, j);
				b_added = true;
				break;
			}
		}
		if (!b_added) {
			m_root.add(n_test);
		}

		//TestCommandの複製、IDの付け替え。
		m_cmds.put(Integer.valueOf(newID), m_cmds.get(curNode.getID())._dup(newID));

		TestCmdNode o_class = (TestCmdNode)curNode.getChildAt(0);
		TestCmdNode n_class = new TestCmdNode(o_class.getNodeName(), newID, tcname);
		n_test.add(n_class);

		TestCmdNode n_assert = new TestCmdNode("Assert=" + Integer.toString(newID), newID, tcname); //$NON-NLS-1$
		n_test.add(n_assert);

		AssertRecord o_arec = m_assert.get(curNode.getID());
		AssertRecord n_arec = null;
		if (o_arec != null) {
			n_arec = (AssertRecord) o_arec._dup(false);//falseはstatusを新規として作る。
			n_arec.id = newID;
		} else {
			//o_arecがnullなので、DBを検索し、なければ新しく作る。
			n_arec = Tool._db().getAssert(curNode.getID());
			if (n_arec == null) {
				n_arec = new AssertRecord(2);
			}
			n_arec.id = newID;
		}
		m_assert.put(newID, n_arec);

		for (int i = 0; o_class.getChildCount() > i; i++) {
			TestCmdNode o_arg = (TestCmdNode)o_class.getChildAt(i);

			String nodeName = o_arg.getNodeName();

			//コピー元Nodeによるtestid$n
			String t_stack = nodeName.replaceFirst("^(\\$\\w+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
			String t_key =Integer.toString(o_arg.getID()) + t_stack;

			ObjectRecord t_rec = null;
			ObjectRecord n_rec = null;
			if ((t_rec = obj_table.get(t_key)) != null) {
				//nullでないなら、それを流用。但し、新規のキーを合成する。

				if (!t_rec.isUpdated()) {
					ObjectRecord temp = new ObjectRecord(0);
					temp.stack = t_stack;
					temp.id = Integer.toString(curNode.getID()) + t_stack;

					//編集していないなら、空の可能性があるため、念のためDBから取る。
					n_rec = Tool._db().getObjectRecord(temp);
				}
				if (n_rec != null) {
					n_rec = n_rec._dup(false);//複製を作ってNEWフラグを立てる。falseは新規という意味。
					n_rec.testID = newID;
					n_rec.id = newID + t_stack;
				}

				t_key = Integer.toString(newID) + t_stack;//$n=のnは新旧で同じ。
				obj_table.put(t_key, n_rec);
			}

			TestCmdNode n_arg = new TestCmdNode(nodeName, newID, tcname);
			n_class.add(n_arg);
		}
		m_model.reload();

	}


	//TestCommandを除去する。（了解ボタンで除去する）第二引数は警告が必要かどうか。
	private void removeTest(TestCmdNode curNode, boolean bAlert) {
		int t_res = JOptionPane.YES_OPTION;
		if (bAlert) {
			ArrayList<String> t_warning = Tool._db().getReferringTestCmdList(curNode.getCaseName(), Integer.toString(curNode.getID())); //$NON-NLS-1$
			String msg = Messages.getString("TestCaseEditor.16"); //$NON-NLS-1$
			if (t_warning != null && t_warning.size() > 0) {
				msg = t_warning + Messages.getString("TestCaseEditor.66"); //$NON-NLS-1$
			}
			t_res = JOptionPane.showConfirmDialog(this, msg + curNode.getNodeName() + "?"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		if(t_res == JOptionPane.YES_OPTION) {
			TestCmdNode parent = (TestCmdNode)curNode.getParent();
			parent.remove(curNode);

			Integer i = curNode.getID();
			m_cmds.get(i).removed = true;//コマンドは無効にして除外する。
			m_cmds.get(i).changed();

			//削除バッファに移す
			m_removings.add(m_cmds.get(i));
			m_cmds.remove(i);

			//ObjectRecord, AssertRecordも管理外にする。
			@SuppressWarnings("unchecked")
			HashMap<String, ObjectRecord> temp = (HashMap<String, ObjectRecord>)obj_table.clone();
			for(Entry<String, ObjectRecord> entry : temp.entrySet()) {
				if (entry.getValue().testID == i) {
					obj_table.get(entry.getKey()).removed = true;
					obj_table.get(entry.getKey()).changed();

					//削除バッファに移す
					m_removings.add(obj_table.get(entry.getKey()));
					obj_table.remove(entry.getKey());
				}
			}
			AssertRecord t_ast = null;
			if (m_assert.get(i) != null) {
				m_assert.get(i).removed = true;
				m_assert.get(i).changed();
				m_assert.remove(i);
			} else {
				t_ast = Tool._db().getAssert(i);
				if (t_ast != null) {
					t_ast.removed = true;
					t_ast.changed();
				}
			}
			//削除バッファに移す
			m_removings.add(t_ast);

			m_model.reload();
		}
	}

	//snapshotを設定する。
	public void setSnapshot(TestCmdNode curNode) {
		TestCommandRecord t_rec = m_cmds.get(curNode.getID());

		//不活性なテストコマンドを再生しても意味がないため除外する。未完成なコマンドも除外する。
		//デフォルトコンストラクタおよびDBにコンストラクタの引数が登録されていない場合も除外する。
		if (t_rec.enable && Tool.hasDefaultConstructor(t_rec.className, true) && t_rec.isComplete()) {
			String t_name = null;
			if (m_snapshot != null) {
				//既存のスナップショットがある場合、それを参照しているコマンドを確認し、解除の警告を行う。
				Integer n_observers = countSnapshotObserver();
				if (n_observers > 0) {
					int t_res = JOptionPane.showConfirmDialog(this, n_observers.toString() + Messages.getString("TestCaseEditor.54")); //$NON-NLS-1$
					if(t_res == JOptionPane.NO_OPTION) {
						return;
					}
				}
				//解除するノード名を元に戻す。
				t_name = m_snapshot.getNodeName();
				t_name = t_name.replaceFirst(Pattern.quote(s_snap_sign), ""); //$NON-NLS-1$
				m_snapshot.setNodeName(t_name);
			}

			t_name = curNode.getNodeName();
			t_name = t_name + s_snap_sign;//書き出しは「TestID」にしないとポップアップしなくなる。
			curNode.setNodeName(t_name);

			m_snapshot = curNode;
			m_model.reload();
		} else {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.42")); //$NON-NLS-1$
		}
	}

	//現在のsnapshotを見ているTestCommandRecordの数をカウントする。
	private Integer countSnapshotObserver() {
		//既存のスナップショットを再生するコマンドのうち、DBに登録済みの数をカウントする。
		ArrayList<TestCommandRecord> t_cmds1 = Tool._db().getTestCmdList(null, null, "$replay(" + Integer.toString(m_snapshot.getID()) + ")", Tool.version); //$NON-NLS-1$ //$NON-NLS-2$

		//既存のスナップショットを再生するコマンドのうち、編集中のTestCaseで、DBに登録済みの再生コマンド数をカウントする。
		ArrayList<TestCommandRecord> t_cmds2 = Tool._db().getTestCmdList(m_root.getCaseName(), null, "$replay(" + Integer.toString(m_snapshot.getID()) + ")", Tool.version); //$NON-NLS-1$ //$NON-NLS-2$

		int t_count = 0;//編集中のテストケースにsnapshotがある場合をカウントする。
		for(Entry<Integer, TestCommandRecord> entry : m_cmds.entrySet()) {
			if (entry.getValue().getMethodName().equals("$replay(" + Integer.toString(m_snapshot.getID()) + ")")) { //$NON-NLS-1$ //$NON-NLS-2$
				t_count++;
			}
		}
		return t_count + t_cmds1.size() - t_cmds2.size();
	}


	//snapshotを解除する。
	public void resetSnapshot(TestCmdNode curNode) {
		if (m_snapshot == curNode) {
			Integer n_observers = countSnapshotObserver();
			if (n_observers > 0) {

				int t_res = JOptionPane.showConfirmDialog(this, n_observers.toString() + Messages.getString("TestCaseEditor.67")); //$NON-NLS-1$
				if(t_res == JOptionPane.NO_OPTION) {
					return;
				}
			}

			String t_name = m_snapshot.getNodeName();
			t_name = t_name.replaceFirst(Pattern.quote(s_snap_sign), ""); //$NON-NLS-1$
			m_snapshot.setNodeName(t_name);

			m_snapshot = null;

			m_model.reload(curNode);
		}
	}


	//このTestCommandRecordのenableを逆転する。
	private void disableTest(TestCmdNode curNode) {
		if (m_snapshot.equals(curNode)) {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.43")); //$NON-NLS-1$
		} else {
			TestCommandRecord t_c = m_cmds.get(curNode.getID());

			t_c.setEnable(!t_c.getEnable());
			t_c.changed();

			//Nodeの先頭に//を入れる。
			curNode.setNodeName("//" + curNode.getNodeName()); //$NON-NLS-1$

			if (hiddenMode && !t_c.getEnable()) {
				TestCmdNode parent = (TestCmdNode)curNode.getParent();
				parent.remove(curNode);
				m_model.reload();
			}
		}
	}

	public void enableTest(TestCmdNode curNode) {
		TestCommandRecord t_c = m_cmds.get(curNode.getID());

		t_c.setEnable(!t_c.getEnable());
		t_c.changed();

		//Nodeの先頭の//を取る。
		curNode.setNodeName(curNode.getNodeName().substring(2));
	}


	//TestIDの振り直し。
	//hiddenmode, Caseの範囲外、番号が既存の場合はエラー。
	private void renumberTest(TestCmdNode curNode) {
		if (hiddenMode) {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.19")); //$NON-NLS-1$
			return;
		}

		//変更前の番号
		Integer oldID = curNode.getID();

		ArrayList<String> t_warning = Tool._db().getReferringObjectList(curNode.getCaseName(), oldID.toString() + "$0"); //$NON-NLS-1$
		if (t_warning != null && t_warning.size() > 0) {
			int t_res = JOptionPane.showConfirmDialog(this, t_warning +Messages.getString("TestCaseEditor.70") + curNode.getNodeName() + "?"); //$NON-NLS-1$ //$NON-NLS-2$
			if(t_res == JOptionPane.CANCEL_OPTION) {
				return;
			}
		}

		ArrayList<String> w_msg = Tool._db().getReferringTestCmdList(curNode.getCaseName(), oldID.toString());
		if (w_msg != null && w_msg.size() > 0) {
			Tool.alertMSG(null, "ALERT!!:  " + w_msg + Messages.getString("TestCaseEditor.77")); //$NON-NLS-1$ //$NON-NLS-2$
		}

		//変更後の番号
		String newID = JOptionPane.showInputDialog(this, Messages.getString("TestCaseEditor.20"), oldID.toString()); //$NON-NLS-1$
		if (newID != null && newID.matches("[0-9]+")) { //$NON-NLS-1$
			Integer nID = Integer.valueOf(newID);

			if (!newID.matches("^[0-9]+$")) { //$NON-NLS-1$
				//番号でない場合
				Tool.alertMSG(null, newID + Messages.getString("TestCaseEditor.22")); //$NON-NLS-1$
				return;
			}
			Integer i = Integer.valueOf(newID);

			if (i < m_begin || i > m_end) {
				//テストケースの範囲を逸脱している場合
				Tool.alertMSG(null, newID + Messages.getString("TestCaseEditor.23")); //$NON-NLS-1$
				return;
			}

			TestCommandRecord t_c = m_cmds.get(i);
			if (t_c != null) {
				Tool.alertMSG(null, newID + Messages.getString("TestCaseEditor.24")); //$NON-NLS-1$
				return;
			}


			//①ｔID以下のオブジェクトを、新IDにコピー。旧オブジェクトは消去。
			//②ｔID以下のオブジェクトを参照しているObjectRecordのstackを新参照先に変更。
			//まず①
			copyPasteCmd(curNode, i);
			if (i >= m_nextid) {
				setNextID();
			}
			removeTest(curNode, false);

			Pattern t_pat = Pattern.compile("([0-9]+)\\$"); //$NON-NLS-1$

			//②
			for(Map.Entry<String, ObjectRecord> entry : obj_table.entrySet()) {
				ObjectRecord t_rec = entry.getValue();

				if (!t_rec.removed) {
					//削除されていないことが前提
					int oid = t_rec.testID;
					if (oldID == oid) {
						t_rec.testID = nID;
					} else if (oldID < oid) {
						//t_recが新しい場合

						Matcher t_m = t_pat.matcher(t_rec.stack);
						if (t_m.find()) {
							String num = t_m.group(1);
							if (num.equals(oldID.toString())) {
								t_rec.stack = t_rec.stack.replaceAll(t_pat.pattern(), Matcher.quoteReplacement(newID + "$")); //$NON-NLS-1$
								t_rec.changed();
							}
						}
						if (t_rec.jSON != null) {
							t_m = t_pat.matcher(t_rec.jSON);
							String t_json = t_rec.jSON;
							while (t_m.find()) {
								String num = t_m.group(1);
								if (num.equals(oldID.toString())) {
									t_json = t_json.replaceAll(t_pat.pattern(), Matcher.quoteReplacement(newID + "$")); //$NON-NLS-1$
								}

							}
							if (!t_rec.jSON.equals(t_json)) {
								t_rec.jSON = t_json;
								t_rec.changed();
							}
						}
					}
				}
			}

			//TestCommandRecordに含まれている旧IDを入れ替える。
			for(Map.Entry<Integer, TestCommandRecord> entry : m_cmds.entrySet()) {
				if (entry.getKey() > oldID) {
					//該当するTestCommandのArgTypesもしくはsubjectの変更。
					TestCommandRecord t_cmd = entry.getValue();
					t_cmd.relink(oldID.toString(), nID.toString());
				}
			}
			m_model.reload();
		} else {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.87")); //$NON-NLS-1$
		}
	}


	private void goObjectPopUp(TestCmdNode targetNode, MouseEvent e) {
		String n_name = targetNode.getNodeName().replaceFirst("^(\\$[0-9_]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
		TestCommandRecord t_c = m_cmds.get(targetNode.getID());
		TestCaseRecord t_case = new TestCaseRecord(0);
		t_case.name = targetNode.getCaseName();
		t_case.include = m_include;

		/*
		 * TestCaseEditor.75=既存オブジェクトを指定
		 * TestCaseEditor.76=オブジェクトからコマンド生成
		 */

		JPopupMenu t_pop = new JPopupMenu();

		//pluginを考慮
		PluginAction.s_curNode = targetNode;
		PluginAction.s_this = this;

		int m = 0;
		boolean not_skip = true;
		for (int k = 0; k < Tool.plugins.size(); k++) {
			Plugin t_plug = Tool.plugins.get(k);
			int n = t_plug.getPriority(t_case, t_c, n_name);
			if (n > m) {
				t_pop.removeAll();

				//独自メニューを主張する場合（他のプラグインのメニューは出さない）
				t_pop.add(t_plug.getMenuItem("editArg")); //$NON-NLS-1$

				if (t_c.varArgsPos() >= 0 && !n_name.equals("$_")) { //$NON-NLS-1$
					//可変長引数ならば追加もあり。
					t_pop.add(t_plug.getMenuItem("addArg")); //$NON-NLS-1$
				}
				m = n;

				//優先度の高いメニュー項目があるのでスキップ
				if (n > 1) {
					not_skip = false;
				}
			}
		}
		//可変長引数の範囲にノードがある時、削除のメニュー項目もつける。
		if (t_c.varArgsPos() >= 0 && !n_name.equals("$_")) { //$NON-NLS-1$
			Integer t_i = Integer.valueOf(n_name.replaceFirst("\\$", "")); //$NON-NLS-1$ //$NON-NLS-2$
			if (t_i > t_c.varArgsPos()) {
				//削除
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.94"), this, targetNode))); //$NON-NLS-1$
			}
		}

		if (not_skip) {
			if (!n_name.equals("$0") && !n_name.equals("$_")) { //$NON-NLS-1$
				//null
				t_pop.add(new JMenuItem(new TestNodeAction("NULL", this, targetNode))); //$NON-NLS-1$
			}
			//既存Objectの再利用
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.75"), this, targetNode))); //$NON-NLS-1$

			//再利用して新コマンド生成
			t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.76"), this, targetNode))); //$NON-NLS-1$
		}

		t_pop.show(e.getComponent(), e.getX(), e.getY());
	}


	//可変長の引数の範囲でメニュー項目「コード変更削除」を選んだ時
	public void removeVarArg(TestCmdNode curNode) {
		//途中で消した場合、順序をそろえること。ObjectRecordのdeleteがうまく働くか？
		TestCommandRecord t_cmd = m_cmds.get(curNode.getID());

		String num = curNode.getNodeName().replaceAll("^\\$([0-9]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
		Integer i = Integer.valueOf(num);

		ArrayList<String> t_warning = Tool._db().getReferringObjectList(curNode.getCaseName(), Integer.toString(curNode.getID()) + "$" + num); //$NON-NLS-1$

		String msg = Messages.getString("TestCaseEditor.29"); //$NON-NLS-1$
		if (t_warning != null && t_warning.size() > 0) {
			msg = t_warning + Messages.getString("TestCaseEditor.95"); //$NON-NLS-1$
		}
		int t_res = JOptionPane.showConfirmDialog(this, msg + curNode.getNodeName() + "?"); //$NON-NLS-1$ //$NON-NLS-2$
		if(t_res == JOptionPane.CANCEL_OPTION) {
			return;
		}

		//argTypesの変更：同じ型なので一つ減らすだけ
		t_cmd.removeLast();

		//ObjectRecordのテーブルに登録されているObjectは再利用する。今のオブジェクトは末尾。それ以外は前に。stackを変更し、changed();
		String t_key = Integer.toString(t_cmd.id) + "$";	//obj_tableのキー。 //$NON-NLS-1$
		ObjectRecord target = obj_table.get(t_key + num);
		int j = i;

		//一つ後ろを前倒し
		ObjectRecord t_rec = obj_table.get(t_key + Integer.toString(j + 1));
		while (t_rec != null) {
			t_rec.stack = "$" + Integer.toString(j); //$NON-NLS-1$
			t_rec.id = t_key+ Integer.toString(j);
			t_rec.changed();
			obj_table.put(t_key+ Integer.toString(j), t_rec);

			//argStatusの更新
			checkCompleted(t_cmd, t_rec, null);

			j++;

			//一つ後ろを前倒し
			t_rec = obj_table.get(t_key + Integer.toString(j + 1));
		}
		//最後の引数として差し替え登録
		obj_table.put(t_key+ Integer.toString(j), target);
		target.removed = true;
		target.id = t_key+ Integer.toString(j);
		target.changed();

		//argStatusの更新
		checkCompleted(t_cmd, null, null);

		//Nodeは最後（Assertの前）だけ削除
		for (int k = 0; k < curNode.getParent().getChildCount(); k++) {
			TestCmdNode parent = (TestCmdNode) curNode.getParent();
			TestCmdNode t_node = (TestCmdNode)parent.getChildAt(k);
			if (t_node.getNodeName().startsWith("$" + Integer.toString(j))) { //$NON-NLS-1$
				parent.remove(t_node);
				break;
			}
		}
		m_model.reload();
	}


	//ObjectRecordにnullをセットする。
	public void setNullObject(TestCmdNode curNode) {
		String t_stack = curNode.getNodeName().replaceAll("^(\\$[0-9]+)=.*$", "$1");   // $n=... の$nを取得する。 //$NON-NLS-1$ //$NON-NLS-2$
		String num = t_stack.replaceAll("^.*\\$([0-9]+)$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

		int t_ID = curNode.getID();
		String t_key = Integer.valueOf(t_ID).toString() + t_stack;							//obj_tableのキーを作る。

		ObjectRecord t_rec = obj_table.get(t_key);  //このエディタが持っている辞書からオブジェクトを取得する。
		String cname = t_rec.className;	//classNameが入っているので保存。

		//編集中のものでなければ、DBから最新のObjectRecordを取得する。初期状態で0（更新されていない）を入れたことが、ここで意味を持つ。
		if (!t_rec.isUpdated()) {
			t_rec = Tool._db().getObjectRecord(t_rec);
		}

		if (t_rec == null) {
			//新規の場合
			t_rec = new ObjectRecord(2);
			t_rec.testID = t_ID;
			t_rec.stack = t_stack;
			t_rec.isPOJO = true;

			//引数にはint,boolean..がありうるがクラスではない。それに変換できるクラスに置き換える。
			t_rec.className = Tool.primitiveToFQN(cname);
		}
		t_rec.jSON = "null"; //$NON-NLS-1$

		t_rec.changed();

		obj_table.put(t_key, t_rec);
		curNode.setNodeName("$" + num + "=" + t_rec.className + " " + t_rec.stack); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

		//completed判断（Assert,Cmdの両方）
		checkCompleted(m_cmds.get(Integer.valueOf(t_ID)), t_rec, null);
	}

	//コマンドが完成かどうか判定し、コマンドの完成フラグを更新する。recとastは一度に入れてはならない。
	private void checkCompleted(TestCommandRecord cmd, ObjectRecord rec, AssertRecord ast) {
		String t_status = ""; //$NON-NLS-1$
		Boolean[] blist = (Boolean[])Tool.getObjectfromJSON(Boolean[].class, cmd.argStatus);

		if (ast == null && rec == null) {
			//指定がない場合、全部なめる。
			ArrayList<Boolean> new_list = new ArrayList<Boolean>();
			new_list.add(blist[0]);

			String t_key = Integer.toString(cmd.id) + "$";	//obj_tableのキー。 //$NON-NLS-1$
			Integer j = 1;
			ObjectRecord t_rec;
			while ((t_rec = obj_table.get(t_key + j.toString())) != null) {
				//削除されていないものだけカウント
				if (!t_rec.removed) {
					new_list.add(t_rec.completed);
				}
				j++;
			}
			new_list.add(blist[blist.length - 1]);

			t_status = Tool.getJSONfromObject(new_list.toArray(new Boolean[new_list.size()]));
		} else {
			if (ast == null) {
				String num = rec.id.replaceAll("^[0-9]+\\$([0-9]+)$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$
				blist[Integer.valueOf(num)] = rec.isCompleted();
			} else if (ast != null){
				blist[blist.length - 1] = ast.isCompleted();
			}
			t_status = Tool.getJSONfromObject(blist);
		}
		cmd.argStatus = t_status;

		cmd.isComplete();
		cmd.changed();
	}

	//このNodeのクラスと同じオブジェクトを既存から探して、このNodeに指定する。
	private void reuseObject(TestCmdNode curNode) {
		String cname = curNode.getNodeName().replaceFirst("^$[0-9]+=([\\w\\.\\[\\]]+)\\$w*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

		ArrayList<Integer> test_list = new ArrayList<Integer>();
		HashMap<Integer, String[]> stack_map = new HashMap<Integer, String[]>();
		ArrayList<String> temp = new ArrayList<String>();

		//再利用可能なオブジェクトの一覧を抽出する。
		for (int i = m_begin; i < curNode.getID(); i++) {
			temp.clear();

			//$_, $0
			TestCommandRecord t_rec = m_cmds.get(i);

			if (t_rec != null) {//歯抜けになったコマンドをはじく。
				if (t_rec.className.equals(cname)) {
					if (!test_list.contains(t_rec.id)) {
						test_list.add(t_rec.id);
					}
					temp.add("$0"); //$NON-NLS-1$
				}
				if (t_rec.returnType.equals(cname)) {
					if (!test_list.contains(t_rec.id)) {
						test_list.add(t_rec.id);
					}
					temp.add("$_"); //$NON-NLS-1$
				}
				for (int j= 0; j < t_rec.numArgs(); j++) {
					if (t_rec.getArgClass(j).equals(cname)) {
						//検索中の引数のクラスが一致した！
						if (!test_list.contains(t_rec.id)) {
							test_list.add(t_rec.id);
						}
						temp.add("[" + cname + "," + t_rec.getArgStack(j) + "]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
					}
				}
				if (temp.size() > 0) {
					stack_map.put(Integer.valueOf(t_rec.id), temp.toArray(new String[temp.size()]));
				}
			}
		}

		// testidのlistと$nの組を選ばせる。
		ReuseArguments t_dial = new ReuseArguments(this, test_list, m_cmds, stack_map);
		t_dial.setVisible(true);

		String t_name = curNode.getNodeName();

		if (t_dial.getID() > 0) {
			String r_name = Integer.toString(t_dial.getID()) + t_dial.getArg(); //[0-9]+$[_0-9]+
			t_name = t_name.replaceFirst("^($[0-9]+=[\\w]\\.[\\w\\[\\]]+)\\s+[\\$0-9_]+$", "$1 " + Matcher.quoteReplacement(r_name)); //$NON-NLS-1$ //$NON-NLS-2$
			String t_stack = t_name.replaceFirst("^($[0-9]+)=[\\w]\\.[\\w\\[\\]]+\\s+[\\$0-9_]+$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$

			int t_ID = curNode.getID();
			TestCommandRecord t_cmd = m_cmds.get(t_ID);
			String t_key = Integer.toString(t_ID) + t_stack;//obj_tableのキーを作る。
			ObjectRecord t_rec = obj_table.get(t_key);  //このエディタが持っている辞書からオブジェクトを取得する。

			t_rec.stack = r_name;
			if (t_key.endsWith("$0")) { //$NON-NLS-1$
				t_cmd.subject = r_name;
			} else {
				Integer n = Integer.valueOf(t_stack.replaceFirst("$", "")); //$NON-NLS-1$ //$NON-NLS-2$
				t_cmd.setArgType(n - 1, cname, r_name);
			}
			t_rec.stack = r_name;

			curNode.setNodeName(t_name);
			m_model.reload(curNode);
		}
	}

	//このオブジェクトを$0にしたTestCommandを作る。
	public void addTestOfObject(TestCmdNode curNode) {
		if (m_end > m_nextid) {
			TestCommandRecord t_cmd = new TestCommandRecord(2);//新規

			t_cmd.id = m_nextid;
			setNextID();

			String c_name = curNode.getNodeName().replaceFirst("^\\$[0-9]+=([\\.\\w\\[\\]]+)\\s.*$", "$1");//$n=classname\s... //$NON-NLS-1$ //$NON-NLS-2$

			//テストするメソッドを指定する。
			ClassReadDialog t_dial = new ClassReadDialog(this, Tool.target_dirs, t_cmd);
			t_dial.setClassName(c_name);
			t_dial.setVisible(true);

			String prevnum = Integer.toString(curNode.getID());
			//$0をどこから再利用するか指定する。
			String prevObj = prevnum + curNode.getNodeName().replaceFirst("^(\\$[0-9_]+)=.*$", "$1"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$

			DefaultPlugin plug = new DefaultPlugin();
			addCommandByPlugin(curNode, plug, prevObj);
		} else {
			Tool.alertMSG(null, Messages.getString("TestCaseEditor.3")); //$NON-NLS-1$
		}
	}


	private void goAssertPopUp(TestCmdNode targetNode, MouseEvent e) {
		Integer i = Integer.valueOf(targetNode.getID());
		TestCommandRecord t_c = m_cmds.get(i);

		//dummy
		TestCaseRecord t_case = new TestCaseRecord(0);
		t_case.name = targetNode.getCaseName();
		t_case.include = m_include;

		JPopupMenu t_pop = new JPopupMenu();

		//pluginを考慮
		PluginAction.s_curNode = targetNode;
		PluginAction.s_this = this;

		int m = 0;
		boolean not_skip = true;
		for (int k = 0; k < Tool.plugins.size(); k++) {
			Plugin t_plug = Tool.plugins.get(k);
			int n = t_plug.getPriority(t_case, t_c, "ASSERT"); //$NON-NLS-1$
			if (n > m) {
				t_pop.removeAll();

				//独自メニューを主張する場合（他のプラグインのメニューは出さない）
				JMenuItem t_item = t_plug.getMenuItem("editAssert"); //$NON-NLS-1$
				if (t_item != null) {
					t_pop.add(t_item);
				}

				m = n;

				//優先度の高いメニュー項目があるので他のメニュー項目はスキップ
				if (n > 1) {
					not_skip = false;
				}
			}
		}

		if (not_skip) {
			/* デフォルトメニューをそのまま使う。
			 * TestCaseEditor.9=Assert有効化
			 * TestCaseEditor.106=判定条件の編集
			 * TestCaseEditor.107=判定条件無効化
			 */

			if (targetNode.getNodeName().equals("null")) {	//disabled //$NON-NLS-1$
				//enable
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.9"), this, targetNode))); //$NON-NLS-1$
			} else {
				//disable
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.107"), this, targetNode))); //$NON-NLS-1$
			}
		}
		if (t_pop.getComponentCount() > 0) {
			t_pop.show(e.getComponent(), e.getX(), e.getY());
		}
	}

	private void editAssertByPlugin(TestCmdNode curNode, Plugin plug, String opt) {
		//引数のクラスリストを渡す。$_, $$:0-...
		Integer i_num = Integer.valueOf(curNode.getID());
		String t_id = i_num.toString();

		TestCommandRecord t_c = m_cmds.get(curNode.getID());
		TestCaseRecord t_case = new TestCaseRecord(0);
		t_case.name = curNode.getCaseName();
		t_case.include = m_include;

		AssertRecord t_ast = m_assert.get(curNode.getID());
		if (t_ast == null) {
			t_ast = Tool._db().getAssert(i_num);
			if (t_ast == null) {
				t_ast = new AssertRecord(2);
				t_ast.id = Integer.valueOf(t_id);
			}
			m_assert.put(i_num, t_ast);
		}
		AssertRecord t_clone = (AssertRecord)t_ast._dup(true);//trueはstatusもコピー
		plug.editAssert(t_ast, t_c);

		//更新したかどうか判定する。
		if (!t_ast.equals(t_clone)) {
			t_clone.changed();
			m_assert.put(i_num, t_ast);

			//completed判断（Assert,Cmdの両方）
			checkCompleted(t_c, null, t_ast);//引数は触っていないのでnull
		}
		if (t_ast.enable) {
			String t_done = ""; //$NON-NLS-1$
			if (t_ast.isCompleted()) {
				t_done = "(done)"; //$NON-NLS-1$
			}
			String t_regs = t_ast.criteriaType;
			if (t_regs == null || t_regs.length() == 0) {
				t_regs = Messages.getString("TestCaseEditor.62"); //$NON-NLS-1$

			}
			curNode.setNodeName("Assert=" + t_regs + t_done); //$NON-NLS-1$
		} else {
			curNode.setNodeName("skip"); //$NON-NLS-1$
		}

		m_model.reload(curNode);
	}

	private void disableAssert(TestCmdNode curNode) {
		//引数のクラスリストを渡す。$_, $$:0-...
		AssertRecord t_ast = m_assert.get(curNode.getID());
		if (t_ast != null) {
			t_ast.setEnable(false);
			t_ast.changed();
		}
		curNode.setNodeName("skip"); //$NON-NLS-1$
		m_model.reload(curNode);
	}

	private void enableAssert(TestCmdNode curNode) {
		AssertRecord t_ast = m_assert.get(curNode.getID());
		Integer i_num = Integer.valueOf(curNode.getID());

		if (t_ast != null) {
			t_ast.setEnable(true);
			t_ast.changed();

			if (t_ast.enable) {
				String t_done = ""; //$NON-NLS-1$
				if (t_ast.isCompleted()) {
					t_done = "(done)"; //$NON-NLS-1$
				}
				String t_regs = t_ast.criteriaType;
				if (t_regs == null || t_regs.length() == 0) {
					t_regs = Messages.getString("TestCaseEditor.71"); //$NON-NLS-1$

				}
				curNode.setNodeName("Assert=" + t_regs + t_done); //$NON-NLS-1$
			} else {
				curNode.setNodeName("skip"); //$NON-NLS-1$
			}
		} else {
			curNode.setNodeName("Assert=" + i_num.toString()); //$NON-NLS-1$
		}
		m_model.reload(curNode);
	}

	private void setNextID() {
		int n = 0;
		if (m_cmds.size() > 0) {
			for(Entry<Integer, TestCommandRecord> entry1 : m_cmds.entrySet()) {
				if (entry1.getValue().id >= n) {
					n = entry1.getValue().id + 1;
				}
			}
		} else if (m_nextid == 0) {
			//コンストラクタの中で呼ばれる
			n = m_begin;
		}
		m_nextid = n;
	}

	//popupのコマンド文字列を受けて、TestMasterGUIのメソッドに中継する。
	//中継だけなので、アクションはこのクラスのみ。
	class TestNodeAction extends AbstractAction {
	    String m_name;
		TestCmdNode curNode;
		TestCaseEditor m_master;

		TestNodeAction(String name, TestCaseEditor master, TestCmdNode targetNode) {
			putValue(Action.NAME, name);
			m_name = name;
			curNode = targetNode;
			m_master = master;
		}

		@Override
		public void actionPerformed(ActionEvent e) {

			/*
			 * TestCaseEditor.113=ケース名変更
			 * TestCaseEditor.114=説明する
			 * TestCaseEditor.14=コード変更削除
			 * TestCaseEditor.90=テストケース結合
			 * TestCaseEditor.115=コマンド再利用
			 * TestCaseEditor.11=コマンド有効化
			 * TestCaseEditor.116=コマンド無効化
			 * TestCaseEditor.117=コマンド削除
			 * TestCaseEditor.119=既存オブジェクトを指定
			 * TestCaseEditor.120=オブジェクトからコマンド生成
			 * TestCaseEditor.121=判定条件の編集
			 * TestCaseEditor.122=判定条件無効化
			 * TestCaseEditor.4=Assert有効化
			 * TestCaseEditor.12=テストID変更
			 * TestCaseEditor.33=ログ無効化
			 * TestCaseEditor.38=ログ有効化
			} else {
				t_pop.add(new JMenuItem(new TestNodeAction(Messages.getString("TestCaseEditor.38"), this, targetNode))); //$NON-NLS-1$
			}

			 */

			if (m_name.equals(Messages.getString("TestCaseEditor.113"))) { //$NON-NLS-1$
				m_master.renTestCase(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.114"))) { //$NON-NLS-1$
				m_master.describeTestCase(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.90"))) { //$NON-NLS-1$
				m_master.includeTestCase(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.33"))) { //$NON-NLS-1$
				m_master.unLoggableTestCase(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.38"))) { //$NON-NLS-1$
				m_master.loggableTestCase(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.120"))) { //$NON-NLS-1$
				m_master.addTestOfObject(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.115"))) { //$NON-NLS-1$
				m_master.copyAddTest(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.11"))) { //$NON-NLS-1$
				m_master.enableTest(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.116"))) { //$NON-NLS-1$
				m_master.disableTest(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.46"))) { //$NON-NLS-1$
				m_master.setSnapshot(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.15"))) { //$NON-NLS-1$
				m_master.resetSnapshot(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.117"))) { //$NON-NLS-1$
				m_master.removeTest(curNode, true);//参照されている時は警告を発する。
			} else if (m_name.equals(Messages.getString("TestCaseEditor.119"))) { //$NON-NLS-1$
				m_master.reuseObject(curNode);
			} else if (m_name.equals("NULL")) { //$NON-NLS-1$
				m_master.setNullObject(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.122"))) { //$NON-NLS-1$
				m_master.disableAssert(curNode);
			} else if (m_name.equals(Messages.getString("TestCaseEditor.4"))) { //$NON-NLS-1$
				m_master.enableAssert(curNode);
			} else if (m_name.startsWith(Messages.getString("TestCaseEditor.12"))) { //$NON-NLS-1$
				m_master.renumberTest(curNode);
			}
		}
	}

	//plugin共通のアクション
	static class PluginAction extends AbstractAction {
		static TestCaseEditor s_this;
		static TestCmdNode s_curNode;
		String m_cmd;
		String m_opt;

		Plugin m_p;

		//optはサブメニュー用
		PluginAction(String cmd, String name, String opt, Plugin p) {
			if (name != null && name.length() > 0) {
				putValue(Action.NAME, name);
			} else {
				Tool.alertMSG(null, Messages.getString("TestCaseEditor.108") + p.getClass().getName()); //$NON-NLS-1$
			}
			m_cmd = cmd;
			m_opt = opt;
			m_p = p;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			if  (m_cmd.equals("addCommand")) { //$NON-NLS-1$
				s_this.addCommandByPlugin(s_curNode, m_p, null);
			} else if  (m_cmd.equals("addArg")) { //$NON-NLS-1$
				s_this.addArgByPlugin(s_curNode, m_p, m_opt);
			} else if  (m_cmd.equals("editArg")) { //$NON-NLS-1$
				s_this.editArgByPlugin(s_curNode, m_p, m_opt);
			} else if  (m_cmd.equals("editAssert")) { //$NON-NLS-1$
				s_this.editAssertByPlugin(s_curNode, m_p, m_opt);
			}
		}
	}

	//（カスケードメニューなども作れるようにするため）pluginから呼ばれる。MenuItemを作る。
	public static JMenuItem getMenuItem(String cmd, String name, String opt, Plugin p) {
		return new JMenuItem(new PluginAction(cmd, name, opt, p));
	}

	//DBを更新すべきかどうかを返す。
	public boolean isOK() {
		return m_isOK;
	}


	//TestCaseRecordに内容を転記する。
	public void copyData(TestCaseRecord rec) {
		rec.description = m_desc;
		rec.name = m_root.getCaseName();
		rec.begin = Integer.valueOf(m_begin);
		rec.end = Integer.valueOf(m_end);
		int n = 0;
		if (m_snapshot != null) {
			n = m_snapshot.getID();
		}
		rec.snapshot = n;
		rec.include = m_include;
		rec.loggable = m_loggable;
	}

}

