import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.jruby.Ruby;
import org.jruby.RubyArray;
import org.jruby.RubyHash;
import org.jruby.runtime.Frame;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.RubyEvent;
import org.jruby.runtime.builtin.IRubyObject;
public class CallsiteHook extends RcovHook {
private static CallsiteHook callsiteHook;
public static CallsiteHook getCallsiteHook() {
if (callsiteHook == null) {
callsiteHook = new CallsiteHook();
}
return callsiteHook;
}
private boolean active;
private RubyHash defsites;
private RubyHash callsites;
private CallsiteHook() {
super();
}
public boolean isActive() {
return active;
}
public boolean isInterestedInEvent(RubyEvent event) {
return event == RubyEvent.CALL || event == RubyEvent.C_CALL;
}
public RubyArray getCallsiteInfo(Ruby runtime) {
RubyArray info = runtime.newArray();
info.add(getCallsites(runtime));
info.add(getDefsites(runtime));
return info;
}
public void setActive(boolean active) {
this.active = active;
}
public RubyHash resetDefsites() {
defsites.clear();
return defsites;
}
public void eventHandler(ThreadContext context, String event, String file, int line, String name, IRubyObject type) {
RubyArray currentMethod = context.getRuntime().newArray();
currentMethod.add(context.getFrameKlazz());
currentMethod.add(context.getRuntime().newSymbol(name));
RubyArray fileLoc = context.getRuntime().newArray();
fileLoc.add(file);
fileLoc.add(Long.valueOf(line));
defsites = getDefsites(context.getRuntime());
if (!context.isWithinTrace()) {
context.setWithinTrace(true);
defsites.put(currentMethod, fileLoc);
callsites = getCallsites(context.getRuntime());
if (!callsites.containsKey(currentMethod)) {
callsites.put(currentMethod, RubyHash.newHash(context.getRuntime()));
}
RubyHash hash = (RubyHash) callsites.get(currentMethod);
RubyArray callerArray = customBacktrace(context);
if (!hash.containsKey(callerArray)) {
hash.put(callerArray, Long.valueOf(0));
}
Long count = (Long) hash.get(callerArray);
long itCount = count.longValue() + 1L;
hash.put(callerArray, Long.valueOf(itCount));
context.setWithinTrace(false);
}
}
private RubyArray customBacktrace(ThreadContext context) {
Frame[] frames = context.createBacktrace(1, false);
RubyArray ary = context.getRuntime().newArray();
ary.addAll(formatBacktrace(context.getRuntime(), frames[frames.length - 1]));
return context.getRuntime().newArray((IRubyObject) ary);
}
/*
* TODO: The logic in this method really needs to be wrapped in a backtrace
* object or something. Then I could fix the file path issues that cause
* test failures.
* @param runtime
* @param backtrace
* @return
*/
private RubyArray formatBacktrace(Ruby runtime, Frame backtrace) {
RubyArray ary = runtime.newArray();
if (backtrace == null) {
ary.add(runtime.getNil());
ary.add(runtime.getNil());
ary.add("");
ary.add(Long.valueOf(0));
} else {
ary.add(backtrace.getKlazz());
ary.add((backtrace.getName() == null ? runtime.getNil() : runtime.newSymbol( backtrace.getName())));
ary.add(backtrace.getFile());
//Add 1 to compensate for the zero offset in the Frame elements.
ary.add(backtrace.getLine() + 1);
}
return ary;
}
private RubyHash getCallsites(Ruby runtime) {
if (this.callsites == null) {
this.callsites = RubyHash.newHash(runtime);
}
return this.callsites;
}
private RubyHash getDefsites(Ruby runtime) {
if (this.defsites == null) {
this.defsites = RubyHash.newHash(runtime);
}
return this.defsites;
}
}