/******************************************************************************* * Copyright (c) 2000, 2015 IBM Corporation and others. * 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: * 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.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.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; 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.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.IEvaluationEngine; import org.eclipse.jdt.debug.eval.IEvaluationListener; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; import org.eclipse.jdt.internal.debug.core.JavaDebugUtils; import org.eclipse.jdt.internal.debug.core.model.JDIDebugTarget; import org.eclipse.jdt.internal.debug.core.model.JDIValue; import com.ibm.icu.text.MessageFormat; 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<File> fSnippetFiles; /** * Collection of directories created by this evaluation engine. */ private List<File> 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]; /** * Constructs 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) */ @Override 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; } @Override 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 (IVariable field : fields) { if (field.getName().equals(RESULT_TYPE_FIELD)) { resultType = (IJavaVariable) field; } if (field.getName().equals(RESULT_VALUE_FIELD)) { resultValue = (IJavaVariable) field; } } 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 place-holders 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 (IJavaVariable local : locals) { 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.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__4, null)); } 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.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize___this___context__5, null)); } field.setValue(thisObject); } } /** * Restores the value local variables from the instance variables in the * 'code snippet object' that are used as place-holders 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 (IJavaVariable local : locals) { 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.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__6, null)); } local.setValue(field.getValue()); } } } /** * @see ICodeSnippetRequestor#acceptProblem(IMarker, String, int) */ @Override 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() */ @Override 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() */ @Override 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) */ @Override 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() { @Override 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())); } finally { LocalEvaluationEngine.this.evaluationComplete(); } } }; Thread t = new Thread(r); t.setDaemon(true); t.start(); } catch (DebugException d) { evaluationAborted(); throw d; } } /** * @see IEvaluationEngine#evaluate(String, IJavaStackFrame, * IEvaluationListener, int) */ @Override 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<String> typeNames = new ArrayList<>(locals.length); List<String> varNames = new ArrayList<>(locals.length); for (IJavaVariable var : locals) { String typeName = getTranslatedTypeName(var .getReferenceTypeName()); if (typeName != null) { typeNames.add(typeName); varNames.add(var.getName()); } } setLocalVariableTypeNames(typeNames .toArray(new String[typeNames.size()])); setLocalVariableNames(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 = JavaDebugUtils .resolveDeclaringType(frame); validateReceivingType(receivingType); // do the evaluation in a different thread Runnable r = new Runnable() { @Override 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())); } finally { LocalEvaluationEngine.this.evaluationComplete(); } } }; Thread t = new Thread(r); t.setDaemon(true); t.start(); } catch (DebugException d) { evaluationAborted(); throw d; } catch (CoreException e) { evaluationAborted(); throw new DebugException(e.getStatus()); } } /** * Verifies the receiving type was resolved and is not an inner type. * * @param receivingType * @throws DebugException */ private void validateReceivingType(final IType receivingType) throws DebugException { if (receivingType == null) { throw new DebugException( new Status( IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED, EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_determine_receiving_type_context__18, null)); } if (receivingType.getDeclaringType() != null) { throw new DebugException( new Status( IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED, EvaluationMessages.LocalEvaluationEngine_Evaluation_in_context_of_inner_type_not_supported__19, null)); } } /** * @see IEvaluationEngine#evaluate(String, IJavaObject, IJavaThread, * IEvaluationListener, int) */ @Override 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 = JavaDebugUtils.resolveType(thisContext .getJavaType()); validateReceivingType(receivingType); // do the evaluation in a different thread Runnable r = new Runnable() { @Override 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())); } finally { LocalEvaluationEngine.this.evaluationComplete(); } } }; Thread t = new Thread(r); t.setDaemon(true); t.start(); } catch (DebugException d) { evaluationAborted(); throw d; } catch (CoreException e) { evaluationAborted(); throw new DebugException(e.getStatus()); } } /** * 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.LocalEvaluationEngine_Evaluation_failed___evaluation_context_has_been_disposed__7, null)); } } /** * 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.LocalEvaluationEngine_Evaluation_failed___evaluation_thread_must_be_suspended__8, null)); } } /** * Deletes deployed class files, and clears state. * * @see IEvaluationEngine#dispose() */ @Override public void dispose() { fDisposed = true; ENGINE_COUNT--; if (isEvaluating()) { // cannot dispose if in an evaluation, must // wait for evaluation to complete return; } List<File> snippetFiles = getSnippetFiles(); Iterator<File> iter = snippetFiles.iterator(); while (iter.hasNext()) { 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("Unable to delete temporary evaluation class file {0}.", new Object[] { file.getAbsolutePath() }), null) //$NON-NLS-1$ ); } } } List<File> directories = getDirectories(); // remove directories in bottom up order int i = directories.size() - 1; while (i >= 0) { File dir = 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("Unable to delete temporary evaluation directory {0}.", new Object[] { 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(); } try (FileOutputStream stream = new FileOutputStream(classFile)) { stream.write(classFiles[i]); } LocalEvaluationEngine.this.addSnippetFile(classFile); } catch (IOException e) { throw new DebugException( new Status( IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED, MessageFormat .format(EvaluationMessages.LocalEvaluationEngine__0__occurred_deploying_class_file_for_evaluation_9, new Object[] { e.toString() }), e)); } } } /** * Adds the given file to this engine's collection of deployed snippet class * files, which are to be deleted when this engine is disposed. * * @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 created directories, * which are to be deleted when this engine is disposed. * * @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 associated 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<File> 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<File> 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 * evaluation 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.LocalEvaluationEngine_Evaluation_failed___unable_to_instantiate_code_snippet_class__11, null)); } 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; } /** * Interprets 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 interpreted based on the * result type. * <p> * Objects as well as primitive 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 interpreted as a * <code>java.lang.Integer</code>. * </p> * * @param resultType * the class of the result * @param resultValue * the value of the result, to be interpreted based on * resultType * @return the result of running the code snippet 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 (IVariable var2 : vars) { IJavaVariable jv = (IJavaVariable) var2; 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.LocalEvaluationEngine_Evaluation_failed___internal_error_retreiving_result__17, null)); } /** * 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 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<String> 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 list.toArray(new String[list.size()]); } /** * @see IClassFileEvaluationEngine#getImports() */ @Override public String[] getImports() { return getEvaluationContext().getImports(); } /** * @see IClassFileEvaluationEngine#setImports(String[]) */ @Override 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; } }