Newer
Older
org.ntlab.reverseDebugger / org.ntlab.reverseDebugger / src / org / ntlab / debuggingControl / DebuggingControlAction.java
package org.ntlab.debuggingControl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.internal.core.LaunchManager;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.ui.JavaUI;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.IWorkbenchWindowActionDelegate;
import org.eclipse.ui.PartInitException;
import org.ntlab.reverseDebugger.ObjectFlowAliases;
import org.ntlab.reverseDebugger.OnlineTraceAnalyzer;
import org.ntlab.reverseDebugger.OnlineTraceAnalyzerCallTester;

import com.sun.jdi.AbsentInformationException;
import com.sun.jdi.BooleanValue;
import com.sun.jdi.ByteValue;
import com.sun.jdi.CharValue;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.ClassType;
import com.sun.jdi.DoubleValue;
import com.sun.jdi.Field;
import com.sun.jdi.FloatValue;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.LocalVariable;
import com.sun.jdi.Location;
import com.sun.jdi.LongValue;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ShortValue;
import com.sun.jdi.StackFrame;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.VirtualMachine;

public class DebuggingControlAction implements IWorkbenchWindowActionDelegate {
	
	@Override
	public void run(IAction arg0) {
		// TODO Auto-generated method stub
		LaunchManager lm = (LaunchManager)DebugPlugin.getDefault().getLaunchManager();
		
		ILaunch[] launches = lm.getLaunches();
		if (launches.length == 0) {
			MessageDialog.openInformation(null, null, "一度Javaプログラムを実行してください");		
		} else {
			ILaunch debugLaunch = null;
			for (int i = 0; i < launches.length; i++) {
				System.out.print(launches[i].getLaunchConfiguration().getName() + ":");
				System.out.print(launches[i].getDebugTarget());
				if (launches[i].getDebugTarget() != null) {
					debugLaunch = launches[i];
					break;
				}
			}
			if (debugLaunch != null) {
				JDIDebugTarget debugTarget = ((JDIDebugTarget)debugLaunch.getDebugTarget());
				VirtualMachine vm = debugTarget.getVM();
				if (vm != null) {
					// デバッグ実行中の場合
					List<ThreadReference> allThreads = vm.allThreads();
					for (int i = 0; i < allThreads.size(); i++) {
						ThreadReference thread = allThreads.get(i);
						if (thread.isSuspended()) {
							try {
								// 停止しているスレッドがどのクラス(どのメソッド)の何行目で停止しているのかを表示する
								List<StackFrame> frames = thread.frames();		// 停止スレッド中の全スタックフレーム
								StackFrame topFrame = frames.get(0);
								Location location = topFrame.location();		// スタックトップにあるフレーム(ブレークポイントで停止中)
								MessageDialog.openInformation(null, null, "Thread [" + thread.name() + "] \n"
										+ location.method().declaringType().name() + "." + location.method().name() + location.method().signature() + " line: "
										+ location.lineNumber());
								System.out.println();
								callCollectorMethods(vm, thread); // traceCollectorのメソッド呼び出し関連
								System.out.println();
							} catch (InvalidTypeException
									| ClassNotLoadedException
									| InvocationException e) {
								e.printStackTrace();
							} catch (IncompatibleThreadStateException e) {
								e.printStackTrace();
							} 
						}
					}
				} else {
					MessageDialog.openInformation(null, null, "Javaプログラムをデバッグ実行中の停止状態にしてください");					
				}
			} else {
				MessageDialog.openInformation(null, null, "Javaプログラムをデバッグ実行してください");						
			}
		}
	}

	@Override
	public void selectionChanged(IAction arg0, ISelection arg1) {
		// TODO Auto-generated method stub

	}

	@Override
	public void dispose() {
		// TODO Auto-generated method stub

	}

	@Override
	public void init(IWorkbenchWindow arg0) {
		// TODO Auto-generated method stub

	}
	
	/**
	 * traceCollectorのメソッドを呼ぶ
	 * @param vm
	 * @param thread
	 */
	private void callCollectorMethods(VirtualMachine vm, ThreadReference thread) throws InvalidTypeException,
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		String packageName = "org.ntlab.traceCollector.tracer.trace";
		String className;
		String methodName;
		
		// threadIdの取得とStringReference型への変換
		methodName = "getId";
		Value threadIdValue = callInstanceMethod(vm, thread, methodName, thread);
		StringReference threadId = vm.mirrorOf(String.valueOf(((LongValue)threadIdValue).value()));
		System.out.println("threadId = " + threadId); // 取得したThreadIdの確認用

		// threadIdに対応するThreadInstanceを取得
		className = "TraceJSON";
		methodName = "getThreadInstance";
		Value threadInstance = callStaticMethod(vm, thread, packageName, className, methodName, threadId);
		
		// threadInstanceの呼び出しスタック上のメソッドについての各種データを取得して表示する
//		printStackData(vm, thread, threadInstance);
		
		// threadInstanceのメソッド呼び出し木の全メソッドについての各種データを取得して表示する
//		printCallTreeDataFromRoot(vm, thread, threadInstance);
		
		// オンライン解析中にtraceCollectorのTraceJSON#getObjectFlow()を呼び出す仮のコード
//		printObjectFlow(vm, thread, threadInstance);
		
		// スピード計測と比較
//		SpeedTester st = new SpeedTester();
//		st.countMethodExecutionTest(vm, thread);
		
		// 設計変更による一次解析用のコード(ターゲット側のVM上で実行するOnlineTraceAnalyzerクラス関連のメソッド)を呼び出すための仮コード
		OnlineTraceAnalyzerCallTester analyzerCallTester = new OnlineTraceAnalyzerCallTester();
		analyzerCallTester.test1(vm, thread);
	}
	
	/**
	 * オンライン解析中にtraceCollectorのTraceJSON#getObjectFlow()を呼び出す仮のコード
	 * 2018/9/16追記 こちらからobjectFlowを呼び出す必要はなくなったためもう不要なコードと考えてよい
	 * 設計変更を行ったため呼び出すとエラーが発生するかもしれない(もう不要なコードのためテストしていない)
	 * @param vm
	 * @param thread
	 * @param threadInstance
	 */
	private void printObjectFlow(VirtualMachine vm, ThreadReference thread, Value threadInstance) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// ThreadInstanceが持つcurMethodExecutionと, そのstatementリストを取得
		String methodName = "getCurrentMethodExecution";
		Value methodExecution = callInstanceMethod(vm, thread, methodName, (ObjectReference)threadInstance);
		Scanner scanner = new Scanner(System.in);
		methodName = "getStatements";
		Value statements = callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
		methodName = "size";
		int statementsSize = ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)statements)).value();
		
		// デバッガで止めている地点に該当するメソッド実行内で, スタートとなる候補のエイリアスのリストを取得して表示する
//		String packageName = "org.ntlab.traceCollector.tracer.trace";
//		String className = "TraceJSON";
		String packageName = "org.ntlab.reverseDebugger";
		String className = "OnlineTraceAnalyzer";
		methodName = "findAllStartAlias";
		Value startAliasList = callStaticMethod(vm, thread, packageName, className, methodName, methodExecution);
		methodName = "size";
		int startAliasListSize = ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAliasList)).value();
		System.out.println("-----------------------------------------------------");
		for (int i = 0; i < startAliasListSize; i++) {
			methodName = "get";
			Value startAlias = callInstanceMethod(vm, thread, methodName, (ObjectReference)startAliasList, vm.mirrorOf(i));
			StringBuilder sb = new StringBuilder(String.format("%2d%s", i, ": "));
			methodName = "getObjectId";
			sb.append(String.format("%12s", ((StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value() + "  "));
			methodName = "getLineNo";
			sb.append(String.format("%4s", ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value() + "  "));
			methodName = "getStatementType";
			sb.append(String.format("%-16s", ((StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value()));
			sb.append(" -> ");
			methodName = "getStatementSignature";
			sb.append(String.format("%-30s", ((StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value() + "  "));
			methodName = "getOccurrenceExp";
			sb.append(String.format("%3d%s", ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value(), "  "));
			methodName = "getClassName";
			sb.append(String.format("%-20s", ((StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias)).value() + "  "));
			System.out.println(sb);
		}
		System.out.println("-----------------------------------------------------");

		// 選択したエイリアスを起点にオブジェクトフローを呼び出して結果をリストで受け取る
		System.out.print("(input)aliasNo? -> ");
		int aliasNo = scanner.nextInt();
		methodName = "get";
		Value startAlias = callInstanceMethod(vm, thread, methodName, (ObjectReference)startAliasList, vm.mirrorOf(aliasNo));
		
//		// DebuggingControl側からGUI関連のクラスの方に無理矢理シードエイリアスを渡してオブジェクトフローを実行してみる
//		// 一時的なもので, 最終的にはここでやる処理ではなくなるので不要になるはず
//		ObjectFlowAliases.getInstance().getObjectFlow(new MethodCaller(vm, thread, (ObjectReference)startAlias));
		
		// 以下は一時的にコメントアウトしておく
//		methodName = "toString";
//		Value str = callInstanceMethod(vm, thread, methodName, (ObjectReference)startAlias);
//		printValue("", "", str, true);
//		methodName = "getObjectFlow";
//		Value aliasLists = callStaticMethod(vm, thread, packageName, className, methodName, startAlias);
//		System.out.println();
//		
//		// 取得したリストの中身をループで回しながら確認し, ついでに当該ソースファイルを対象Eclipseで開かせてみる
//		methodName = "size";
//		int aliasListsSize = ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)aliasLists)).value();
//		List<List<MethodCaller>> objectFlowAliasLists = new ArrayList<>();
//		for (int i = 0; i < aliasListsSize; i++) {
//			List<MethodCaller> objectFlowAliasList = new ArrayList<>();
//			objectFlowAliasLists.add(objectFlowAliasList);
//			System.out.println("---------------------------------------------");
//			System.out.println("リスト" + i);
//			methodName = "get";
//			Value aliasList = callInstanceMethod(vm, thread, methodName,(ObjectReference)aliasLists, vm.mirrorOf(i));
//			methodName = "size";
//			int aliasListSize = ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)aliasList)).value();
//			for (int j = 0; j < aliasListSize; j++) {
//				methodName = "get";
//				Value alias = callInstanceMethod(vm, thread, methodName, (ObjectReference)aliasList, vm.mirrorOf(j));
//				methodName = "toString";
//				Value str2 = callInstanceMethod(vm, thread, methodName, (ObjectReference)alias);
//				printValue("", "", str2, true);
//				methodName = "getMethodExecution";
//				Value aliasMethodExecution = callInstanceMethod(vm ,thread, methodName, (ObjectReference)alias);
//				openSrcFileOfMethodExecution(vm, thread, aliasMethodExecution);
//				objectFlowAliasList.add(new MethodCaller(vm, thread, (ObjectReference)alias));
//			}
//		}
	}
	
	/**
	 * 引数で渡したthreadInstanceの呼び出しスタック上のメソッドについての各種データを取得して表示する
	 * @param vm
	 * @param thread
	 * @param threadInstance
	 */
	private void printStackData(VirtualMachine vm, ThreadReference thread, Value threadInstance) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// ThreadInstanceが持つcurMethodExecutionを取得
		String methodName = "getCurrentMethodExecution";
		Value methodExecution = callInstanceMethod(vm, thread, methodName, (ObjectReference)threadInstance);
		Value child = null;

		// curMethodExetuionのシグネチャを取得し、その呼び出し元のメソッドについても同様の処理を繰り返す
		while (methodExecution != null) {
			Value lineNo = (child != null) ? getMethodExecutionLineNo(vm, thread, methodExecution, child)
							: vm.mirrorOf(thread.frame(0).location().lineNumber()); // child == null のとき(=スタックのトップのとき)は、正確なlineNoはJDI側のメソッドを用いて取得する

			// データの取得と表示関連
			String indent = "	";
			Value methodSignature = getMethodExecutionSignature(vm, thread, methodExecution);
			printValue("", "", methodSignature, true);
			printValue(indent, "lineNo = ", lineNo, true);
			System.out.println();
			IType type = findIType(vm, thread, methodExecution);
			IMethod method = findIMethod(vm, thread, type, methodExecution);
			openSrcFileOfIMethod(type, method);
			printAllIdAndType(vm, thread, methodExecution, method, indent);

			// curMethodExecutionの呼び出しメソッドを取得 (MethodExecution)
			child = methodExecution;
			methodName = "getParent";
			methodExecution = callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
		}				
	}
	
	/**
	 * 引数で渡したthreadInstanceの呼び出し木のメソッドについての各種データを取得して表示する<br>
	 * 呼び出し木のメソッドは各threadInstanceでのRootから順番に取得
	 * @param vm
	 * @param thread
	 * @param threadInstance
	 */
	private void printCallTreeDataFromRoot(VirtualMachine vm, ThreadReference thread, Value threadInstance) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {		
		// ThreadInstanceが持つrootのMethodExecutionを取得(ArrayList<MethodExecution>)
		String methodName = "getRoot";
		Value methodExecutions = callInstanceMethod(vm, thread, methodName, (ObjectReference)threadInstance);
		printCallTreeData(vm, thread, null, methodExecutions, "");
	}
	
	/**
	 * 引数で渡したmethodExecutionリストが持つメソッドを起点として、呼び出し木を深さ優先探索の<br>
	 * 行きがけ順で探索して、メソッドとその各種データを取得して表示する
	 * @param vm
	 * @param thread
	 * @param parent
	 * @param methodExecutions
	 * @param indent
	 */
	private void printCallTreeData(VirtualMachine vm, ThreadReference thread, Value parent, Value methodExecutions, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		if (methodExecutions == null) {
			return;
		}				
		String methodName = "size";
		final int METHOD_EXECUTIONS_SIZE = ((IntegerValue)callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecutions)).value();
		if (METHOD_EXECUTIONS_SIZE == 0) {
			return;
		}

		final String DEEP_SPACE_INDENT = indent.replace("-", " ") + "	"; // メソッドシグネチャ以外での表示用
		for (int i = 0; i < METHOD_EXECUTIONS_SIZE; i++) {
			IntegerValue index = vm.mirrorOf(i);
			methodName = "get";
			Value methodExecution = callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecutions, index);
			Value lineNo = (parent != null) ? getMethodExecutionLineNo(vm, thread, parent, methodExecution) : vm.mirrorOf(0);

			// データの取得と表示関連			
			Value methodSignature = getMethodExecutionSignature(vm, thread, methodExecution);
			printValue(indent, "", methodSignature, true);
			printValue(DEEP_SPACE_INDENT, "lineNoCalledByParent = ", lineNo, true);
			System.out.println();
			IType type = findIType(vm, thread, methodExecution);
			IMethod method = findIMethod(vm, thread, type, methodExecution);
			openSrcFileOfIMethod(type, method);
			printAllIdAndType(vm, thread, methodExecution, method, DEEP_SPACE_INDENT);
			
			// メソッド呼び出し木においての子メソッドについても同様の処理を再帰的に行う
			methodName = "getChildren";
			Value children = callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
			printCallTreeData(vm, thread, methodExecution, children, (indent + "--------"));
		}
	}
		
	/**
	 * thisや仮引数のIDとType 及び それらが持つフィールドのIDとTypeを全て表示する 
	 * @param vm
	 * @param thread
	 * @param methodExecution
	 * @param method
	 * @param indent
	 */
	private void printAllIdAndType(VirtualMachine vm, ThreadReference thread, Value methodExecution, IMethod method, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// thisのIDとTypeを取得して表示
		Value thisId = getMethodExecutionThisId(vm, thread, methodExecution);
		printValue(indent, "this id = ", thisId, true);
		Value thisType = getMethodExecutionThisType(vm, thread, methodExecution);
		printValue("", " type = ", thisType, false);

		// thisの持つフィールドのIDとTypeを表示 (呼び出し先で再帰)
		System.out.println();
		printFieldIdAndType(vm, thread, thisType, thisId, (indent + "    "));
		
		// 引数のIDとType 及びその引数が持つフィールドのIDとTypeを表示 (呼び出し先で再帰)
		System.out.println();
		printArgsIdAndType(vm, thread, methodExecution, method, indent);
	}
	
	private Value getMethodExecutionSignature(VirtualMachine vm, ThreadReference thread, Value methodExecution) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {		
		// curMethodExecutionのシグネチャを取得(String)
		String methodName = "getSignature";
		return callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
	}
	
	private Value getMethodExecutionThisType(VirtualMachine vm, ThreadReference thread, Value methodExecution) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {		
		// thisClassNameを取得(String)
		String methodName = "getThisClassName";
		return callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);		
	}
	
	private Value getMethodExecutionThisId(VirtualMachine vm, ThreadReference thread, Value methodExecution) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// thisObjIdを取得(String)
		String methodName = "getThisObjId";
		return callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);		
	}
	
	private Value getMethodExecutionLineNo(VirtualMachine vm, ThreadReference thread, Value methodExecution, Value child) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// methodExecutionがchildを呼び出した場所のlineNoを取得(IntegerValue)
		String methodName = "getMethodInvocation";
		ObjectReference methodInvocation = (ObjectReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution, child);
		if (methodInvocation != null) {
			methodName = "getLineNo";
			return callInstanceMethod(vm, thread, methodName, methodInvocation);
		}
		return null;
	}

	private void printFieldIdAndType(VirtualMachine vm, ThreadReference thread, Value thisClassName, Value thisObjId, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		String packageName = "org.ntlab.traceCollector.tracer.trace";
		String className = "TraceJSON";
		String methodName;
		
		// フィールドのIDとTypeを取得して表示
		List<ReferenceType> classes = vm.classesByName(((StringReference)thisClassName).value());
		ClassType type = (ClassType)classes.get(0);
		for (Field field : type.allFields()) {
			if (field.isStatic()) {
				continue; // staticフィールドは飛ばす (取ろうとしても、IDもTypeも取れない)
			}
			String fieldName = field.declaringType().name() + "." + field.name(); // 完全限定名			
			System.out.print(indent + fieldName);
			
			// そのフィールドについての最新の更新情報を取得(FieldUpdate)
			methodName = "getRecentlyFieldUpdate";
			ObjectReference fieldUpdate = (ObjectReference)callStaticMethod(vm, thread, packageName, className, methodName, thisObjId, vm.mirrorOf(fieldName), thread);
			if (fieldUpdate == null) {
				System.out.println(" id = nullだよ!" + " type = nullだよ!");
				continue;
			}
			
			// フィールドのIDとTypeを取得(String)
			methodName = "getValueObjId";
			Value fieldObjIdValue = callInstanceMethod(vm, thread, methodName, fieldUpdate);
			printValue("", " id = ", fieldObjIdValue, false);
			methodName = "getValueClassName";
			Value fieldTypeValue = callInstanceMethod(vm, thread, methodName, fieldUpdate);
			printValue("", " type = ", fieldTypeValue, false);
			
			// 取得したフィールドがオブジェクトや配列の場合に、そのオブジェクトや配列が持つフィールドについての処理を行う
			System.out.println();
			printDeepHierarchyField(vm, thread, fieldObjIdValue, fieldTypeValue, fieldName, (indent + "    "));			
		}
	}

	/**
	 * 引数で渡したフィールドが参照型の場合や、配列の場合に、<br>
	 * その参照型オブジェクトが持っているフィールドや、配列の各要素のオブジェクトについて、<br>
	 * それぞれに対応したメソッドをさらに呼び出す (このメソッドと対応メソッドとの相互再帰)
	 * @param vm
	 * @param thread
	 * @param objIdValue
	 * @param typeValue
	 * @param fieldName
	 * @param indent
	 */
	private void printDeepHierarchyField(VirtualMachine vm, ThreadReference thread, Value objIdValue, Value typeValue,
			String fieldName, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// フィールドのIDやTypeがない場合や、Type(=ActualType)が"---"の場合は何もしない
		if (objIdValue == null
				|| typeValue == null
				|| ((StringReference)objIdValue).value().isEmpty()
				|| ((StringReference)typeValue).value().isEmpty()) {
			return;
		}
		final String NULL_ACTUAL_TYPE = "---"; // フィールドに対して明示的にnullを入れた場合のActualTypeの取得文字列
		String fieldType = ((StringReference)typeValue).value();
		if (fieldType.equals(NULL_ACTUAL_TYPE)) {
			return;
		}

		final String ARRAY_SIGNATURE_HEAD = "["; // 配列のシグネチャの先頭は、配列の次元数だけ [ が連なる
		if (fieldType.startsWith(ARRAY_SIGNATURE_HEAD)) {
			// フィールドのTypeが配列型( [ で始まる )場合 (その配列が持つ各要素についてさらなるデータ取得処理を呼び出す)
			printArrayIdAndType(vm, thread, objIdValue, fieldName, indent);
		} else {
			String[] primitives = {"byte", "short", "int", "long", "float", "double", "char", "boolean"};
			if (!Arrays.asList(primitives).contains(fieldType)) {
				// フィールドのTypeが参照型(=オブジェクト)の場合 (そのオブジェクトが持っているフィールドについてさらなるデータ取得処理を呼び出す)
				printFieldIdAndType(vm, thread, typeValue, objIdValue, indent);
			}
		}
	}

	private void printArrayIdAndType(VirtualMachine vm, ThreadReference thread, Value arrayObjId, String arrayName, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		String packageName = "org.ntlab.traceCollector.tracer.trace";
		String className = "TraceJSON";
		String methodName;
		
		for (int i = 0;; i++){
			// その配列要素についての最新の更新情報を取得(ArrayUpdate)
			methodName = "getRecentlyArrayUpdate";
			IntegerValue index = vm.mirrorOf(i);
			ObjectReference arrayUpdate = (ObjectReference)callStaticMethod(vm, thread, packageName, className, methodName, arrayObjId, index, thread);
			if (arrayUpdate == null) {
				// 配列のサイズが取得できないため、インデックスがサイズ超過のときに確実に抜けられる方法として仮処理
				// ただし、配列要素の途中に未定義があった場合でも、抜けてしまうのが問題点
				break;
			}			
			String arrayIndexName = arrayName + "[" + index.value() + "]";
			System.out.print(indent + arrayIndexName);
			
			// 配列要素のIDとTypeを取得(String)
			methodName = "getValueObjectId";
			Value valueObjId = callInstanceMethod(vm, thread, methodName, arrayUpdate);
			printValue("", " id = ", valueObjId, false);
			methodName = "getValueClassName";
			Value valueType = callInstanceMethod(vm, thread, methodName, arrayUpdate);
			printValue("", " type = ", valueType, false);

			// 取得した配列要素がオブジェクトや配列の場合に、そのオブジェクトや配列が持つフィールドについての処理を行う
			System.out.println();
			printDeepHierarchyField(vm, thread, valueObjId, valueType, arrayIndexName, (indent + "    "));
		}		
	}
	
	private void printArgsIdAndType(VirtualMachine vm, ThreadReference thread, Value methodExecution, IMethod method, String indent) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		// methodExecutionが持つargumentsを取得(ArrayList)し、そのargumentsのサイズも取得(int)
		String methodName = "getArguments";
		ObjectReference args = (ObjectReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
		methodName = "size";
		final int ARGUMENTS_NUM = ((IntegerValue)callInstanceMethod(vm, thread, methodName, args)).value();
		if (ARGUMENTS_NUM > 0) {
			String[] argNames = getParameterNames(method); // 引数のIMethodから仮引数名を取得する
			System.out.println(indent + "args");
			for (int i = 0; i < ARGUMENTS_NUM; i++) {
				String argName = (argNames.length == ARGUMENTS_NUM) ? argNames[i] : "arg" + i; // 少なくとも引数の個数が不一致のときは正しい引数名が取れていない
				methodName = "get";
				ObjectReference arg = (ObjectReference)callInstanceMethod(vm, thread, methodName, args, vm.mirrorOf(i));
				methodName = "getId";
				Value argId = callInstanceMethod(vm, thread, methodName, arg);
				methodName = "getActualType";
				Value argType = callInstanceMethod(vm, thread, methodName, arg);
				System.out.println(indent + argName + " id = " + ((StringReference)argId).value() + " type = " + ((StringReference)argType).value());
				printDeepHierarchyField(vm, thread, argId, argType, argName, (indent + "    "));
				System.out.println();
			}
		}
	}

	private String[] getParameterNames(IMethod method) {
		String[] argNames = new String[0];
		if (method != null) {
			try {
				argNames = method.getParameterNames();
			} catch (JavaModelException e) {
				e.printStackTrace();
			}
		}
		return argNames;
	}
	
	/**
	 * 引数で渡したmethodExecutionが定義されているクラスのソースコードを対象Eclipseのエディタで開かせる
	 * @param vm
	 * @param thread
	 * @param methodExecution
	 */
	private void openSrcFileOfMethodExecution(VirtualMachine vm, ThreadReference thread, Value methodExecution) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		IType type = findIType(vm, thread, methodExecution);
		if (type != null) {
			IMethod method = findIMethod(vm, thread, type, methodExecution);
			if (method != null) {
				openSrcFileOfIMethod(type, method);
			}
		}
	}
	
	/**
	 * 引数で渡したITypeとIMethodに対応するソースコードを対象Eclipseのエディタで開かせる
	 * @param type
	 * @param method
	 */
	private void openSrcFileOfIMethod(IType type, IMethod method) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		openInJavaEditor(type, method);
	}
	
	private IType findIType(VirtualMachine vm, ThreadReference thread, Value methodExecution) throws InvalidTypeException,
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		String methodName = "getDeclaringClassName";
		StringReference declaringClassName = (StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
		String projectPath = getLoaderPath(vm, thread, methodExecution, declaringClassName);
		IType type = null;
		if (projectPath != null) {
			IJavaProject javaProject = findJavaProject(projectPath);
			if (javaProject != null) {
				StringReference methodSignature = (StringReference)getMethodExecutionSignature(vm, thread, methodExecution);
				try {
					type = javaProject.findType(declaringClassName.value());
				} catch (JavaModelException e) {
					e.printStackTrace();
				}				
			}
		}
		return type;
	}

	private String getLoaderPath(VirtualMachine vm, ThreadReference thread, Value methodExecution, StringReference declaringClassName) throws InvalidTypeException, 
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		String packageName = "org.ntlab.traceCollector.tracer.trace";
		String className = "TraceJSON";
		String methodName;

		methodName = "getClassInfo";
		ObjectReference classInfo = (ObjectReference)callStaticMethod(vm, thread, packageName, className, methodName, declaringClassName);
		if (classInfo == null) {
			methodName = "getThisClassName";
			declaringClassName = (StringReference)callInstanceMethod(vm, thread, methodName, (ObjectReference)methodExecution);
			methodName = "getClassInfo";
			classInfo = (ObjectReference)callStaticMethod(vm, thread, packageName, className, methodName, declaringClassName);
		}
		String loaderPath = null;
		if (classInfo != null) {
//			// loaderPathを取得
//			methodName = "getLoaderPath";
//			loaderPath = ((StringReference)callInstanceMethod(vm, thread, methodName, classInfo)).value();
		
			// なぜかloaderPathが取得できていないため、実際に出力されるJSONトレースを参考にしてpathから真似てみる
			methodName = "getPath";
			String path = ((StringReference)callInstanceMethod(vm, thread, methodName, classInfo)).value();
			String declaringClassNameString  = declaringClassName.value().replace(".", "/");
			loaderPath = path.substring(0, path.indexOf(declaringClassNameString)); // pathからクラスの完全限定名以降を全て外したものをprojectPathにしてみる
		}
		return loaderPath;
	}
	
	private IJavaProject findJavaProject(String projectPath) {
		IJavaProject javaProject = null;
		IWorkspaceRoot root =  ResourcesPlugin.getWorkspace().getRoot();
		IProject[] projects = root.getProjects();
		for (IProject project: projects) {
			if (checkProjectPath(project, projectPath)) {
				javaProject = JavaCore.create(project);
				break;				
			}
		}
		return javaProject;
	}

	private boolean checkProjectPath(IProject project, String projectPath) {
		String projectLocation = project.getLocation().toString();
		if (projectPath.startsWith(projectLocation)) {
			String[] projectPathSplit = projectPath.split(projectLocation);
			return (projectPathSplit[1].charAt(0) == '/'); // プロジェクト名の前方一致による他プロジェクトとの誤判定を避ける
		}
		return false;
	}

	private void openInJavaEditor(IType type, IMethod method) {
		try {
			if (type != null && method != null) {
				IEditorPart editor = JavaUI.openInEditor(type);
				if (!type.isLocal() && !type.isMember()) {
					JavaUI.revealInEditor(editor, (IJavaElement)method);
				}				
			}
		} catch (PartInitException | JavaModelException e) {
			e.printStackTrace();
		}
	}
	
	private IMethod findIMethod(VirtualMachine vm, ThreadReference thread, IType type, Value methodExecution) throws InvalidTypeException,
			ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		IMethod method = null;
		if (type != null) {
			StringReference methodSignature = (StringReference)getMethodExecutionSignature(vm, thread, methodExecution);
			method = findIMethod(type, methodSignature.value());
		}
		return method;
	}
	
	private IMethod findIMethod(IType type, String methodSignature) {
		try {
			for (IMethod method: type.getMethods()) {
				if (checkMethodSignature(type, method, methodSignature)) {
					return method;
				}
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return null;
	}
	
	private boolean checkMethodSignature(IType type, IMethod method, String methodSignature) {
		String fqcn = type.getFullyQualifiedName();
		try {
			StringBuilder jdtMethodSignature = new StringBuilder();
			jdtMethodSignature.append((method.isConstructor()) ? (fqcn + "(") : (fqcn + "." + method.getElementName() + "("));
			if (methodSignature.contains(jdtMethodSignature)) {
				// 部分一致していた場合に限り、引数リストを加えた上でも一致するかどうかを判定 (オーバーロードによる誤判定を避ける)
				jdtMethodSignature.append(String.join(",", parseFQCNParameters(type, method)) + ")"); // 全引数のシグネチャを完全限定クラス名に変換して,で区切った文字列
				return methodSignature.contains(jdtMethodSignature);
			}
		} catch (JavaModelException e) {
			e.printStackTrace();
		}
		return false;
	}

	/**
	 * IMethodから取得できる全引数のシグネチャを完全限定クラス名に置き換えて格納したリストを返す
	 * @param type
	 * @param method
	 * @return
	 */
	private List<String> parseFQCNParameters(IType type, IMethod method) throws JavaModelException {
		List<String> parameters = new ArrayList<>();
		for (String parameterType : method.getParameterTypes()) {
			String readableType = Signature.toString(parameterType); // シグネチャの文字列を型名の文字列に変換 (配列は後ろに[]がつく)
			String[] readableTypeSplit = {"", ""}; // 型名と[]の分割用
			int firstBracketIndex = readableType.indexOf("[]");
			final int NO_BRACKET_INDEX = -1;
			if (firstBracketIndex == NO_BRACKET_INDEX) {
				readableTypeSplit[0] = readableType; // 型名
			} else {
				readableTypeSplit[0] = readableType.substring(0, firstBracketIndex); // 型名
				readableTypeSplit[1] = readableType.substring(firstBracketIndex); // 型名の後ろの[]全て
			}
			String[][] resolveType = type.resolveType(readableTypeSplit[0]); // パッケージ名とクラス名の組み合わせを取得
			if (resolveType != null) {
				if (resolveType[0][0].isEmpty()) {
					readableTypeSplit[0] = (resolveType[0][1]); // デフォルトパッケージの場合はパッケージ名と.は入れない
				} else {
					readableTypeSplit[0] = (resolveType[0][0] + "." + resolveType[0][1]); // 完全限定クラス名	
				}
			} 
			parameters.add(readableTypeSplit[0] + readableTypeSplit[1]);
		}
		return parameters;
	}
	
	/**
	 * パッケージ名とクラス名とメソッド名と引数を指定してそのクラスメソッドを呼び出す
	 * @param vm
	 * @param thread
	 * @param packageName 呼びたしたいメソッドがあるクラスのパッケージ名 
	 * @param className 呼び出したいメソッドがあるクラス名
	 * @param methodName 呼び出したいメソッド名 (static)
	 * @param args 呼び出したいメソッドに渡す引数(Value のクラス型で可変長)
	 * @return 呼び出したメソッドからの戻り値(Value)
	 */
	private Value callStaticMethod(VirtualMachine vm, ThreadReference thread, String packageName, String className, String methodName, Value... args) 
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		List<ReferenceType> classes = vm.classesByName(packageName + "." + className); // クラス名 (完全限定名)
		ClassType type = (ClassType)classes.get(0);
		List<Method> methodsByName = type.methodsByName(methodName);
		List<Value> argList = Arrays.asList(args); // メソッドに渡す引数のリスト
		return type.invokeMethod(thread, methodsByName.get(0), argList, thread.INVOKE_SINGLE_THREADED);	// デバッグ中のプログラム内のメソッドを呼び出す
	}
	
	/**
	 * メソッド名とObjectReferenceと引数を指定してそのオブジェクトのインスタンスメソッドを呼び出す
	 * @param vm
	 * @param thread
	 * @param methodName 呼び出したいメソッド名
	 * @param obj receiverのインスタンスに対応するObjectReference
	 * @param args 呼び出したいメソッドに渡す引数(Value のクラス型で可変長)
	 * @return 呼び出したメソッドからの戻り値(Value)
	 */
	private Value callInstanceMethod(VirtualMachine vm, ThreadReference thread, String methodName, ObjectReference obj, Value... args) 
			throws InvalidTypeException, ClassNotLoadedException, InvocationException, IncompatibleThreadStateException {
		ClassType type = (ClassType)obj.type();
		List<Method> methodsByName = type.methodsByName(methodName);
		List<Value> argList = Arrays.asList(args); // メソッドに渡す引数のリスト
		return obj.invokeMethod(thread, methodsByName.get(0), argList, thread.INVOKE_SINGLE_THREADED);	// デバッグ中のプログラム内のメソッドを呼び出す
	}

	/**
	 * 引数で渡されたValueクラスを表示する<br>
	 * (Valueクラスの子クラスから対応する基本型やString型が取れることの確認を兼ねている)
	 * @param indent インデント
	 * @param msg Valueの表示前に入れるメッセージ
	 * @param value 表示させたいValue
	 * @param isNewLine このメソッドによる表示をする際に改行をしてから表示するか
	 */
	private void printValue(String indent, String msg, Value value, boolean isNewLine) {		
		System.out.print(((isNewLine) ? "\n" : "") + indent + msg);
		printValue(value);
	}

	/**
	 * 引数で渡されたValueクラスを表示する<br>
	 * (Valueクラスの子クラスから対応する基本型やString型が取れることの確認を兼ねている)
	 * @param value
	 */
	private void printValue(Value value) {
		if (value == null) {
			System.out.print("nullだよ!");
			return;
		}		
		if (value instanceof StringReference) {
			String log = ((StringReference)value).value();
			System.out.print(log);
		} else if (value instanceof IntegerValue) {
			int log = ((IntegerValue)value).value();
			System.out.print(log);
		} else if (value instanceof LongValue) {
			long log = ((LongValue)value).value();
			System.out.print(log);
		} else if (value instanceof BooleanValue) {
			boolean log = ((BooleanValue)value).value();
			System.out.print(log);
		} else if (value instanceof DoubleValue) {
			double log = ((DoubleValue)value).value();
			System.out.print(log);
		} else if (value instanceof FloatValue) {
			float log = ((FloatValue)value).value();
			System.out.print(log);
		} else if (value instanceof ShortValue) {
			short log = ((ShortValue)value).value();
			System.out.print(log);
		} else if (value instanceof ByteValue) {
			byte log = ((ByteValue)value).value();
			System.out.print(log);
		} else if (value instanceof CharValue) {
			char log = ((CharValue)value).value();
			System.out.print(log);
		} else {
			throw new IllegalArgumentException("value is not Primitive or String");
		}
	}
}