package com.redhat.ceylon.eclipse.core.debug.model;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.internal.utils.Cache;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.model.IDebugTarget;
import org.eclipse.debug.core.model.IProcess;
import org.eclipse.debug.core.model.IThread;
import org.eclipse.jdt.core.IJavaProject;
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.debug.core.IEvaluationRunnable;
import org.eclipse.jdt.debug.core.IJavaArray;
import org.eclipse.jdt.debug.core.IJavaBreakpoint;
import org.eclipse.jdt.debug.core.IJavaClassObject;
import org.eclipse.jdt.debug.core.IJavaFieldVariable;
import org.eclipse.jdt.debug.core.IJavaMethodBreakpoint;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaReferenceType;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaThreadGroup;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDINullValue;
import org.eclipse.jdt.internal.debug.core.model.JDIObjectValue;
import org.eclipse.jdt.internal.debug.core.model.JDIThread;
import com.redhat.ceylon.eclipse.core.launch.LaunchHelper;
import com.redhat.ceylon.ide.common.debug.agent.CeylonDebugEvaluationThread;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.VirtualMachine;
public class CeylonJDIDebugTarget extends JDIDebugTarget {
private IProject project = null;
private String[] ceylonStepFilters;
private boolean stepFiltersEnabled;
private boolean filterDefaultArgumentsCode;
private boolean debugAsJavaCode = false;
public CeylonJDIDebugTarget(ILaunch launch, VirtualMachine jvm, String name,
boolean supportTerminate, boolean supportDisconnect,
IProcess process, boolean resume) {
super(launch, jvm, name, supportTerminate, supportDisconnect, process, resume);
try {
ILaunchConfiguration config = launch.getLaunchConfiguration();
String projectName;
projectName = config.getAttribute("org.eclipse.jdt.launching.PROJECT_ATTR", "");
if (projectName != null && ! projectName.isEmpty()) {
IProject theProject = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
if (theProject.exists()) {
project = theProject;
}
}
startLocation = LaunchHelper.getStartLocation(config);
} catch (CoreException e) {
e.printStackTrace();
}
}
@Override
public boolean isStepThruFilters() {
return false;
}
private static IJavaBreakpoint ceylonDebugEvaluationBreakpoint = null;
@Override
protected synchronized void initialize() {
super.initialize();
if (ceylonDebugEvaluationBreakpoint == null) {
try {
Map<String, Object> map = new HashMap<String, Object>();
IJavaMethodBreakpoint bp = JDIDebugModel
.createMethodBreakpoint(
ResourcesPlugin
.getWorkspace()
.getRoot(),
CeylonDebugEvaluationThread.name,
CeylonDebugEvaluationThread.methodForBreakpoint,
"()V",
true, false, false, -1, -1,
-1, 0, false, map);
bp.setPersisted(false);
ceylonDebugEvaluationBreakpoint = bp;
} catch (CoreException e) {
e.printStackTrace();
}
}
breakpointAdded(ceylonDebugEvaluationBreakpoint);
}
private IJavaObject mainThread = null;
private String startLocation = null;
public String getStartLocation() {
return startLocation;
}
public boolean isMainThread(IThread thread) {
if (! (thread instanceof JDIThread)) {
return false;
}
if (ceylonEvaluationThread == null) {
return false;
}
if (mainThread == null) {
return false;
}
JDIThread javaThread = (JDIThread) thread;
try {
return mainThread != null && mainThread.equals(javaThread.getThreadObject());
} catch (DebugException e) {
e.printStackTrace();
return false;
}
}
private IJavaThread ceylonEvaluationThread = null;
private boolean hasEvaluationThreadGroup = false;
public boolean hasCeylonEvaluationThread() {
return ceylonEvaluationThread != null;
}
public boolean hasCeylonEvaluationThreadGroup() {
return hasEvaluationThreadGroup;
}
@Override
protected JDIThread newThread(ThreadReference reference) {
try {
JDIThread newThread = new CeylonJDIThread(this, reference);
try {
if (CeylonDebugEvaluationThread.name.equals(reference.name())) {
ceylonEvaluationThread = newThread;
for (IJavaThreadGroup threadGroup : getRootThreadGroups()) {
if (CeylonDebugEvaluationThread.name.equals(threadGroup.getName())) {
hasEvaluationThreadGroup = true;
break;
}
}
IJavaObject evalThreadObject = ceylonEvaluationThread.getThreadObject();
IJavaFieldVariable mainThreadRefVariable = evalThreadObject.getField(CeylonDebugEvaluationThread.mainThreadRefFieldName, false);
IJavaObject mainThreadRefValue = (IJavaObject)mainThreadRefVariable.getValue();
if (mainThreadRefValue != null) {
IJavaFieldVariable mainThreadVariable = mainThreadRefValue.getField("referent", true);
IJavaObject mainThreadValue = (IJavaObject) mainThreadVariable.getValue();
if (mainThreadValue != null) {
mainThread = mainThreadValue;
}
}
}
} catch(com.sun.jdi.VMDisconnectedException e) {
} catch(DebugException e) {
e.printStackTrace();
}
return newThread;
} catch (ObjectCollectedException exception) {
// ObjectCollectionException can be thrown if the thread has already
// completed (exited) in the VM.
}
return null;
}
public IProject getProject() {
return project;
}
private Cache jdiAnnotationParameterSignatureCache = new Cache(10);
private static final Object NULL_ENTRY = new Object();
private String getAnnotationParameterSignature(
final Class<? extends Annotation> annotationClass, String parameter) throws DebugException {
String key = annotationClass.getName() + "/" + parameter;
String annotationSignature = null;
synchronized (jdiAnnotationParameterSignatureCache) {
Cache.Entry entry = jdiAnnotationParameterSignatureCache.getEntry(key);
if (entry == null) {
try {
IJavaProject javaProject = JavaCore
.create(getProject());
IType annotationType = javaProject
.findType(annotationClass.getName());
IMethod method = annotationType.getMethod(parameter, new String[0]);
if (method != null) {
annotationSignature = method.getSignature();
}
} catch (JavaModelException e) {
e.printStackTrace();
}
if (annotationSignature != null) {
jdiAnnotationParameterSignatureCache.addEntry(key, annotationSignature);
} else {
jdiAnnotationParameterSignatureCache.addEntry(key, NULL_ENTRY);
}
} else {
Object cached = entry.getCached();
annotationSignature = cached == NULL_ENTRY ? null : (String) cached;
}
}
return annotationSignature;
}
private static final String getAnnotationsMethodSignature = "()[Ljava/lang/annotation/Annotation;";
private static final String getAnnotationsMethodName = "getAnnotations";
public static class EvaluationWaiter implements EvaluationListener {
boolean finished = false;
IJavaValue result = null;
synchronized public void finished(
IJavaValue annotation) {
this.result = annotation;
finished = true;
notifyAll();
}
synchronized public IJavaValue waitForResult(long timeout) {
if (!finished) {
try {
wait(timeout);
} catch (InterruptedException e1) {
e1.printStackTrace();
// Fall through
}
}
return result;
}
};
public static interface EvaluationListener {
void finished(IJavaValue annotation);
};
public interface EvaluationRunner {
void run(
IJavaThread innerThread,
IProgressMonitor monitor,
EvaluationListener listener) throws DebugException;
}
public IJavaValue getEvaluationResult(final EvaluationRunner runner, long timeout) {
EvaluationWaiter listener = new EvaluationWaiter();
evaluate(runner, listener);
return jdiNullValueToNull(listener.waitForResult(timeout));
}
public void evaluate(final EvaluationRunner runner, final EvaluationListener listener) {
if (listener == null) {
return;
}
final IJavaThread evaluationThread = ceylonEvaluationThread;
if (evaluationThread == null) {
listener.finished(null);
} else {
Runnable runnable = new Runnable() {
public void run() {
if (evaluationThread == null || !evaluationThread.isSuspended()) {
System.err.println("Evaluation cancelled : thread is not suspended");
listener.finished(null);
return;
}
IEvaluationRunnable eval = new IEvaluationRunnable() {
public void run(
IJavaThread innerThread,
IProgressMonitor monitor)
throws DebugException {
try {
runner.run(innerThread, monitor, listener);
} catch(Throwable t) {
t.printStackTrace();
listener.finished(null);
}
}
};
try {
evaluationThread.runEvaluation(
eval,
null,
DebugEvent.EVALUATION_IMPLICIT,
false);
} catch (DebugException e) {
e.printStackTrace();
listener.finished(null);
}
}
};
if (listener != null) {
evaluationThread.queueRunnable(runnable);
}
}
}
private IJavaValue jdiNullValueToNull(IJavaValue value) {
if (value instanceof JDINullValue) {
return null;
}
return value;
}
public IJavaValue getAnnotation(IJavaReferenceType valueType, Class<? extends Annotation> annotationClass, String parameter, long timeout) {
EvaluationWaiter listener = new EvaluationWaiter();
evaluateAnnotation(valueType, annotationClass, parameter, listener);
return jdiNullValueToNull(listener.waitForResult(timeout));
}
public void evaluateAnnotation(IJavaReferenceType valueType, final Class<? extends Annotation> annotationClass, final String parameter, final EvaluationListener listener) {
try {
final IJavaClassObject theClassObject = valueType.getClassObject();
EvaluationRunner runner = new EvaluationRunner() {
public void run(
IJavaThread innerThread,
IProgressMonitor monitor,
EvaluationListener listener) throws DebugException {
IJavaValue result = null;
IJavaValue annotations = theClassObject.sendMessage(
getAnnotationsMethodName,
getAnnotationsMethodSignature,
new IJavaValue[0],
innerThread,
(String) null);
if (annotations instanceof IJavaArray) {
IJavaValue[] annotationValues = ((IJavaArray)annotations).getValues();
for (IJavaValue annotationValue : annotationValues) {
IJavaValue annotationClassValue = ((IJavaObject)annotationValue).sendMessage("annotationType", "()Ljava/lang/Class;", new IJavaValue[0], innerThread, null);
if (annotationClassValue instanceof IJavaObject && ! (annotationClassValue instanceof JDINullValue)) {
IJavaValue annotationClassName = ((IJavaObject)annotationClassValue).sendMessage("getName", "()Ljava/lang/String;", new IJavaValue[0], innerThread, null);
if (annotationClassName != null && annotationClassName.getValueString().equals(annotationClass.getName())) {
result = annotationValue;
break;
}
}
}
}
if (parameter == null) {
listener.finished(result);
} else {
if (result instanceof JDINullValue) {
listener.finished(null);
} else {
IJavaObject annotationObject = (IJavaObject)result;
result = null;
String annotationParameterMethodSignature = getAnnotationParameterSignature(annotationClass, parameter);
if (annotationParameterMethodSignature != null) {
result = ((IJavaObject) annotationObject).sendMessage(
parameter,
annotationParameterMethodSignature,
new IJavaValue[] { },
innerThread,
(String) null);
}
listener.finished(result);
}
}
}
};
evaluate(runner, listener);
} catch (DebugException e) {
e.printStackTrace();
}
}
private boolean isAnnotationPresent(IJavaValue annotation) {
return annotation instanceof JDIObjectValue &&
!(annotation instanceof JDINullValue);
}
public boolean isAnnotationPresent(IJavaReferenceType valueType, Class<? extends Annotation> annotationClass, long timeout) {
EvaluationWaiter listener = new EvaluationWaiter();
evaluateAnnotation(valueType, annotationClass, null, listener);
return isAnnotationPresent(listener.waitForResult(timeout));
}
public void evaluateAnnotationPresent(IJavaReferenceType valueType, final Class<? extends Annotation> annotationClass, final EvaluationListener listener) {
EvaluationListener internalListener = null;
internalListener = new EvaluationListener() {
@Override
public void finished(IJavaValue annotation) {
if (isAnnotationPresent(annotation)) {
IDebugTarget debugTarget = annotation.getDebugTarget();
if (! (debugTarget instanceof JDIDebugTarget)) {
System.err.println("This should be a JDIDebugTarget !!!");
listener.finished(null);
return;
}
listener.finished(((JDIDebugTarget) debugTarget).newValue(true));
} else {
listener.finished(null);
}
}
};
evaluateAnnotation(valueType, annotationClass, null, internalListener);
}
public void setCeylonStepFilters(String[] list) {
ceylonStepFilters = list;
}
@Override
public String[] getStepFilters() {
return ceylonStepFilters;
}
public void setFiltersDefaultArgumentsCode(boolean enabled) {
filterDefaultArgumentsCode = enabled;
}
public boolean isFiltersDefaultArgumentsCode() {
return filterDefaultArgumentsCode;
}
public void setCeylonStepFiltersEnabled(boolean enabled) {
stepFiltersEnabled = enabled;
}
@Override
public boolean isStepFiltersEnabled() {
return stepFiltersEnabled;
}
public boolean isDebugAsJavaCode() {
return debugAsJavaCode;
}
public void setDebugAsJavaCode(boolean debugAsJavaCode) {
this.debugAsJavaCode = debugAsJavaCode;
}
}