package tbrugz.rtcoverage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.sun.jdi.ArrayReference;
import com.sun.jdi.ClassLoaderReference;
import com.sun.jdi.ClassNotLoadedException;
import com.sun.jdi.Field;
import com.sun.jdi.IncompatibleThreadStateException;
import com.sun.jdi.IntegerValue;
import com.sun.jdi.InvalidTypeException;
import com.sun.jdi.InvocationException;
import com.sun.jdi.Method;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.StringReference;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.Value;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.ClassUnloadEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventIterator;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.ThreadDeathEvent;
import com.sun.jdi.event.ThreadStartEvent;
import com.sun.jdi.event.VMDisconnectEvent;
public class JDIEventProcessor extends Thread {
static Log log = LogFactory.getLog(JDIEventProcessor.class);
static Log logClazz = LogFactory.getLog(JDIEventProcessor.class.getName()+".class");
static Log logThread = LogFactory.getLog(JDIEventProcessor.class.getName()+".thread");
static Log logException = LogFactory.getLog(JDIEventProcessor.class.getName()+".ex");
static Log logAnalysis = LogFactory.getLog(JDIEventProcessor.class.getName()+".analysis");
class ShowClassesLoaded extends Thread {
@Override
public void run() {
showJarsUsed();
}
}
//final Set<String> classPathJarsTracked = new TreeSet<String>();
//final Set<String> classesLoaded = new TreeSet<String>();
final Set<String> classesShadowed = new TreeSet<String>();
final Set<String> jarsLoaded = new TreeSet<String>();
final Set<String> jarsInExistence = new TreeSet<String>();
final Set<String> urlsNotFound = new TreeSet<String>();
final Map<String, Set<String>> jarContentsCache = new HashMap<String, Set<String>>();
final Set<String> classloadersWithNoGetURLsMethod = new TreeSet<String>();
//final Set<String> classloadersWithNoGetParentMethod = new TreeSet<String>();
final Map<String, String> classesLoadedClassLoaders = new TreeMap<String, String>();
final Map<String, String> parentClassLoaders = new HashMap<String, String>();
final Map<String, String> classLoaderNames = new HashMap<String, String>();
//Set<String> jarsWithManifestTracked = new TreeSet<String>();
EventQueue eventQueue;
//FileFinder ff = new FileFinder(null, null);
long runningTime = 0;
long eventTime = 0;
public JDIEventProcessor(EventQueue eventQueue) {
super();
this.eventQueue = eventQueue;
start();
}
@Override
public void run() {
long initTime = System.currentTimeMillis();
try {
//so if the program is interrupter it still shows (hopefully) the classes/jars used
Thread shutdownHook = new ShowClassesLoaded();
Runtime.getRuntime().addShutdownHook(shutdownHook);
boolean stop = false;
do {
EventSet eventSet = eventQueue.remove();
long eventIniTime = System.currentTimeMillis();
//int resumeVMtimes = 0;
for(EventIterator i = eventSet.eventIterator(); i.hasNext(); ) {
Event evt = i.nextEvent();
if(evt instanceof ClassPrepareEvent) {
ClassPrepareEvent cpe = (ClassPrepareEvent) evt;
ReferenceType refType = cpe.referenceType();
logClazz.debug("Loading: " + refType.name() + " [cl:" + refType.classLoader()+"]");
loadClassDebug(refType, cpe.thread());
} else if(evt instanceof ClassUnloadEvent) {
ClassUnloadEvent cue = (ClassUnloadEvent) evt;
logClazz.debug("Unloading: " + cue.className());
} else if(evt instanceof ThreadStartEvent) {
ThreadStartEvent tse = (ThreadStartEvent) evt;
threadEventDebug(tse.thread(), "Thread Start");
//logThread.debug("Thread Start: " + tse.thread().name());
} else if(evt instanceof ThreadDeathEvent) {
ThreadDeathEvent tde = (ThreadDeathEvent) evt;
threadEventDebug(tde.thread(), "Thread Death");
//resumeVMtimes++;
//tde.virtualMachine().resume();
//logThread.debug("Thread Death: " + tde.thread().name());
//SuspendVMTimed svt = new SuspendVMTimed(tde.virtualMachine(), 1000);
} else if(evt instanceof ExceptionEvent) {
ExceptionEvent exe = (ExceptionEvent) evt;
exceptionEventDebug(exe);
} else if(evt instanceof VMDisconnectEvent) {
log.debug("VMDisconnectEvent!");
stop = true;
}
}
eventTime += (System.currentTimeMillis() - eventIniTime);
//for(int i=0;i<resumeVMtimes;i++) {
// eventQueue.virtualMachine().resume();
//}
//resumeVMtimes = 0;
eventSet.resume();
//eventQueue.virtualMachine().resume();
} while(!stop);
}
catch(Throwable oops) {
oops.printStackTrace();
}
finally {
runningTime = (System.currentTimeMillis()-initTime);
log.info("running time: "+runningTime+"ms ; event treatment time: "+eventTime+
" ; ratio: "+((double)eventTime/(double)runningTime));
//showJarsUsed();
}
}
void loadClassDebug(ReferenceType rt, ThreadReference thr) {
ClassLoaderReference clr = rt.classLoader();
if(clr==null) return; //don't deal with bootstrap classloader
try {
String classLoaderClassName = clr.referenceType().name();
IntegerValue vHashCode = (IntegerValue) simpleInvokeVMMethod(thr, clr, "hashCode");
String iHashCode = Integer.toHexString(vHashCode.value());
classJarsDebug(rt, thr, clr, classLoaderClassName, iHashCode);
classloaderGetParentDebug(thr, clr, classLoaderClassName, iHashCode);
} catch(Exception e) {
e.printStackTrace();
}
}
void classJarsDebug(ReferenceType rt, ThreadReference thr, ClassLoaderReference clr, String classLoaderClassName, String iHashCode) {
try {
Value vURLs = null;
String pathFound = null;
if(classloadersWithNoGetURLsMethod.contains(classLoaderClassName)) { return; }
try {
vURLs = simpleInvokeVMMethod(thr, clr, "getURLs");
}
catch(NoSuchMethodException nsme) {
logClazz.warn(nsme.getMessage());
classloadersWithNoGetURLsMethod.add(classLoaderClassName);
}
if(vURLs==null) { return; }
//logClazz.debug(" CL-URL:"+vURLs.toString()+"/"+vURLs.type());
ArrayReference arURLs = (ArrayReference) vURLs;
List<Value> values = arURLs.getValues();
//XXX: get all StringReferences first, then find() in new thread (so method returns and eventSet may be resumed)
for(Value vURL: values) {
ObjectReference or = (ObjectReference) vURL;
//logClazz.debug(" u:"+vURL.toString()+"/"+vURL.type());
//Value v3 = simpleInvokeVMMethod(thr, or, "toString");
StringReference vURLstr = (StringReference) simpleInvokeVMMethod(thr, or, "toString");
String svURLstr = vURLstr.value();
logClazz.debug(" url: "+svURLstr);
//classPathJarsTracked.add(sr.value());
String pathFoundTmp = find(svURLstr, rt.name());
if(pathFound==null) {
pathFound = pathFoundTmp;
}
//XXX: option to not test for shadowed classes (for performance improvement)
else {
if(pathFoundTmp!=null && !pathFound.equals(pathFoundTmp)) {
classesShadowed.add(pathFoundTmp);
}
}
}
if(pathFound!=null) {
//classesLoaded.add(pathFound);
classesLoadedClassLoaders.put(pathFound, iHashCode);
String jar = pathFound.substring(0, pathFound.indexOf(ZIP_FILE_SEPARATOR));
jarsLoaded.add(jar);
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
void classloaderGetParentDebug(ThreadReference thr, ClassLoaderReference clr, String classLoaderClassName, String iHashCode) throws InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException {
try {
if(classLoaderNames.get(iHashCode)!=null) { return; }
StringReference vToString = (StringReference) simpleInvokeVMMethod(thr, clr, "toString");
String clrToString = vToString.value();
classLoaderNames.put(iHashCode, clrToString);
Value vParent = simpleInvokeVMMethod(thr, clr, "getParent");
ObjectReference oParent = (ObjectReference) vParent;
if(oParent!=null) {
StringReference vParentToString = (StringReference) simpleInvokeVMMethod(thr, oParent, "toString");
String parentToString = vParentToString.value();
IntegerValue vParentHashCode = (IntegerValue) simpleInvokeVMMethod(thr, oParent, "hashCode");
String iParentHashCode = Integer.toHexString(vParentHashCode.value());
parentClassLoaders.put(iHashCode, iParentHashCode);
classLoaderNames.put(iParentHashCode, parentToString);
}
}
catch(NoSuchMethodException nsme) {
//classloadersWithNoGetParentMethod.add(classLoaderClassName);
log.warn(nsme.getMessage());
}
}
void threadEventDebug(ThreadReference thr, String message) {
try {
logThread.debug(message+": " + thr.name());
}
catch(ObjectCollectedException e) {
log.warn("objectcollected: "+e);
}
catch(RuntimeException e) {
log.warn("exception: "+e);
}
}
void exceptionEventDebug(ExceptionEvent exc) {
try {
ReferenceType rt = exc.exception().referenceType();
Field field = rt.fieldByName("detailMessage");
//detailMessage
Value v = exc.exception().getValue(field);
if(exc.catchLocation()!=null) {
logException.debug("CaughtEx: [" + exc.location() + " | catchLoc: " + exc.catchLocation() + "] "+exc.exception().referenceType().name()+": "+v.toString());
}
else {
logException.debug("UncaughtEx: [" + exc.location() + "] "+exc.exception().referenceType().name()+": "+v.toString());
}
}
catch(RuntimeException e) {
log.warn("ExceptionEvent: runtime-exception: "+e);
}
}
//XXX: cache of Method by RT+methodNameString
Value simpleInvokeVMMethod(ThreadReference thr, ObjectReference or, String method) throws InvalidTypeException, ClassNotLoadedException, IncompatibleThreadStateException, InvocationException, NoSuchMethodException {
ReferenceType ort = or.referenceType();
List<Method> methods = ort.methodsByName(method);
if(methods.size()<=0) {
throw new NoSuchMethodException("No '"+method+"' method on remote class '"+ort.name()+"'");
//logClazz.warn("No '"+method+"' method on remote class '"+ort.name()+"'");
//return null;
}
Method remoteMethod = methods.get(0);
return or.invokeMethod(thr, remoteMethod, new ArrayList<Value>(), 0);
}
public static final String ZIP_FILE_SEPARATOR = "!/";
String find(String surl, String className) throws ZipException, IOException {
URL url = new URL(surl);
String file = url.getFile();
file = URLDecoder.decode(file, "UTF-8");
//file = file.replaceAll("%20", " ");
File baseFile = new File(file);
String baseFileCanonicalPath = baseFile.getCanonicalPath();
if(urlsNotFound.contains(baseFileCanonicalPath)) { return null; }
if(!baseFile.exists()) {
logClazz.warn("find(): not dir nor file...: "+baseFile);
urlsNotFound.add(baseFile.getCanonicalPath());
return null;
}
//file = file.substring(1); //XXX removes initial "/" - windows only?
//logClazz.debug(" file: "+file);
int i = className.lastIndexOf('.');
String simpleClassName = className.substring(i+1);
String pathName = className.substring(0,i);
pathName = pathName.replace('.', '/');
String fullClassName = pathName+"/"+simpleClassName+".class";
File fileToSearch = new File(file+'/'+pathName);
File jarFileToSearch = baseFile;
logClazz.trace(" find(): simpleClassname: "+simpleClassName+"; path: "+pathName+"; file: "+file+"; fullClassName: "+fullClassName);
String pathFound = "";
if(fileToSearch.isDirectory()) {
String[] files = fileToSearch.list();
for(String s: files) {
String filename = fileToSearch.getAbsolutePath()+File.separator+s;
if(s.contains(simpleClassName)) {
pathFound = fileToSearch.getAbsolutePath();
logClazz.debug(" find(): found on: "+filename);
return pathFound;
}
}
return null;
}
else if(jarFileToSearch.exists()) {
jarsInExistence.add(jarFileToSearch.getCanonicalPath());
JarFile jar = new JarFile(jarFileToSearch);
pathFound = findInJar(jar, fullClassName);
if(pathFound!=null) { return pathFound; }
//search in jars pointed by manifest.mf
File parent = jarFileToSearch.getParentFile();
Manifest manifest = jar.getManifest();
if(manifest!=null) {
logClazz.debug(" find(): searching in manifest's class-path jars..."); //:: "+manifest);
Attributes atts = manifest.getMainAttributes(); //"Class-Path");
String cp = atts.getValue(Attributes.Name.CLASS_PATH);
String[] cps = cp.split("[ ]+");
//StringBuffer sb = new StringBuffer();
//sb.append(" jim: ");
for(String s: cps) {
//cada um desses eh um jar...
String jarInManifestPath = parent.getCanonicalPath()+'/'+s;
File jarInManifestPathFile = new File(jarInManifestPath);
jarInManifestPath = jarInManifestPathFile.getCanonicalPath();
if(urlsNotFound.contains(jarInManifestPath)) { continue; }
try {
JarFile jarFromManifest = new JarFile(jarInManifestPath); //aqui ocorre o FNFE
//sb.append(s+"/"+jarFromManifest.getName()+" ; ");
jarsInExistence.add(jarInManifestPath);
String pathFoundTmp = findInJar(jarFromManifest, fullClassName);
//if path already found do not search in other jars
if(pathFound==null) {
pathFound = pathFoundTmp;
}
}
catch(FileNotFoundException fnfe) {
logClazz.trace(" find(): FNFE: "+fnfe);
urlsNotFound.add(jarInManifestPath);
//fnfe.printStackTrace();
}
}
if(pathFound!=null) {
//logClazz.debug(sb.toString());
return pathFound;
}
//logClazz.debug(sb.toString());
//out.println(" atts-CP:: "+atts.getValue(Attributes.Name.CLASS_PATH));
}
return null;
}
else {
//not dir nor jar...
logClazz.warn("find()-arrrg!: not dir nor file...: "+fileToSearch);
//logClazz.warn("not dir nor file...: "+fileToSearch+"/"+jarFileToSearch);
urlsNotFound.add(fileToSearch.getCanonicalPath());
return null; //pathFound;
}
}
String findInJar(JarFile jar, String sClassName) throws IOException {
String jarFileToSearchAbsPath = new File(jar.getName()).getCanonicalPath();
Set<String> jarContents = jarContentsCache.get(jarFileToSearchAbsPath);
if(jarContents==null) {
jarContents = new HashSet<String>();
Enumeration<JarEntry> entries = (Enumeration<JarEntry>) jar.entries();
while(entries.hasMoreElements()) {
JarEntry ze = entries.nextElement();
jarContents.add(ze.getName());
}
jarContentsCache.put(jarFileToSearchAbsPath, jarContents);
}
for(String entry: jarContents) {
if(entry.contains(sClassName)) {
String outfilename = jarFileToSearchAbsPath+ZIP_FILE_SEPARATOR+entry;
logClazz.debug(" findInJar(): found on: "+outfilename+"; className: "+sClassName+"; entry: "+entry);
return outfilename;
}
}
/*String outfilename = jarFileToSearchAbsPath+ZIP_FILE_SEPARATOR+ze.getName();
if(outfilename.contains(sClassName)) {
//pathFound = jarFileToSearch.getCanonicalPath();
logClazz.debug(" found on: "+outfilename);
return outfilename;
//return pathFound;
}*/
return null;
}
String oldfindInJar(JarFile jar, String sClassName) {
Enumeration<JarEntry> entries = (Enumeration<JarEntry>) jar.entries();
String jarFileToSearchAbsPath = jar.getName();
while(entries.hasMoreElements()) {
JarEntry ze = entries.nextElement();
String outfilename = jarFileToSearchAbsPath+ZIP_FILE_SEPARATOR+ze.getName();
//String outfilename = jarFileToSearch.getAbsolutePath()+ZIP_FILE_SEPARATOR+ze.getName();
if(outfilename.contains(sClassName)) {
//pathFound = jarFileToSearch.getCanonicalPath();
logClazz.debug(" oldFindInJar(): found on: "+outfilename);
return outfilename;
//return pathFound;
}
}
return null;
}
void showJarsUsed() {
long iniTime = System.currentTimeMillis();
logAnalysis.info("classes used ["+classesLoadedClassLoaders.size()+"]:");
for(String s: classesLoadedClassLoaders.keySet()) {
logAnalysis.info(" "+s+" [cl:"+classesLoadedClassLoaders.get(s)+"]");
}
/*for(String s: classesLoaded) {
logAnalysis.info(" "+s);
}*/
logAnalysis.info("classes shadowed ["+classesShadowed.size()+"]:");
for(String s: classesShadowed) {
logAnalysis.info(" "+s);
}
logAnalysis.info("classLoaders with no 'getURLs' method ["+classloadersWithNoGetURLsMethod.size()+"]:");
for(String s: classloadersWithNoGetURLsMethod) {
logAnalysis.info(" "+s);
}
logAnalysis.info("jars in existence ["+jarsInExistence.size()+"]:");
for(String s: jarsInExistence) {
logAnalysis.info(" "+s);
}
logAnalysis.info("jars not found ["+urlsNotFound.size()+"]:");
for(String s: urlsNotFound) {
logAnalysis.info(" "+s);
}
logAnalysis.info("jars used ["+jarsLoaded.size()+"]:");
for(String s: jarsLoaded) {
logAnalysis.info(" "+s);
}
Set<String> jarsNotUsed = new HashSet<String>();
jarsNotUsed.addAll(jarsInExistence);
jarsNotUsed.removeAll(jarsLoaded);
logAnalysis.info("jars not used ["+jarsNotUsed.size()+"]:");
for(String s: jarsNotUsed) {
logAnalysis.info(" "+s);
}
logAnalysis.info("classloaders ["+classLoaderNames.size()+"]:");
for(String key: classLoaderNames.keySet()) {
logAnalysis.info(" "+key+": "+classLoaderNames.get(key)+" [parent: "+parentClassLoaders.get(key)+"]");
}
logAnalysis.info("-- runned at "+new Date()+", took "+runningTime+"ms [analisys time: "+(System.currentTimeMillis()-iniTime)+"ms]");
}
}