blob: e386f8eccf8d8073caaca7b1a89c787b7dead031 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2004 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.eval;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugEvent;
import org.eclipse.debug.core.DebugException;
import org.eclipse.debug.core.model.IVariable;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.eval.ICodeSnippetRequestor;
import org.eclipse.jdt.core.eval.IEvaluationContext;
import org.eclipse.jdt.debug.core.IEvaluationRunnable;
import org.eclipse.jdt.debug.core.IJavaClassObject;
import org.eclipse.jdt.debug.core.IJavaClassType;
import org.eclipse.jdt.debug.core.IJavaDebugTarget;
import org.eclipse.jdt.debug.core.IJavaObject;
import org.eclipse.jdt.debug.core.IJavaStackFrame;
import org.eclipse.jdt.debug.core.IJavaThread;
import org.eclipse.jdt.debug.core.IJavaType;
import org.eclipse.jdt.debug.core.IJavaValue;
import org.eclipse.jdt.debug.core.IJavaVariable;
import org.eclipse.jdt.debug.core.JDIDebugModel;
import org.eclipse.jdt.debug.eval.IClassFileEvaluationEngine;
import org.eclipse.jdt.debug.eval.IEvaluationListener;
import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin;
import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget;
import org.eclipse.jdt.internal.debug.core.model.JDIValue;
import com.sun.jdi.InvocationException;
import com.sun.jdi.ObjectReference;
/**
* An evaluation engine that deploys class files locally
*/
public class LocalEvaluationEngine implements IClassFileEvaluationEngine, ICodeSnippetRequestor , IEvaluationRunnable {
private static final String CODE_SNIPPET_NAME= "CodeSnippet.class"; //$NON-NLS-1$
/**
* A count of the number of engines created.
* Count is incremented on instantiation and decremented on
* dispose. When the count == 0, the special CodeSnippet.class
* is deleted as this class file is shared by all.
*/
private static int ENGINE_COUNT= 0;
/**
* The Java project context in which to compile snippets.
*/
private IJavaProject fJavaProject;
/**
* The debug target on which to execute snippets
*/
private IJavaDebugTarget fDebugTarget;
/**
* The location in which to deploy snippet class files
*/
private File fOutputDirectory;
/**
* The listener to notify when the current evaluation
* is complete.
*/
private IEvaluationListener fListener;
/**
* The stack frame context for the current evaluation
* or <code>null</code> if there is no stack frame
* context.
*/
private IJavaStackFrame fStackFrame;
/**
* The result of this evaluation
*/
private EvaluationResult fResult;
/**
* Collection of deployed snippet class files
*/
private List fSnippetFiles;
/**
* Collection of directories created by this evaluation
* engine.
*/
private List fDirectories;
/**
* Evaluation context for the Java project associated
* with this evaluation engine.
*/
private IEvaluationContext fEvaluationContext;
/**
* Array of modifier constants for visible local variables
* in the current evaluation.
*
* XXX: constants should be 'default' or 'final'. Where
* are these constants defined.
*/
private int[] fLocalVariableModifiers;
/**
* Array of names of visible local variables
* in the current evaluation.
*/
private String[] fLocalVariableNames;
/**
* Array of type names of visible local variables
* in the current evaluation.
*/
private String[] fLocalVariableTypeNames;
/**
* The 'this' object for the current evaluation
* or <code>null</code> if there is no 'this'
* context (static method, or not context)
*/
private IJavaObject fThis;
/**
* Whether this engine has been disposed.
*/
private boolean fDisposed = false;
/**
* The number of evaluations currently being
* performed.
*/
private int fEvaluationCount = 0;
/**
* The name of the code snippet class to instantiate
*/
private String fCodeSnippetClassName = null;
/**
* Whether to hit breakpoints in the evaluation thread
*/
private boolean fHitBreakpoints = false;
/**
* Constant for empty array of <code>java.lang.String</code>
*/
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
* Constant for empty array of <code>int</code>
*/
private static final int[] EMPTY_INT_ARRAY = new int[0];
/**
* Cosntructs a new evaluation engine for the given VM in the context
* of the specified project. Class files required for the evaluation will
* be deployed to the specified directory (which must be on the class
* path of the VM in order for evaluation to work).
*
* @param project context in which to compile snippets
* @param vm debug target in which to evaluate snippets
* @param directory location where snippet class files will
* be deployed for execution. The directory must exist
*/
public LocalEvaluationEngine(IJavaProject project, IJavaDebugTarget vm, File directory) {
setJavaProject(project);
setDebugTarget(vm);
setOutputDirectory(directory);
ENGINE_COUNT++;
}
/**
* @see ICodeSnippetRequestor#acceptClassFiles(byte[][], String[][], String)
*/
public boolean acceptClassFiles(
byte[][] classFileBytes,
String[][] classFileCompoundNames,
String codeSnippetClassName) {
try {
deploy(classFileBytes, classFileCompoundNames);
} catch (DebugException e) {
getResult().setException(e);
return false;
}
if (codeSnippetClassName != null) {
setCodeSnippetClassName(codeSnippetClassName);
try {
getThread().runEvaluation(this, null, DebugEvent.EVALUATION, getHitBreakpoints());
} catch (DebugException e) {
// exception handling is in evaluation runnable
}
}
return true;
}
public void run(IJavaThread thread, IProgressMonitor monitor) {
IJavaObject codeSnippetInstance = null;
try {
codeSnippetInstance = newInstance(getCodeSnippetClassName());
initializeLocals(codeSnippetInstance);
codeSnippetInstance.sendMessage(RUN_METHOD, "()V", null, getThread(), false); //$NON-NLS-1$
restoreLocals(codeSnippetInstance);
// now retrieve the description of the result
IVariable[] fields = codeSnippetInstance.getVariables();
IJavaVariable resultValue = null;
IJavaVariable resultType = null;
for (int i = 0; i < fields.length; i++) {
if (fields[i].getName().equals(RESULT_TYPE_FIELD)) {
resultType = (IJavaVariable)fields[i];
}
if (fields[i].getName().equals(RESULT_VALUE_FIELD)) {
resultValue = (IJavaVariable)fields[i];
}
}
IJavaValue result = convertResult((IJavaClassObject)resultType.getValue(), (IJavaValue)resultValue.getValue());
getResult().setValue(result);
} catch (DebugException e) {
getResult().setException(e);
Throwable underlyingException = e.getStatus().getException();
if (underlyingException instanceof InvocationException) {
ObjectReference theException = ((InvocationException)underlyingException).exception();
if (theException != null) {
try {
try {
IJavaObject v = (IJavaObject)JDIValue.createValue((JDIDebugTarget)getDebugTarget(), theException);
v.sendMessage("printStackTrace", "()V", null, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$
} catch (DebugException de) {
JDIDebugPlugin.log(de);
}
} catch (RuntimeException re) {
JDIDebugPlugin.log(re);
}
}
}
}
}
/**
* Initializes the value of instance variables in the
* 'code snippet object' that are used as placeholders
* for locals and 'this' in the current stack frame.
*
* @param object instance of code snippet class that will
* be run
* @exception DebugException if an exception is thrown
* accessing the given object
*/
protected void initializeLocals(IJavaObject object) throws DebugException {
IJavaVariable[] locals = null;
IJavaObject thisObject = getThis();
if (getStackFrame() != null) {
locals = getStackFrame().getLocalVariables();
}
if (locals != null) {
for (int i = 0; i < locals.length; i++) {
IJavaVariable local = locals[i];
IJavaVariable field = object.getField(LOCAL_VAR_PREFIX + local.getName(), false);
// internal error if field is not found
if (field == null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_initialize_local_variables._4"), null) //$NON-NLS-1$
);
}
field.setValue(local.getValue());
}
}
if (thisObject != null) {
IJavaVariable field = object.getField(DELEGATE_THIS, false);
// internal error if field is not found
if (field == null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_initialize___this___context._5"), null) //$NON-NLS-1$
);
}
field.setValue(thisObject);
}
}
/**
* Restores the value local variables from the instance
* variables in the 'code snippet object' that are used
* as placeholders for locals in the current stack frame.
*
* @param object instance of code snippet class that was
* run
* @exception DebugException if an exception is thrown
* accessing the given object
*/
protected void restoreLocals(IJavaObject object) throws DebugException {
IJavaVariable[] locals = null;
if (getStackFrame() != null) {
locals = getStackFrame().getLocalVariables();
}
if (locals != null) {
for (int i = 0; i < locals.length; i++) {
IJavaVariable local = locals[i];
IJavaVariable field = object.getField(LOCAL_VAR_PREFIX + local.getName(), false);
// internal error if field is not found
if (field == null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_initialize_local_variables._6"), null) //$NON-NLS-1$
);
}
local.setValue(field.getValue());
}
}
}
/**
* @see ICodeSnippetRequestor#acceptProblem(IMarker, String, int)
*/
public void acceptProblem(IMarker problemMarker, String fragmentSource, int fragmentKind) {
if (problemMarker.getAttribute(IMarker.SEVERITY, -1) != IMarker.SEVERITY_ERROR) {
return;
}
getResult().addError(problemMarker.getAttribute(IMarker.MESSAGE, "")); //$NON-NLS-1$
}
/**
* @see IEvaluationEngine#getDebugTarget()
*/
public IJavaDebugTarget getDebugTarget() {
return fDebugTarget;
}
/**
* Sets the debug target in which snippets are executed.
*
* @param debugTarget the debug target in which snippets are executed
*/
private void setDebugTarget(IJavaDebugTarget debugTarget) {
fDebugTarget = debugTarget;
}
/**
* @see IEvaluationEngine#getJavaProject()
*/
public IJavaProject getJavaProject() {
return fJavaProject;
}
/**
* Sets the Java project in which snippets are compiled.
*
* @param javaProject the Java project in which snippets are compiled
*/
private void setJavaProject(IJavaProject javaProject) {
fJavaProject = javaProject;
}
/**
* Returns the directory in which snippet class files are deployed.
*
* @return the directory in which snippet class files are deployed.
*/
public File getOutputDirectory() {
return fOutputDirectory;
}
/**
* Sets the directory in which snippet class files are
* deployed.
*
* @param outputDirectory location to deploy snippet class files
*/
private void setOutputDirectory(File outputDirectory) {
fOutputDirectory = outputDirectory;
}
/**
* @see IClassFileEvaluationEngine#evaluate(String, IJavaThread, IEvaluationListener)
*/
public void evaluate(String snippet, IJavaThread thread, IEvaluationListener listener, boolean hitBreakpoints) throws DebugException {
checkDisposed();
checkEvaluating();
try {
evaluationStarted();
setListener(listener);
setHitBreakpoints(hitBreakpoints);
setResult(new EvaluationResult(this, snippet, thread));
checkThread();
// no receiver/stack frame context
setThis(null);
setLocalVariableNames(EMPTY_STRING_ARRAY);
setLocalVariableTypeNames(EMPTY_STRING_ARRAY);
setLocalVariableModifiers(EMPTY_INT_ARRAY);
// do the evaluation in a different thread
Runnable r = new Runnable() {
public void run() {
try {
LocalEvaluationEngine.this.getEvaluationContext().
evaluateCodeSnippet(LocalEvaluationEngine.this.getSnippet(),
LocalEvaluationEngine.this, null);
} catch (JavaModelException e) {
LocalEvaluationEngine.this.getResult().setException(new DebugException(e.getStatus()));
}
LocalEvaluationEngine.this.evaluationComplete();
}
};
Thread t = new Thread(r);
t.start();
} catch (DebugException d) {
evaluationAborted();
throw d;
}
}
/**
* @see IEvaluationEngine#evaluate(String, IJavaStackFrame, IEvaluationListener, int)
*/
public void evaluate(String snippet, IJavaStackFrame frame, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints) throws DebugException {
checkDisposed();
checkEvaluating();
try {
evaluationStarted();
setListener(listener);
setStackFrame(frame);
setHitBreakpoints(hitBreakpoints);
setResult(new EvaluationResult(this, snippet, (IJavaThread)frame.getThread()));
checkThread();
// set up local variables and 'this' context for evaluation
IJavaVariable[] locals = frame.getLocalVariables();
List typeNames = new ArrayList(locals.length);
List varNames = new ArrayList(locals.length);
for (int i = 0; i < locals.length; i++) {
IJavaVariable var = locals[i];
String typeName = getTranslatedTypeName(var.getReferenceTypeName());
if (typeName != null) {
typeNames.add(typeName);
varNames.add(var.getName());
}
}
setLocalVariableTypeNames((String[])typeNames.toArray(new String[typeNames.size()]));
setLocalVariableNames((String[])varNames.toArray(new String[varNames.size()]));
int[] modifiers = new int[typeNames.size()];
// cannot determine if local is final, so specify as default
Arrays.fill(modifiers, 0);
setLocalVariableModifiers(modifiers);
setThis(frame.getThis());
final boolean isStatic = frame.isStatic();
final boolean isConstructor = frame.isConstructor();
final IType receivingType = getReceivingType(frame);
// do the evaluation in a different thread
Runnable r = new Runnable() {
public void run() {
try {
LocalEvaluationEngine.this.getEvaluationContext().
evaluateCodeSnippet(
LocalEvaluationEngine.this.getSnippet(),
LocalEvaluationEngine.this.getLocalVariableTypeNames(),
LocalEvaluationEngine.this.getLocalVariableNames(),
LocalEvaluationEngine.this.getLocalVariableModifiers(),
receivingType,
isStatic,
isConstructor,
LocalEvaluationEngine.this,
null);
} catch (JavaModelException e) {
LocalEvaluationEngine.this.getResult().setException(new DebugException(e.getStatus()));
}
LocalEvaluationEngine.this.evaluationComplete();
}
};
Thread t = new Thread(r);
t.start();
} catch (DebugException d) {
evaluationAborted();
throw d;
}
}
/**
* @see IEvaluationEngine#evaluate(String, IJavaObject, IJavaThread, IEvaluationListener, int)
*/
public void evaluate(String snippet, IJavaObject thisContext, IJavaThread thread, IEvaluationListener listener, int evaluationDetail, boolean hitBreakpoints) throws DebugException {
checkDisposed();
checkEvaluating();
try {
evaluationStarted();
setListener(listener);
setHitBreakpoints(hitBreakpoints);
setResult(new EvaluationResult(this, snippet, thread));
checkThread();
// no locals
setLocalVariableTypeNames(new String[0]);
setLocalVariableNames(new String[0]);
setLocalVariableModifiers(new int[0]);
setThis(thisContext);
final boolean isStatic = false;
final boolean isConstructor = false;
final IType receivingType = getReceivingType(thisContext);
// do the evaluation in a different thread
Runnable r = new Runnable() {
public void run() {
try {
LocalEvaluationEngine.this.getEvaluationContext().
evaluateCodeSnippet(
LocalEvaluationEngine.this.getSnippet(),
LocalEvaluationEngine.this.getLocalVariableTypeNames(),
LocalEvaluationEngine.this.getLocalVariableNames(),
LocalEvaluationEngine.this.getLocalVariableModifiers(),
receivingType,
isStatic,
isConstructor,
LocalEvaluationEngine.this,
null);
} catch (JavaModelException e) {
LocalEvaluationEngine.this.getResult().setException(new DebugException(e.getStatus()));
}
LocalEvaluationEngine.this.evaluationComplete();
}
};
Thread t = new Thread(r);
t.start();
} catch (DebugException d) {
evaluationAborted();
throw d;
}
}
/**
* Throws an exception if this engine has already been
* disposed.
*
* @exception DebugException if this engine has been disposed
*/
protected void checkDisposed() throws DebugException {
if (isDisposed()) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_evaluation_context_has_been_disposed._7"), null) //$NON-NLS-1$
);
}
}
/**
* Throws an exception if this engine is already in an
* evaluation.
*
* @exception DebugException if this engine is currently
* performing an evaluation
*/
protected void checkEvaluating() throws DebugException {
if (isEvaluating()) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, "Cannot perform nested evaluations.", null) //$NON-NLS-1$
);
}
}
/**
* Throws an exception if this engine's current evaluation
* thread is not suspended.
*
* @exception DebugException if this engine's current evaluation
* thread is not suspended
*/
protected void checkThread() throws DebugException {
if (!getThread().isSuspended()) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_evaluation_thread_must_be_suspended._8"), null) //$NON-NLS-1$
);
}
}
/**
* Deletes deployed class files, and clears state.
*
* @see IEvaluationEngine#dispose()
*/
public void dispose() {
fDisposed = true;
ENGINE_COUNT--;
if (isEvaluating()) {
// cannot dispose if in an evaluation, must
// wait for evaluation to complete
return;
}
List snippetFiles = getSnippetFiles();
Iterator iter = snippetFiles.iterator();
while (iter.hasNext()) {
File file = (File)iter.next();
if (file.exists()) {
if (CODE_SNIPPET_NAME.equals(file.getName()) && ENGINE_COUNT > 0) {
continue; //do not delete the common file for other engines
}
if (!file.delete()) {
JDIDebugPlugin.log(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED,
MessageFormat.format(EvaluationMessages.getString("LocalEvaluationEngine.Unable_to_delete_temporary_evaluation_class_file_{0}_1"), new String[] {file.getAbsolutePath()}), null) //$NON-NLS-1$
);
}
}
}
List directories = getDirectories();
// remove directories in bottom up order
int i = directories.size() - 1;
while (i >= 0) {
File dir = (File)directories.get(i);
String[] listing= dir.list();
if (dir.exists() && listing != null && listing.length == 0 && !dir.delete()) {
JDIDebugPlugin.log(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED,
MessageFormat.format(EvaluationMessages.getString("LocalEvaluationEngine.Unable_to_delete_temporary_evaluation_directory_{0}_2"), new String[] {dir.getAbsolutePath()}), null) //$NON-NLS-1$
);
}
i--;
}
reset();
setJavaProject(null);
setDebugTarget(null);
setOutputDirectory(null);
setResult(null);
setEvaluationContext(null);
}
/**
* Resets this engine for another evaluation.
*/
private void reset() {
setThis(null);
setStackFrame(null);
setListener(null);
}
/**
* Returns the listener to notify when the current
* evaluation is complete.
*
* @return the listener to notify when the current
* evaluation is complete
*/
protected IEvaluationListener getListener() {
return fListener;
}
/**
* Sets the listener to notify when the current
* evaluation is complete.
*
* @param listener the listener to notify when the current
* evaluation is complete
*/
private void setListener(IEvaluationListener listener) {
fListener = listener;
}
/**
* Returns the stack frame context for the current
* evaluation, or <code>null</code> if none.
*
* @return the stack frame context for the current
* evaluation, or <code>null</code> if none
*/
protected IJavaStackFrame getStackFrame() {
return fStackFrame;
}
/**
* Sets the stack frame context for the current evaluation.
*
* @param stackFrame stack frame context or <code>null</code>
* if none
*/
private void setStackFrame(IJavaStackFrame stackFrame) {
fStackFrame = stackFrame;
}
/**
* Returns the thread in which the current evaluation is
* to be executed.
*
* @return the thread in which the current evaluation is
* to be executed
*/
protected IJavaThread getThread() {
return getResult().getThread();
}
/**
* Returns the code snippet being evaluated.
*
* @return the code snippet being evaluated.
*/
protected String getSnippet() {
return getResult().getSnippet();
}
/**
* Returns the current evaluation result.
*
* @return the current evaluation result
*/
protected EvaluationResult getResult() {
return fResult;
}
/**
* Sets the current evaluation result.
*
* @param result the current evaluation result
*/
private void setResult(EvaluationResult result) {
fResult = result;
}
/**
* Deploys the given class files to this engine's
* output location, and adds the files to this
* engines list of temporary files to be deleted
* when disposed.
*
* @exception DebugException if this fails due to a
* lower level exception.
*/
protected void deploy(byte[][] classFiles, String[][] classFileNames) throws DebugException {
for (int i = 0; i < classFiles.length; i++) {
String[] compoundName = classFileNames[i];
//create required folders
File dir = LocalEvaluationEngine.this.getOutputDirectory();
try {
String pkgDirName = dir.getCanonicalPath();
for (int j = 0; j < (compoundName.length - 1); j++) {
pkgDirName += File.separator + compoundName[j];
File pkgDir = new File(pkgDirName);
if (!pkgDir.exists()) {
pkgDir.mkdir();
addDirectory(pkgDir);
}
}
String name = compoundName[compoundName.length - 1] + ".class"; //$NON-NLS-1$
File classFile = new File(pkgDirName + File.separator + name);
if (!classFile.exists()) {
classFile.createNewFile();
}
FileOutputStream stream = new FileOutputStream(classFile);
stream.write(classFiles[i]);
stream.close();
LocalEvaluationEngine.this.addSnippetFile(classFile);
} catch (IOException e) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED,
MessageFormat.format(EvaluationMessages.getString("LocalEvaluationEngine.{0}_occurred_deploying_class_file_for_evaluation_9"), new String[] {e.toString()}), e) //$NON-NLS-1$
);
}
}
}
/**
* Adds the given file to this engine's collection
* of deployed snippet class files, which are to
* be deleted when this engine is diposed.
*
* @param File snippet class file
*/
private void addSnippetFile(File file) {
if (fSnippetFiles == null) {
fSnippetFiles = new ArrayList();
}
fSnippetFiles.add(file);
}
/**
* Adds the given file to this engine's collection
* of cerated directories, which are to
* be deleted when this engine is diposed.
*
* @param file directory created for class file deployment
*/
private void addDirectory(File file) {
if (fDirectories == null) {
fDirectories = new ArrayList();
}
fDirectories.add(file);
}
/**
* Returns an evaluation context for this evaluation
* engine. An evaluation context is associted with a
* specific Java project. The evaluation context is
* created lazily on the first access.
*
* @return evaluation context
*/
protected IEvaluationContext getEvaluationContext() {
if (fEvaluationContext == null) {
fEvaluationContext = getJavaProject().newEvaluationContext();
}
return fEvaluationContext;
}
/**
* Sets the evaluation context for this evaluation
* engine.
*
* @param context evaluation context
*/
private void setEvaluationContext(IEvaluationContext context) {
fEvaluationContext = context;
}
/**
* Returns a collection of snippet class file deployed by
* this evaluation engine, possibly empty.
*
* @return deployed class files
*/
protected List getSnippetFiles() {
if (fSnippetFiles == null) {
return Collections.EMPTY_LIST;
}
return fSnippetFiles;
}
/**
* Returns a collection of directories created by
* this evaluation engine, possibly empty.
*
* @return directories created when deploying class files
*/
protected List getDirectories() {
if (fDirectories == null) {
return Collections.EMPTY_LIST;
}
return fDirectories;
}
/**
* Returns whether this evaluation engine has been
* disposed.
*
* @return whether this evaluation engine has been
* disposed
*/
protected boolean isDisposed() {
return fDisposed;
}
/**
* The evaluation is complete. Notify the current listener
* and reset for the next evaluation.
*/
protected void evaluationComplete() {
// only notify if plug-in not yet shutdown (bug# 8693)
if (JDIDebugPlugin.getDefault() != null) {
getListener().evaluationComplete(getResult());
}
evaluationEnded();
reset();
if (isDisposed()) {
// if the engine was disposed during an evaluation
// do the cleanup now
dispose();
}
}
/**
* Increments the evaluation counter.
*/
private void evaluationStarted() {
fEvaluationCount++;
}
/**
* Decrements the evaluation counter.
*/
private void evaluationEnded() {
if (fEvaluationCount > 0) {
fEvaluationCount--;
}
}
/**
* Returns whether this engine is currently in the
* midst of an evaluation.
*/
protected boolean isEvaluating() {
return fEvaluationCount > 0;
}
/**
* Called when an evaluation is aborted due to an
* exception. Decrements the evalution count, and
* disposes this engine if the target VM disconnected
* or terminated during the evaluation attempt.
*/
private void evaluationAborted() {
evaluationEnded();
if (isDisposed()) {
// if the engine was disposed during an evaluation
// do the cleanup now
dispose();
}
}
/**
* Constructs and returns a new instance of the specified
* class on the target VM.
*
* @param className fully qualified class name
* @return a new instance on the target, as an <code>IJavaValue</code>
* @exception DebugException if creation fails
*/
protected IJavaObject newInstance(String className) throws DebugException {
IJavaObject object = null;
IJavaClassType clazz = null;
IJavaType[] types = getDebugTarget().getJavaTypes(className);
if (types != null && types.length > 0) {
clazz = (IJavaClassType)types[0];
}
if (clazz == null) {
// The class is not loaded on the target VM.
// Force the load of the class.
types = getDebugTarget().getJavaTypes("java.lang.Class"); //$NON-NLS-1$
IJavaClassType classClass = null;
if (types != null && types.length > 0) {
classClass = (IJavaClassType)types[0];
}
if (classClass == null) {
// unable to load the class
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_instantiate_code_snippet_class._11"), null) //$NON-NLS-1$
);
}
IJavaValue[] args = new IJavaValue[] {getDebugTarget().newValue(className)};
IJavaObject classObject = (IJavaObject)classClass.sendMessage("forName", "(Ljava/lang/String;)Ljava/lang/Class;", args, getThread()); //$NON-NLS-2$ //$NON-NLS-1$
object = (IJavaObject)classObject.sendMessage("newInstance", "()Ljava/lang/Object;", null, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$
} else {
object = clazz.newInstance("<init>", null, getThread()); //$NON-NLS-1$
}
return object;
}
/**
* Interpretts and returns the result of the running the snippet
* class file. The type of the result is described by an instance of
* <code>java.lang.Class</code>. The value is interpretted based
* on the result type.
* <p>
* Objects as well as primitve data types (boolean, int, etc.),
* have class objects, which are created by the VM. If the class object
* represents a primitive data type, then the associated value
* is stored in an instance of its "object" class. For example, when
* the result type is the class object for <code>int</code>, the result
* object is an instance of <code>java.lang.Integer</code>, and the
* actual <code>int</code> is stored in the </code>intValue()</code>.
* When the result type is the class object for <code>java.lang.Integer</code>
* the result object is an instance of <code>java.lang.Integer</code>,
* to be interpretted as a <ocde>java.lang.Integer</code>.
* </p>
*
* @param resultType the class of the result
* @param resultValue the value of ther result, to be interpretted
* based on resultType
* @return the result of running the code snipped class file
*/
protected IJavaValue convertResult(IJavaClassObject resultType, IJavaValue result) throws DebugException {
if (resultType == null) {
// there was an exception or compilation problem - no result
return null;
}
// check the type of the result - if a primitive type, convert it
String sig = resultType.getInstanceType().getSignature();
if (sig.equals("V") || sig.equals("Lvoid;")) { //$NON-NLS-2$ //$NON-NLS-1$
// void
return getDebugTarget().voidValue();
}
if (result.getJavaType() == null) {
// null result
return result;
}
if (sig.length() == 1) {
// primitive type - find the instance variable with the
// signature of the result type we are looking for
IVariable[] vars = result.getVariables();
IJavaVariable var = null;
for (int i = 0; i < vars.length; i++) {
IJavaVariable jv = (IJavaVariable)vars[i];
if (!jv.isStatic() && jv.getSignature().equals(sig)) {
var = jv;
break;
}
}
if (var != null) {
return (IJavaValue)var.getValue();
}
} else {
// an object
return result;
}
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_internal_error_retreiving_result._17"), null) //$NON-NLS-1$
);
}
/**
* Returns the modifiers of the local variables
* visible in this evaluation, possibly empty.
*
* @return array of modifiers
*/
private int[] getLocalVariableModifiers() {
return fLocalVariableModifiers;
}
/**
* Sets the modifiers of the local variables
* visible in this evaluation, possibly empty.
*
* @param localVariableModifiers array of modifiers
*/
private void setLocalVariableModifiers(int[] localVariableModifiers) {
fLocalVariableModifiers = localVariableModifiers;
}
/**
* Returns the names of the local variables
* visible in this evaluation, possibly empty.
*
* @param array of names
*/
private String[] getLocalVariableNames() {
return fLocalVariableNames;
}
/**
* Sets the names of the local variables
* visible in this evaluation, possibly empty.
*
* @param localVariableNames array of names
*/
private void setLocalVariableNames(String[] localVariableNames) {
fLocalVariableNames = localVariableNames;
}
/**
* Returns the type names of the local variables
* visible in this evaluation, possibly empty.
*
* @param array of type names
*/
private String[] getLocalVariableTypeNames() {
return fLocalVariableTypeNames;
}
/**
* Sets the type names of the local variables
* visible in this evaluation, possibly empty.
*
* @param localVariableTypeNames array of type names
*/
private void setLocalVariableTypeNames(String[] localVariableTypeNames) {
fLocalVariableTypeNames = localVariableTypeNames;
}
/**
* Sets the receiver context for the associated evaluation,
* possibly <code>null</code> if the evaluation is
* in the context of a static method or there is
* no object context.
*
* @param thisObject the receiver content of the
* associated evaluation, or <code>null</code>
*/
private void setThis(IJavaObject thisObject) {
fThis = thisObject;
}
/**
* Returns the receiver context for the associated evaluation,
* or <code>null</code> if the evaluation is
* in the context of a static method or there is
* no object context.
*
* @return the receiver context of the associated
* evaluation or <code>null</code>
*/
private IJavaObject getThis() {
return fThis;
}
/**
* Returns a copy of the type name with '$' replaced by
* '.', or returns <code>null</code> if the given type
* name refers to an anonymous inner class.
*
* @param typeName a fully qualified type name
* @return a copy of the type name with '$' replaced by
* '.', or returns <code>null</code> if the given type
* name refers to an anonymous inner class.
*/
protected String getTranslatedTypeName(String typeName) {
int index = typeName.lastIndexOf('$');
if (index == -1) {
return typeName;
}
if (index + 1 > typeName.length()) {
// invalid name
return typeName;
}
String last = typeName.substring(index + 1);
try {
Integer.parseInt(last);
return null;
} catch (NumberFormatException e) {
return typeName.replace('$', '.');
}
}
/**
* Returns the receiving type of the the given stack frame.
*
* @return receiving type
* @exception DebugException if:<ul>
* <li>A failure occurs while accessing attributes of
* the stack frame</li>
* <li>the resolved type is an inner type</li>
* <li>unable to resolve a type</li>
* </ul>
*/
private IType getReceivingType(IJavaStackFrame frame) throws DebugException {
String typeName = frame.getReceivingTypeName();
String sourceName =frame.getSourceName();
if (sourceName == null || !typeName.equals(frame.getDeclaringTypeName())) {
// if there is no debug attribute or the declaring type is not the
// same as the receiving type, we must guess at the receiver's source
// file
int dollarIndex= typeName.indexOf('$');
if (dollarIndex >= 0) {
typeName= typeName.substring(0, dollarIndex);
}
typeName = typeName.replace('.', IPath.SEPARATOR);
typeName+= ".java"; //$NON-NLS-1$
} else {
int index = typeName.lastIndexOf('.');
if (index >= 0) {
typeName = typeName.substring(0, index + 1);
typeName = typeName.replace('.', IPath.SEPARATOR);
} else {
typeName = ""; //$NON-NLS-1$
}
typeName+=sourceName;
}
IPath sourcePath = new Path(typeName);
IType type = null;
try {
IJavaElement result = getJavaProject().findElement(sourcePath);
String[] typeNames = getNestedTypeNames(frame.getReceivingTypeName());
if (result != null) {
if (result instanceof IClassFile) {
type = ((IClassFile)result).getType();
} else if (result instanceof ICompilationUnit) {
type = ((ICompilationUnit)result).getType(typeNames[0]);
}
}
for (int i = 1; i < typeNames.length; i++) {
type = type.getType(typeNames[i]);
}
} catch (JavaModelException e) {
throw new DebugException(e.getStatus());
}
if (type == null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_determine_receiving_type_context._18"), null) //$NON-NLS-1$
);
}
if (type.getDeclaringType() != null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_in_context_of_inner_type_not_supported._19"), null) //$NON-NLS-1$
);
}
return type;
}
/**
* Returns the type of the the given object.
*
* @return type
* @exception DebugException if:<ul>
* <li>A failure occurs while accessing attributes of
* the object</li>
* </ul>
*/
private IType getReceivingType(IJavaObject object) throws DebugException {
String typeName = object.getJavaType().getName();
// we must guess at the receiver's source file name
int dollarIndex= typeName.indexOf('$');
if (dollarIndex >= 0) {
typeName= typeName.substring(0, dollarIndex);
}
return getType(typeName);
}
/**
* Returns the type associated with the specified
* name in this evaluation engine's associated Java project.
*
* @param typeName fully qualified name of type, for
* example, <code>java.lang.String</code>
* @return main type associated with source file
* @exception DebugException if:<ul>
* <li>the resolved type is an inner type</li>
* <li>unable to resolve a type</li>
* <li>a lower level Java exception occurs</li>
* </ul>
*/
private IType getType(String typeName) throws DebugException {
String path = typeName.replace('.', IPath.SEPARATOR);
path+= ".java"; //$NON-NLS-1$
IPath sourcePath = new Path(path);
IType type = null;
try {
IJavaElement result = getJavaProject().findElement(sourcePath);
String[] typeNames = getNestedTypeNames(typeName);
if (result != null) {
if (result instanceof IClassFile) {
type = ((IClassFile)result).getType();
} else if (result instanceof ICompilationUnit) {
type = ((ICompilationUnit)result).getType(typeNames[0]);
}
}
for (int i = 1; i < typeNames.length; i++) {
type = type.getType(typeNames[i]);
}
} catch (JavaModelException e) {
throw new DebugException(e.getStatus());
}
if (type == null) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_failed_-_unable_to_determine_receiving_type_context._18"), null) //$NON-NLS-1$
);
}
if (type.getParent() instanceof IType) {
throw new DebugException(
new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(),
DebugException.REQUEST_FAILED, EvaluationMessages.getString("LocalEvaluationEngine.Evaluation_in_context_of_inner_type_not_supported._19"), null) //$NON-NLS-1$
);
}
return type;
}
/**
* Returns an array of simple type names that are
* part of the given type's qualified name. For
* example, if the given name is <code>x.y.A$B</code>,
* an array with <code>["A", "B"]</code> is returned.
*
* @param typeName fully qualified type name
* @return array of nested type names
*/
protected String[] getNestedTypeNames(String typeName) {
int index = typeName.lastIndexOf('.');
if (index >= 0) {
typeName= typeName.substring(index + 1);
}
index = typeName.indexOf('$');
ArrayList list = new ArrayList(1);
while (index >= 0) {
list.add(typeName.substring(0, index));
typeName = typeName.substring(index + 1);
index = typeName.indexOf('$');
}
list.add(typeName);
return (String[])list.toArray(new String[list.size()]);
}
/**
* @see IClassFileEvaluationEngine#getImports()
*/
public String[] getImports() {
return getEvaluationContext().getImports();
}
/**
* @see IClassFileEvaluationEngine#setImports(String[])
*/
public void setImports(String[] imports) {
getEvaluationContext().setImports(imports);
}
/**
* Sets the name of the code snippet to instantiate
* to run the current evaluation.
*
* @param name the name of the deployed code snippet
* to instantiate and run
*/
private void setCodeSnippetClassName(String name) {
fCodeSnippetClassName = name;
}
/**
* Returns the name of the code snippet to instantiate
* to run the current evaluation.
*
* @return the name of the deployed code snippet
* to instantiate and run
*/
protected String getCodeSnippetClassName() {
return fCodeSnippetClassName;
}
/**
* @see ICodeSnippetRequestor#isRequestingClassFiles()
*/
public boolean isRequestingClassFiles() {
return true;
}
/**
* Returns whether to hit breakpoints in the evaluation thread.
*
* @return whether to hit breakpoints in the evaluation thread
*/
protected boolean getHitBreakpoints() {
return fHitBreakpoints;
}
/**
* Sets whether to hit breakpoints in the evaluation thread.
* @param hit whether to hit breakpoints in the evaluation thread
*/
private void setHitBreakpoints(boolean hit) {
fHitBreakpoints = hit;
}
}