Newer
Older
org.ntlab.traceAnalyzer / org.ntlab.traceAnalyzer / src / org / ntlab / trace / TraceJSON.java
package org.ntlab.trace;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;

public class TraceJSON extends Trace {
	private HashMap<String, ClassInfo> classes = new HashMap<>();
	
	/**
	 * 指定したJSONトレースファイルを解読して Trace オブジェクトを生成する
	 * @param traceFile トレースファイルのパス
	 */
	public TraceJSON(String traceFile) {
		super();
		BufferedReader file;
		try {
			file = new BufferedReader(new FileReader(traceFile));
			// トレースファイル読み込み
			String line = null;
			String[] type;
			String[] classNameData;
			String[] pathData;
			String[] signature;
			String[] receiver;
			String[] arguments;
			String[] lineData;
			String[] threadId;
			String[] thisObj;
			String[] containerObj;
			String[] valueObj;
			String[] returnValue;
			String[] arrayObj;
			String[] thisData;
			String[] containerData;
			String[] valueData;
			String[] returnData;
			String[] arrayData;
			String[] blockIdData;
			String[] incomingsData;
			String[] dimensionData;
			String[] indexData;
			String className;
			String classPath;
			String loaderPath;
			String time;
			String thisObjectId;
			String thisClassName;
			String containerObjectId;
			String containerClassName;
			String valueObjectId;
			String valueClassName;
			String returnClassName;
			String returnObjectId;
			String arrayObjectId;
			String arrayClassName;
			String shortSignature;
			boolean isConstractor = false;
			boolean isCollectionType = false;
			boolean isStatic = false;
			int dimension;
			int index;
			int blockId;
			int incomings;
			int lineNum;
			long timeStamp = 0L;
			ThreadInstance thread = null;
			HashMap<String, Stack<String>> stacks = new HashMap<String, Stack<String>>();
			while ((line = file.readLine()) != null) {
				// トレースファイルの解析
				if (line.startsWith("{\"type\":\"classDef\"")) {
					// クラス定義
					type = line.split(",\"name\":\"");
					classNameData = type[1].split("\",\"path\":\"");
					className = classNameData[0];
					pathData = classNameData[1].split("\",\"loaderPath\":\"");
					classPath = pathData[0].substring(1);								// 先頭の / を取り除く
					loaderPath = pathData[1].substring(1, pathData[1].length() - 3);	// 先頭の / と、末尾の "}, を取り除く
					initializeClass(className, classPath, loaderPath);
				} else if (line.startsWith("{\"type\":\"methodCall\"")) {
					// メソッド呼び出しの呼び出し側
					type = line.split(",\"callerSideSignature\":\"");
					signature = type[1].split("\",\"threadId\":");
					threadId = signature[1].split(",\"lineNum\":");
					lineNum = Integer.parseInt(threadId[1].substring(0, threadId[1].length() - 2));	// 末尾の }, を取り除く
					thread = threads.get(threadId[0]);
					thread.preCallMethod(signature[0], lineNum);
				} else if (line.startsWith("{\"type\":\"methodEntry\"")) {
					// メソッド呼び出し
					type = line.split("\"signature\":\"");
					signature = type[1].split("\",\"receiver\":");
					receiver = signature[1].split(",\"args\":");
					arguments = receiver[1].split(",\"threadId\":");
					threadId = arguments[1].split(",\"time\":");
					thisData = parseClassNameAndObjectId(receiver[0]);
					thisClassName = thisData[0];
					thisObjectId = thisData[1];
					isConstractor = false;
					isStatic = false;
					if (signature[0].contains("static ")) {
						isStatic = true;
					}
					thread = threads.get(threadId[0]);
					time = threadId[1].substring(0, threadId[1].length() - 2);			// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					Stack<String> stack;
					if (thread == null) {
						thread = new ThreadInstance(threadId[0]);
						threads.put(threadId[0], thread);
						stack = new Stack<String>();
						stacks.put(threadId[0], stack);
					} else {
						stack = stacks.get(threadId[0]);
					}
					stack.push(signature[0]);
					// メソッド呼び出しの設定
					thread.callMethod(signature[0], null, thisClassName, thisObjectId, isConstractor, isStatic, timeStamp);
					// 引数の設定
					thread.setArgments(parseArguments(arguments));
				} else if (line.startsWith("{\"type\":\"constructorEntry\"")) {
					// コンストラクタ呼び出し
					type = line.split("\"signature\":\"");
					signature = type[1].split("\",\"class\":\"");
					receiver = signature[1].split("\",\"args\":");
					arguments = receiver[1].split(",\"threadId\":");
					threadId = arguments[1].split(",\"time\":");
					thisClassName = receiver[0];
					thisObjectId = "0";
					isConstractor = true;
					isStatic = false;
					thread = threads.get(threadId[0]);
					time = threadId[1].substring(0, threadId[1].length() - 2);			// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					Stack<String> stack;
					if (thread == null) {
						thread = new ThreadInstance(threadId[0]);
						threads.put(threadId[0], thread);
						stack = new Stack<String>();
						stacks.put(threadId[0], stack);
					} else {
						stack = stacks.get(threadId[0]);
					}
					stack.push(signature[0]);
					// メソッド呼び出しの設定
					thread.callMethod(signature[0], null, thisClassName, thisObjectId, isConstractor, isStatic, timeStamp);
					// 引数の設定
					thread.setArgments(parseArguments(arguments));
				} else if (line.startsWith("{\"type\":\"methodExit\"")) {
					// メソッドからの復帰
					type = line.split(",\"shortSignature\":\"");
					signature = type[1].split("\",\"receiver\":");
					receiver = signature[1].split(",\"returnValue\":");
					returnValue = receiver[1].split(",\"threadId\":");
					threadId = returnValue[1].split(",\"time\":");
					thisData = parseClassNameAndObjectId(receiver[0]);
					thisClassName = thisData[0];
					thisObjectId = thisData[1];
					returnData = parseClassNameAndObjectId(returnValue[0]);
					returnClassName = returnData[0];
					returnObjectId = returnData[1];
					shortSignature = signature[0];
					time = threadId[1].substring(0, threadId[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					Stack<String> stack = stacks.get(threadId[0]);
					if (!stack.isEmpty()) {
						String line2 = stack.peek();
						if (line2.endsWith(shortSignature)) {
							stack.pop();
						} else {
							do {
								line2 = stack.pop();
								thread.terminateMethod();
							} while (!stack.isEmpty() && !line2.endsWith(shortSignature));
						}
						thread = threads.get(threadId[0]);
						ObjectReference returnVal = new ObjectReference(returnObjectId, returnClassName);					
						isCollectionType = false;
						if(thisClassName.contains("java.util.List")
								|| thisClassName.contains("java.util.Vector")
								|| thisClassName.contains("java.util.Iterator")
								|| thisClassName.contains("java.util.ListIterator")
								|| thisClassName.contains("java.util.ArrayList")
								|| thisClassName.contains("java.util.Stack")
								|| thisClassName.contains("java.util.Hash")
								|| thisClassName.contains("java.util.Map")
								|| thisClassName.contains("java.util.Set")
								|| thisClassName.contains("java.util.Linked")
								|| thisClassName.contains("java.lang.Thread")) {
							isCollectionType = true;
						}
						// メソッドからの復帰の設定
						thread.returnMethod(returnVal, thisObjectId, isCollectionType, timeStamp);
					}
				} else if (line.startsWith("{\"type\":\"constructorExit\"")) {
					// コンストラクタからの復帰
					type = line.split(",\"shortSignature\":\"");
					signature = type[1].split("\",\"returnValue\":");
					returnValue = signature[1].split(",\"threadId\":");
					threadId = returnValue[1].split(",\"time\":");
					returnData = parseClassNameAndObjectId(returnValue[0]);
					thisClassName = returnClassName = returnData[0];
					thisObjectId = returnObjectId = returnData[1];
					time = threadId[1].substring(0, threadId[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					Stack<String> stack = stacks.get(threadId[0]);
					shortSignature = signature[0];
					if (!stack.isEmpty()) {
						String line2 = stack.peek();
						if (line2.endsWith(shortSignature)) {
							stack.pop();
						} else {
							do {
								line2 = stack.pop();
								thread.terminateMethod();
							} while (!stack.isEmpty() && !line2.endsWith(shortSignature));
						}
						thread = threads.get(threadId[0]);
						ObjectReference returnVal = new ObjectReference(returnObjectId, returnClassName);					
						isCollectionType = false;
						if(thisClassName.contains("java.util.List")
								|| thisClassName.contains("java.util.Vector")
								|| thisClassName.contains("java.util.Iterator")
								|| thisClassName.contains("java.util.ListIterator")
								|| thisClassName.contains("java.util.ArrayList")
								|| thisClassName.contains("java.util.Stack")
								|| thisClassName.contains("java.util.Hash")
								|| thisClassName.contains("java.util.Map")
								|| thisClassName.contains("java.util.Set")
								|| thisClassName.contains("java.util.Linked")
								|| thisClassName.contains("java.lang.Thread")) {
							isCollectionType = true;
						}
						// メソッドからの復帰の設定
						thread.returnMethod(returnVal, thisObjectId, isCollectionType, timeStamp);
					}
				} else if (line.startsWith("{\"type\":\"fieldGet\"")) {
					// フィールドアクセス
					type = line.split("\"this\":");
					thisObj = type[1].split(",\"container\":");
					containerObj = thisObj[1].split(",\"value\":");
					valueObj = containerObj[1].split(",\"threadId\":");
					threadId = valueObj[1].split(",\"lineNum\":");
					lineData = threadId[1].split(",\"time\":");
					thisData = parseClassNameAndObjectId(thisObj[0]);
					thisClassName = thisData[0];
					thisObjectId = thisData[1];
					containerData = parseClassNameAndObjectId(containerObj[0]);
					containerClassName = containerData[0];
					containerObjectId = containerData[1];
					valueData = parseClassNameAndObjectId(valueObj[0]);
					valueClassName = valueData[0];
					valueObjectId = valueData[1];
					thread = threads.get(threadId[0]);
					lineNum = Integer.parseInt(lineData[0]);
					time = lineData[1].substring(0, lineData[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					// フィールドアクセスの設定
					if (thread != null) thread.fieldAccess(valueClassName, valueObjectId, containerClassName, containerObjectId, thisClassName, thisObjectId, lineNum, timeStamp);
				} else if (line.startsWith("{\"type\":\"fieldSet\"")) {
					// フィールド更新
					type = line.split(",\"container\":");
					containerObj = type[1].split(",\"value\":");
					valueObj = containerObj[1].split(",\"threadId\":");
					threadId = valueObj[1].split(",\"lineNum\":");
					lineData = threadId[1].split(",\"time\":");
					containerData = parseClassNameAndObjectId(containerObj[0]);
					containerClassName = containerData[0];
					containerObjectId = containerData[1];
					valueData = parseClassNameAndObjectId(valueObj[0]);
					valueClassName = valueData[0];
					valueObjectId = valueData[1];
					thread = threads.get(threadId[0]);
					lineNum = Integer.parseInt(lineData[0]);
					time = lineData[1].substring(0, lineData[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					// フィールド更新の設定
					if (thread != null) thread.fieldUpdate(valueClassName, valueObjectId, containerClassName, containerObjectId, lineNum, timeStamp);
				} else if (line.startsWith("{\"type\":\"arrayCreate\"")) {
					// 配列生成
					type = line.split(",\"array\":");
					arrayObj = type[1].split(",\"dimension\":");
					arrayData = parseClassNameAndObjectId(arrayObj[0]);
					arrayClassName = arrayData[0];
					arrayObjectId = arrayData[1];
					dimensionData = arrayObj[1].split(",\"threadId\":");
					dimension = Integer.parseInt(dimensionData[0]);
					threadId = dimensionData[1].split(",\"lineNum\":");
					thread = threads.get(threadId[0]);
					lineData = threadId[1].split(",\"time\":");
					lineNum = Integer.parseInt(lineData[0]);
					time = lineData[1].substring(0, lineData[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					if (thread != null) thread.arrayCreate(arrayClassName, arrayObjectId, dimension, lineNum, timeStamp);
				} else if (line.startsWith("{\"type\":\"arraySet\"")) {
					// 配列要素への代入
					type = line.split(",\"array\":");
					arrayObj = type[1].split(",\"index\":");
					arrayData = parseClassNameAndObjectId(arrayObj[0]);
					arrayClassName = arrayData[0];
					arrayObjectId = arrayData[1];
					indexData = arrayObj[1].split(",\"value\":");
					index = Integer.parseInt(indexData[0]);
					valueObj = indexData[1].split(",\"threadId\":");
					valueData = parseClassNameAndObjectId(valueObj[0]);
					valueClassName = valueData[0];
					valueObjectId = valueData[1];
					threadId = valueObj[1].split(",\"time\":");
					thread = threads.get(threadId[0]);
					time = threadId[1].substring(0, threadId[1].length() - 2);		// 末尾の }, を取り除く					
					timeStamp = Long.parseLong(time);
					if (thread != null) thread.arraySet(arrayClassName, arrayObjectId, index, valueClassName, valueObjectId, 0, timeStamp);
				} else if (line.startsWith("{\"type\":\"arrayGet\"")) {
					// 配列要素の参照
					type = line.split(",\"array\":");
					arrayObj = type[1].split(",\"index\":");
					arrayData = parseClassNameAndObjectId(arrayObj[0]);
					arrayClassName = arrayData[0];
					arrayObjectId = arrayData[1];
					indexData = arrayObj[1].split(",\"value\":");
					index = Integer.parseInt(indexData[0]);
					valueObj = indexData[1].split(",\"threadId\":");
					valueData = parseClassNameAndObjectId(valueObj[0]);
					valueClassName = valueData[0];
					valueObjectId = valueData[1];
					threadId = valueObj[1].split(",\"time\":");
					thread = threads.get(threadId[0]);
					time = threadId[1].substring(0, threadId[1].length() - 2);		// 末尾の }, を取り除く					
					timeStamp = Long.parseLong(time);
					if (thread != null) thread.arrayGet(arrayClassName, arrayObjectId, index, valueClassName, valueObjectId, 0, timeStamp);
				} else if (line.startsWith("{\"type\":\"blockEntry\"")) {
					// ブロックの開始
					type = line.split(",\"methodSignature\":\"");
					signature = type[1].split("\",\"blockId\":");
					blockIdData = signature[1].split(",\"incomings\":");
					blockId = Integer.parseInt(blockIdData[0]);
					incomingsData = blockIdData[1].split(",\"threadId\":");
					incomings = Integer.parseInt(incomingsData[0]);
					threadId = incomingsData[1].split(",\"lineNum\":");
					thread = threads.get(threadId[0]);
					lineData = threadId[1].split(",\"time\":");
					lineNum = Integer.parseInt(lineData[0]);
					time = lineData[1].substring(0, lineData[1].length() - 2);		// 末尾の }, を取り除く
					timeStamp = Long.parseLong(time);
					if (thread != null) thread.blockEnter(blockId, incomings, lineNum, timeStamp);
				}
			}
			file.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * クラス名とオブジェクトIDを表すJSONオブジェクトを解読する
	 * @param classNameAndObjectIdJSON トレースファイル内のJSONオブジェクト
	 * @return
	 */
	protected String[] parseClassNameAndObjectId(String classNameAndObjectIdJSON) {
		// 先頭の {"class":" の10文字と末尾の } を取り除いて分離
		return classNameAndObjectIdJSON.substring(10, classNameAndObjectIdJSON.length() - 1).split("\",\"id\":");
	}
	
	/**
	 * 引数を表すJSON配列を解読する
	 * @param arguments
	 * @return
	 */
	protected ArrayList<ObjectReference> parseArguments(String[] arguments) {
		String[] argData;
		argData = arguments[0].substring(1, arguments[0].length() - 1).split(",");		// 先頭の [ と末尾の ] を取り除く
		ArrayList<ObjectReference> argumentsData = new ArrayList<ObjectReference>();
		for (int k = 0; k < argData.length - 1; k += 2) {
			argumentsData.add(new ObjectReference(argData[k+1].substring(5, argData[k+1].length() - 1), argData[k].substring(10, argData[k].length() - 1)));
		}
		return argumentsData;
	}
	
	public void initializeClass(String name, String path, String loaderPath) {
		classes.put(name, new ClassInfo(name, path, loaderPath));
	}
	
	public ClassInfo getClassInfo(String className) {
		return classes.get(className);
	}
	
	public TracePoint getArraySetTracePoint(final Reference ref, TracePoint before) {
		final TracePoint start = before.duplicate();
		before = traverseStatementsInTraceBackward(new IStatementVisitor() {
				@Override
				public boolean preVisitStatement(Statement statement) {
					if (statement instanceof ArrayUpdate) {
						ArrayUpdate arraySet = (ArrayUpdate)start.getStatement();
						String srcObjId = ref.getSrcObjectId();
						String dstObjId = ref.getDstObjectId();
						String srcClassName = ref.getSrcClassName();
						String dstClassName = ref.getDstClassName();
						if ((srcObjId != null && srcObjId.equals(arraySet.getArrayObjectId())) 
							|| (srcObjId == null || isNull(srcObjId)) && srcClassName.equals(arraySet.getArrayClassName())) {
							if ((dstObjId != null && dstObjId.equals(arraySet.getValueObjectId()))
								|| ((dstObjId == null || isNull(dstObjId)) && dstClassName.equals(arraySet.getValueClassName()))) {
								if (srcObjId == null) {
									ref.setSrcObjectId(arraySet.getArrayObjectId());
								} else if (srcClassName == null) {
									ref.setSrcClassName(arraySet.getArrayClassName());
								}
								if (dstObjId == null) {
									ref.setDstObjectId(arraySet.getValueObjectId());
								} else if (dstClassName == null) {
									ref.setDstClassName(arraySet.getValueClassName());
								}
								return true;
							}
						}
					}
					return false;
				}
				@Override
				public boolean postVisitStatement(Statement statement) { return false; }
			}, start);
		if (before != null) {
			return before;
		}
		return null;
	}
	
	/**
	 * 実行された全ブロックを取得する
	 * @return 全ブロック(メソッド名:ブロックID)
	 */
	public HashSet<String> getAllBlocks() {
		final HashSet<String> blocks = new HashSet<String>();
		Iterator<String> threadsIterator = threads.keySet().iterator();
		for (; threadsIterator.hasNext();) {
			ThreadInstance thread = threads.get(threadsIterator.next());
			thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() {
				@Override
				public boolean preVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean postVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean preVisitMethodExecution(MethodExecution methodExecution) {
					for (Statement s: methodExecution.getStatements()) {
						if (s instanceof BlockEnter) {
							blocks.add(methodExecution.getSignature() + ":" + ((BlockEnter)s).getBlockId());
						}
					}
					return false;
				}
				@Override
				public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) {
					return false;
				}
			});
		}	
		return blocks;
	}
	
	/**
	 * マーク内で実行が開始されたブロックを取得する
	 * @param markStart マークの開始時刻
	 * @param markEnd マークの終了時刻
	 * @return 該当するブロック(メソッド名:ブロックID)
	 */
	public HashSet<String> getMarkedBlocks(final long markStart, final long markEnd) {
		final HashSet<String> blocks = new HashSet<String>();
		Iterator<String> threadsIterator = threads.keySet().iterator();
		for (; threadsIterator.hasNext();) {
			ThreadInstance thread = threads.get(threadsIterator.next());
			thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() {
				@Override
				public boolean preVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean postVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean preVisitMethodExecution(MethodExecution methodExecution) {
					if (methodExecution.getExitTime() < markStart) return true;		// 探索終了
					if (methodExecution.getEntryTime() > markEnd) return false;
					for (Statement s: methodExecution.getStatements()) {
						if (s instanceof BlockEnter) {
							long entryTime = ((BlockEnter)s).getTimeStamp();
							if (entryTime >= markStart && entryTime <= markEnd) {
								blocks.add(methodExecution.getSignature() + ":" + ((BlockEnter)s).getBlockId());
							}
						}
					}
					return false;
				}
				@Override
				public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) {
					return false;
				}
			});
		}	
		return blocks;
	}
	
	/**
	 * 実行された全フローを取得する
	 * @return 全フロー(メソッド名:フロー元ブロックID:フロー先ブロックID)
	 */
	public HashSet<String> getAllFlows() {
		final HashSet<String> flows = new HashSet<String>();
		Iterator<String> threadsIterator = threads.keySet().iterator();
		for (; threadsIterator.hasNext();) {
			ThreadInstance thread = threads.get(threadsIterator.next());
			thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() {
				@Override
				public boolean preVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean postVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean preVisitMethodExecution(MethodExecution methodExecution) {
					int prevBlockId = -1;
					for (Statement s: methodExecution.getStatements()) {
						if (s instanceof BlockEnter) {
							int curBlockID = ((BlockEnter)s).getBlockId();
							if (prevBlockId != -1) {
								flows.add(methodExecution.getSignature() + ":" + prevBlockId + ":" + curBlockID);
							} else {
								flows.add(methodExecution.getSignature() + ":" + curBlockID);
							}
							prevBlockId = curBlockID;
						}
					}
					return false;
				}
				@Override
				public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) {
					return false;
				}
			});
		}	
		return flows;
	}
	
	/**
	 * マーク内で実行されたフローを取得する
	 * @param markStart マークの開始時刻
	 * @param markEnd マークの終了時刻
	 * @return 該当するフロー(メソッド名:フロー元ブロックID:フロー先ブロックID)
	 */
	public HashSet<String> getMarkedFlows(final long markStart, final long markEnd) {
		final HashSet<String> flows = new HashSet<String>();
		Iterator<String> threadsIterator = threads.keySet().iterator();
		for (; threadsIterator.hasNext();) {
			ThreadInstance thread = threads.get(threadsIterator.next());
			thread.traverseMethodExecutionsBackward(new IMethodExecutionVisitor() {
				@Override
				public boolean preVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean postVisitThread(ThreadInstance thread) {
					return false;
				}
				@Override
				public boolean preVisitMethodExecution(MethodExecution methodExecution) {
					if (methodExecution.getExitTime() < markStart) return true;		// 探索終了
					if (methodExecution.getEntryTime() > markEnd) return false;
					int prevBlockId = -1;
					for (Statement s: methodExecution.getStatements()) {
						if (s instanceof BlockEnter) {
							long entryTime = ((BlockEnter)s).getTimeStamp();
							int curBlockID = ((BlockEnter)s).getBlockId();
							if (entryTime >= markStart && entryTime <= markEnd) {
								if (prevBlockId != -1) {
									flows.add(methodExecution.getSignature() + ":" + prevBlockId + ":" + curBlockID);
								} else {
									flows.add(methodExecution.getSignature() + ":" + curBlockID);
								}
							}
							prevBlockId = curBlockID;
						}
					}
					return false;
				}
				@Override
				public boolean postVisitMethodExecution(MethodExecution methodExecution, ArrayList<MethodExecution> children) {
					return false;
				}
			});
		}	
		return flows;
	}
}