/*******************************************************************************
* Copyright (c) 2010 Eric Bodden.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eric Bodden - initial API and implementation
******************************************************************************/
package de.bodden.tamiflex.reporting.rt;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
public class ReflLogger {
private static PrintWriter logger;
private static File logFile;
private static class ThreadLocalState {
List<Entry> logEntries = new LinkedList<Entry>();
StackTraceElement[] stackTrace;
}
private static List<ThreadLocalState> perThreadStates = Collections.synchronizedList(new LinkedList<ReflLogger.ThreadLocalState>());
private static ThreadLocal<ThreadLocalState> threadLocalState = new ThreadLocal<ReflLogger.ThreadLocalState>() {
protected ThreadLocalState initialValue() {
ThreadLocalState state = new ThreadLocalState();
perThreadStates.add(state);
return state;
}
};
public static void classNewInstance(boolean entering, Class<?> c) {
StackTraceElement frame = getInvokingFrame();
log(entering, frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassNewInstance,c.getName());
}
private static void log(boolean entering, Object... toPrint) {
ThreadLocalState state = threadLocalState.get();
List<Entry> entries = state.logEntries;
if(entering) {
Entry entry = new Entry(entries.size(),state.stackTrace,flatten(toPrint));
entries.add(entry);
} else {
Entry entry = new Entry(entries.size(),state.stackTrace,flatten(toPrint));
boolean found = false;
for(int i = entries.size()-1; i>=0; i--) {
Entry other = entries.get(i);
if(entry.matchesEarlierEntry(other)) {
other.markAsSucceeded();
found = true;
break;
} else {
if(other.successUnknown())
other.markAsFailed();
}
}
if(!found) {
throw new IllegalStateException("closing entry without matching opening entry:" +entry);
}
}
}
private static String flatten(Object[] toPrint) {
StringWriter sw = new StringWriter();
int i = 0;
for(Object o: toPrint) {
sw.append(o.toString());
if(i<toPrint.length)
sw.append(";");
i++;
}
return sw.toString();
}
public static void classForName(boolean entering, String typeName) {
StackTraceElement frame = getInvokingFrame();
log(entering,frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassForName,handleArrayTypes(typeName));
}
public static void classForName(boolean entering, String typeName, boolean initialize, ClassLoader classLoader) {
StackTraceElement frame = getInvokingFrame();
String classLoaderClassName = classLoader==null ? "null" : classLoader.getClass().getName();
log(entering,frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ClassForNameWithClassLoader,handleArrayTypes(typeName),initialize,classLoaderClassName);
}
public static void constructorNewInstance(boolean entering, Constructor<?> c) {
StackTraceElement frame = getInvokingFrame();
String paramTypes = classesToTypeNames(c.getParameterTypes());
log(entering, frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.ConstructorNewInstance,"void "+c.getDeclaringClass().getName()+".<init>"+paramTypes);
}
public static void methodInvoke(boolean entering, Object receiver, Method m) {
Class<?> receiverClass = Modifier.isStatic(m.getModifiers()) ? m.getDeclaringClass() : receiver.getClass();
try {
//resolve virtual call
Method resolved = null;
Class<?> c = receiverClass;
do {
try {
resolved = c.getDeclaredMethod(m.getName(), m.getParameterTypes());
} catch(NoSuchMethodException e) {
c = c.getSuperclass();
}
} while(resolved==null && c!=null);
if(resolved==null) {
Error error = new Error("Method not found : "+m+" in class "+receiverClass+" and super classes.");
error.printStackTrace();
}
StackTraceElement frame = getInvokingFrame();
String paramTypes = classesToTypeNames(resolved.getParameterTypes());
log(entering,frame.getClassName()+"."+frame.getMethodName(),frame.getLineNumber(),Kind.MethodInvoke,getTypeName(resolved.getReturnType())+" "+resolved.getDeclaringClass().getName()+"."+resolved.getName()+paramTypes);
} catch (Exception e) {
e.printStackTrace();
}
}
private static String classesToTypeNames(Class<?>[] params) {
String paramTypes = "(";
int i=0;
for (Class<?> type : params) {
paramTypes += getTypeName(type);
i++;
if(i<params.length)
paramTypes+= ",";
}
return paramTypes + ")";
}
protected static String handleArrayTypes(String className) {
int arrDepth = 0;
for(int i=0;i<className.length();i++) {
if(className.charAt(i)=='[') {
arrDepth++;
} else {
break;
}
}
className = className.substring(arrDepth);
if(className.endsWith(";")) {
//cut of leading "L" and trailing ";"
className = className.substring(1,className.indexOf(';'));
}
if("B".equals(className))
className= Byte.class.getName();
if("C".equals(className))
className= Character.class.getName();
if("D".equals(className))
className= Double.class.getName();
if("F".equals(className))
className= Float.class.getName();
if("I".equals(className))
className = Integer.class.getName();
if("J".equals(className))
className = Long.class.getName();
if("S".equals(className))
className = Short.class.getName();
if("Z".equals(className))
className = Boolean.class.getName();
if("V".equals(className))
className= Void.class.getName();
for(int i=0; i<arrDepth; i++) {
className += "[]";
}
return className;
}
private static String getTypeName(Class<?> type) {
//copied from java.lang.reflect.Field.getTypeName(Class)
if (type.isArray()) {
try {
Class<?> cl = type;
int dimensions = 0;
while (cl.isArray()) {
dimensions++;
cl = cl.getComponentType();
}
StringBuffer sb = new StringBuffer();
sb.append(cl.getName());
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
return sb.toString();
} catch (Throwable e) { /*FALLTHRU*/ }
}
return type.getName();
}
private static StackTraceElement getInvokingFrame() {
StackTraceElement[] stackTrace = new Exception().getStackTrace();
StackTraceElement outerFrame = null;
int i=0;
for (StackTraceElement frame : stackTrace) {
String c = frame.getClassName();
if(!c.equals(ReflLogger.class.getName())
&& !c.equals(Class.class.getName())
&& !c.equals(Method.class.getName())
&& !c.equals(Constructor.class.getName())) {
outerFrame = frame;
break;
}
i++;
}
StackTraceElement[] truncated = new StackTraceElement[stackTrace.length-i];
System.arraycopy(stackTrace, i, truncated, 0, stackTrace.length-i);
threadLocalState.get().stackTrace = truncated;
return outerFrame;
}
public static synchronized void closeLogger() {
int entriesWritten = 0;
int threads = 0;
List<Entry> allEntries = new LinkedList<Entry>();
for(ThreadLocalState state : perThreadStates) {
for(Entry entry: state.logEntries) {
if(entry.successUnknown()) entry.markAsFailed();
logger.println(entry);
allEntries.add(entry);
}
entriesWritten += state.logEntries.size();
threads++;
logger.println();
}
logger.flush();
logger.close();
System.err.println("\n=============================================");
System.err.println("TamiFlex Reporting Agent Version "+ReflLogger.class.getPackage().getImplementationVersion());
System.err.println("Found "+entriesWritten+" entries in "+threads+" threads.");
System.err.println("Log file written to: "+logFile.getAbsolutePath());
File reportFile = new File(logFile.getParent(),"report.txt");
PrintWriter out;
try {
out = new PrintWriter(new FileOutputStream(reportFile));
Reporter.generateReport(allEntries, out);
out.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
public static void setLogFile(File f) {
logFile = f;
try {
logger = new PrintWriter(f);
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
}
}