diff options
author | Jesper Steen Møller | 2020-02-08 22:52:46 +0000 |
---|---|---|
committer | Jesper Moller | 2020-02-08 23:32:58 +0000 |
commit | d9744f28d9ca87d7973aa4ac4266bf497c280e46 (patch) | |
tree | 55327119345cd12b5b8b22e64addd3aa2a3e84ae | |
parent | d0f47b7a782b14bcb12d0cc0ded0000b005fafb3 (diff) | |
download | eclipse.jdt.debug-d9744f28d9ca87d7973aa4ac4266bf497c280e46.tar.gz eclipse.jdt.debug-d9744f28d9ca87d7973aa4ac4266bf497c280e46.tar.xz eclipse.jdt.debug-d9744f28d9ca87d7973aa4ac4266bf497c280e46.zip |
Bug 448473 - MVP for debugger expressions with lambdas
12 files changed, 2907 insertions, 13 deletions
diff --git a/org.eclipse.jdt.debug.tests/java8/EvalTest18.java b/org.eclipse.jdt.debug.tests/java8/EvalTest18.java index 02e72ba0a..b7c82cbb1 100644 --- a/org.eclipse.jdt.debug.tests/java8/EvalTest18.java +++ b/org.eclipse.jdt.debug.tests/java8/EvalTest18.java @@ -19,5 +19,17 @@ public class EvalTest18 { public static void main(String[] args) { List<String> strings = Arrays.asList("One", "Two", "Three"); System.out.println("Count of strings in stream from array =" + strings.stream().count()); + nextMain(); } + + public static void nextMain() { + int someInt = 22; List<Integer> l = Arrays.asList(1,2,3,4); + + System.out.println("Just some int = " + someInt); + + for (int i : l ) { + System.out.println("i = " + i); + } + } + }
\ No newline at end of file diff --git a/org.eclipse.jdt.debug.tests/java8/FunctionalCaptureTest18.java b/org.eclipse.jdt.debug.tests/java8/FunctionalCaptureTest18.java new file mode 100644 index 000000000..32ef5cafc --- /dev/null +++ b/org.eclipse.jdt.debug.tests/java8/FunctionalCaptureTest18.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2020 Jesper Steen Møller and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jesper Steen Møller - initial API and implementation + *******************************************************************************/ + +import java.util.Objects; +import java.util.function.Function; + +public class FunctionalCaptureTest18 { + + FunctionalCaptureTest18(int field) { + publicField = field; + } + + public static void main(String[] args) { + new FunctionalCaptureTest18(1).runAssertions(1); + } + + static <I, O> O assertFunctionalExpression(Function<I, O> functional, I input, O expected) { + O result = functional.apply(input); + if (!Objects.equals(result, expected)) { + throw new RuntimeException("Expected " + expected + ", got " + result); + } + return result; + } + + @Override + public int hashCode() { + return 1992; + } + + @Override + public boolean equals(Object obj) { + if (this == obj || obj == null) + return false; + return getClass() == obj.getClass(); + } + + final public int publicField; + private int privateField = 2; + + public static int publicStaticField = 3; + private static int privateStaticField = 4; + + public Integer publicMethod() { + return 5; + } + private Integer privateMethod() { + return 6; + } + public int publicArgMethod(int i) { + return i + 6; + } + private int privateArgMethod(int i) { + return i + 7; + } + public static int publicStaticMethod(int i) { + return i - 8; + } + private static int privateStaticMethod(int i) { + return i - 9; + } + + public void runAssertions(int parameter /* = 1 */) { + int localConstI = -3; + System.out.println("Go!"); + /* CHECK EXPRESSIONS BELOW */ + /* Nothing captured */ + assertFunctionalExpression(n -> -3, 42, -3); + assertFunctionalExpression(n -> n / 2 - 7, 10, -2); + + /* Capture locals */ + /* But not yet on project's types */ + assertFunctionalExpression(n -> n + localConstI, 2, -1); + assertFunctionalExpression(n -> n - parameter, parameter, 0); + + /* Capture instance fields */ + /* But not yet on project's types */ + assertFunctionalExpression(n -> n - publicField, 2, 1);/* SKIP */ + assertFunctionalExpression(n -> n - privateField, 4, 2);/* SKIP */ + + /* Capture static fields */ + /* But not yet on project's types */ + assertFunctionalExpression(n -> n - publicStaticField, 6, 3);/* SKIP */ + assertFunctionalExpression(n -> n - privateStaticField, 8, 4);/* SKIP */ + + /* Evaluate unbound method references */ + assertFunctionalExpression(FunctionalCaptureTest18::publicMethod, this, 5);/* SKIP */ + assertFunctionalExpression(FunctionalCaptureTest18::privateMethod, this, 6);/* SKIP */ + + /* Evaluate instance method references */ + assertFunctionalExpression("Hello, "::concat, "World", "Hello, World"); + /* But not yet on project's types */ + assertFunctionalExpression(this::publicArgMethod, 1, 7);/* SKIP */ + assertFunctionalExpression(this::privateArgMethod, 1, 8);/* SKIP */ + + /* Evaluate static method references */ + assertFunctionalExpression(Integer::valueOf, "16", 16); + /* But not yet on project's types */ + assertFunctionalExpression(FunctionalCaptureTest18::publicStaticMethod, 17, 9);/* SKIP */ + assertFunctionalExpression(FunctionalCaptureTest18::privateStaticMethod, 19, 10);/* SKIP */ + + /* Capture methods */ + assertFunctionalExpression(s -> Integer.valueOf(s, 16), "0B", 11); + /* But not yet on project's types */ + assertFunctionalExpression(obj -> obj.publicMethod() + 7, this, 12);/* SKIP */ + assertFunctionalExpression(obj -> this.privateMethod() + 7, this, 13);/* SKIP */ + assertFunctionalExpression(obj -> obj.publicMethod() + 9, this, 14);/* SKIP */ + assertFunctionalExpression(obj -> this.privateMethod() + 9, this, 15);/* SKIP */ + + /* Constructor references */ + assertFunctionalExpression(String::new, new char[] { 'a','b','c' }, "abc"); + /* But not yet on project's types */ + assertFunctionalExpression(FunctionalCaptureTest18::new, 42, new FunctionalCaptureTest18(42));/* SKIP */ + + /* END OF TESTS */ + System.out.println("OK"); + } +} diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java index 9a3e82664..ea193de06 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/AbstractDebugTest.java @@ -169,6 +169,7 @@ import org.eclipse.ui.progress.WorkbenchJob; import org.osgi.service.prefs.BackingStoreException; import com.sun.jdi.InternalException; +import com.sun.jdi.InvocationException; import junit.framework.TestCase; @@ -456,6 +457,7 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation if (!loaded18) { jp = createProject(ONE_EIGHT_PROJECT_NAME, JavaProjectHelper.TEST_1_8_SRC_DIR.toString(), JavaProjectHelper.JAVA_SE_1_8_EE_NAME, false); cfgs.add(createLaunchConfiguration(jp, "EvalTest18")); + cfgs.add(createLaunchConfiguration(jp, "FunctionalCaptureTest18")); cfgs.add(createLaunchConfiguration(jp, "EvalTestIntf18")); cfgs.add(createLaunchConfiguration(jp, "EvalIntfSuperDefault")); cfgs.add(createLaunchConfiguration(jp, "DebugHoverTest18")); @@ -2845,10 +2847,10 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation } IEvaluationResult result = listener.getResult(); assertNotNull("The evaluation should have result: ", result); - assertNull("The evaluation should not have exception : " + result.getException(), result.getException()); + assertNull("Evaluation of '" + snippet + "' should not have exception : " + findCause(result.getException()), result.getException()); String firstError = result.hasErrors() ? result.getErrorMessages()[0] : ""; - assertFalse("The evaluation should not have errors : " + firstError, result.hasErrors()); + assertFalse("The evaluation of '\" + snippet + \"' should not have errors : " + firstError, result.hasErrors()); return listener.getResult().getValue(); } finally { @@ -2856,6 +2858,17 @@ public abstract class AbstractDebugTest extends TestCase implements IEvaluation } } + private static Object findCause(DebugException problem) { + if (problem == null) { + return null; + } + Throwable cause = problem.getCause(); + if (cause instanceof InvocationException) { + return ((InvocationException)cause).exception().toString(); + } + return cause; + } + /** * @return true if the UI event loop should be proicessed during wait operations on UI thread */ diff --git a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/Java8Tests.java b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/Java8Tests.java index 9c14dddbc..9353f40c5 100644 --- a/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/Java8Tests.java +++ b/org.eclipse.jdt.debug.tests/tests/org/eclipse/jdt/debug/tests/eval/Java8Tests.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2014, 2015 Jesper S. Møller and others. + * Copyright (c) 2014, 2020 Jesper S. Møller and others. * * This program and the accompanying materials * are made available under the terms of the Eclipse Public License 2.0 @@ -17,6 +17,7 @@ package org.eclipse.jdt.debug.tests.eval; import org.eclipse.debug.core.model.IValue; +import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaProject; import org.eclipse.jdt.debug.core.IJavaLineBreakpoint; import org.eclipse.jdt.debug.core.IJavaThread; @@ -129,4 +130,115 @@ public class Java8Tests extends AbstractDebugTest { terminateAndRemove(thread); } } + + /** + * Evaluates a snippet containing a lambda + * + * @throws Exception + */ + public void testEvalLambda() throws Exception { + IJavaThread thread = null; + try { + String type = "EvalTest18"; + IJavaLineBreakpoint bp = createLineBreakpoint(28, type); + assertNotNull("should have created breakpoint", bp); + thread = launchToBreakpoint(type); + assertNotNull("The program did not suspend", thread); + String snippet = "l.stream().filter(i -> i > 2).count()"; + IValue result = doEval(thread, snippet); + assertEquals("2", result.getValueString()); + } finally { + removeAllBreakpoints(); + terminateAndRemove(thread); + } + } + + /** + * Evaluates a snippet containing a lambda referencing a variable in a loop + * + * @throws Exception + */ + public void testEvalLambdaInLoop() throws Exception { + IJavaThread thread = null; + try { + String type = "EvalTest18"; + IJavaLineBreakpoint bp = createLineBreakpoint(31, type); + assertNotNull("should have created breakpoint", bp); + thread = launchToBreakpoint(type); + assertNotNull("The program did not suspend", thread); + String snippet = "l.stream().filter(j -> j > i+1).count()"; + IValue result = doEval(thread, snippet); + assertEquals("2", result.getValueString()); + } finally { + removeAllBreakpoints(); + terminateAndRemove(thread); + } + } + + /** + * Evaluates a snippet containing a method reference + * + * @throws Exception + */ + public void testEvalMethodReference() throws Exception { + IJavaThread thread = null; + try { + String type = "EvalTest18"; + IJavaLineBreakpoint bp = createLineBreakpoint(28, type); + assertNotNull("should have created breakpoint", bp); + thread = launchToBreakpoint(type); + assertNotNull("The program did not suspend", thread); + String snippet = "l.stream().mapToInt(Integer::bitCount).sum()"; + IValue result = doEval(thread, snippet); + assertEquals("5", result.getValueString()); + } finally { + removeAllBreakpoints(); + terminateAndRemove(thread); + } + } + + /** + * Evaluates a snippet containing a method reference + * + * @throws Exception + */ + public void testContextEvaluations() throws Exception { + IJavaThread thread = null; + try { + String type = "FunctionalCaptureTest18"; + ICompilationUnit cu = getType(type).getCompilationUnit(); + String[] lines = new String(cu.getBuffer().getCharacters()).split("\n"); + + int i = 0; + for (; i < lines.length; ++i) { + if (lines[i].contains("/* CHECK EXPRESSIONS BELOW */")) break; + } + assertTrue("Missing source marker", i < lines.length); + IJavaLineBreakpoint bp = createLineBreakpoint(i, type); + assertNotNull("should have created breakpoint", bp); + thread = launchToBreakpoint(type); + assertNotNull("The program did not suspend", thread); + + for (; i < lines.length; ++i) { + String line = lines[i]; + + if (line.contains("/* END OF TESTS */")) break; + if (line.trim().startsWith("/") || line.trim().isEmpty()) continue; // Comment, just skip it + if (line.contains("/* SKIP */")) continue; + + int lastSemicolon = line.lastIndexOf(';'); + assertTrue(lastSemicolon > 1); + String snippet = line.substring(0, lastSemicolon).trim(); + //System.out.println("*******************: " + snippet); + IValue result = doEval(thread, snippet); + assertNotNull(result); + //System.out.println(">>>>>>>>>>>>>>>>>>>: " + result.getReferenceTypeName()); + } + + } finally { + removeAllBreakpoints(); + terminateAndRemove(thread); + } + } + } diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ExpressionBinder.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ExpressionBinder.java new file mode 100644 index 000000000..af86d6dca --- /dev/null +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ExpressionBinder.java @@ -0,0 +1,8 @@ +package org.eclipse.jdt.internal.debug.eval; + +import org.eclipse.jdt.core.dom.IVariableBinding; + +public interface ExpressionBinder { + // Record and register the binding specified + void bind(IVariableBinding bind, String asVariableName); +} diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluator.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluator.java new file mode 100644 index 000000000..02224b9b7 --- /dev/null +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluator.java @@ -0,0 +1,422 @@ +/******************************************************************************* + + * Copyright (c) 2019 Jesper Steen Møller and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jesper Steen Møller - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.eval; + +import static org.eclipse.jdt.internal.eval.EvaluationConstants.LOCAL_VAR_PREFIX; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.debug.core.DebugException; +import org.eclipse.debug.core.model.IVariable; +import org.eclipse.jdt.core.eval.ICodeSnippetRequestor; +import org.eclipse.jdt.debug.core.IJavaArray; +import org.eclipse.jdt.debug.core.IJavaArrayType; +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.IJavaFieldVariable; +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.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.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 to a debuggee by using Unsafe through the JDWP. + */ + +public class RemoteEvaluator { + + private final LinkedHashMap<String, byte[]> classFiles; + + private final String codeSnippetClassName; + + private final List<String> variableNames; + + private IJavaClassObject loadedClass = null; + + /** + * 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 codeSnippetClassName + * @param classFiles2 + * @param variableNames + */ + public RemoteEvaluator(LinkedHashMap<String, byte[]> classFiles, String codeSnippetClassName, List<String> variableNames) { + this.classFiles = classFiles; + this.codeSnippetClassName = codeSnippetClassName.replace('.', '/'); + this.variableNames = variableNames; + } + + private IJavaClassObject loadTheClasses(IJavaThread theThread) throws DebugException { + + if (loadedClass != null) { + return loadedClass; + } + + JDIDebugTarget debugTarget = ((JDIDebugTarget) theThread.getDebugTarget()); + IJavaClassType unsafeClass = (IJavaClassType) findType("sun.misc.Unsafe", debugTarget); //$NON-NLS-1$ + + // IJavaValue[] getDeclaredFieldArgs = new IJavaValue[] { getDebugTarget().newValue("theUnsafe") }; //$NON-NLS-1$ + IJavaFieldVariable theField = unsafeClass.getField("theUnsafe"); //$NON-NLS-1$ + IJavaObject theUnsafe = (IJavaObject) theField.getValue(); + + // IJavaValue[] setAccessibleArgs = new IJavaValue[] { getDebugTarget().newValue(true) }; + // theField.sendMessage( + // "setAccessible", "(Z)V", setAccessibleArgs, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$ + + // IJavaValue[] getArgs = new IJavaValue[] { getDebugTarget().newValue(null) }; + // IJavaObject theUnsafe = (IJavaObject) theField + // .sendMessage( + // "get", "()Ljava/lang/Object;", getArgs, getThread(), false); //$NON-NLS-2$ //$NON-NLS-1$ + + IJavaClassObject theMainClass = null; + + IJavaReferenceType byteArrayType = findType("byte[]", debugTarget);//$NON-NLS-1$ + + for (Map.Entry<String, byte[]> entry : classFiles.entrySet()) { + String className = entry.getKey(); + + IJavaReferenceType existingClass = tryLoadType(className, debugTarget); + if (existingClass != null) { + if (codeSnippetClassName.equals(className)) { + theMainClass = existingClass.getClassObject(); + } + } else { + byte[] classBytes = entry.getValue(); + IJavaArray byteArray = ((IJavaArrayType) byteArrayType).newInstance(classBytes.length); + + IJavaValue[] debugClassBytes = new IJavaValue[classBytes.length]; + for (int ix = 0; ix < classBytes.length; ++ix) { + debugClassBytes[ix] = ((JDIDebugTarget) theThread.getDebugTarget()).newValue(classBytes[ix]); + } + byteArray.setValues(debugClassBytes); + IJavaValue[] defineClassArgs = new IJavaValue[] { + debugTarget.newValue(className), + byteArray, // classBytes, + debugTarget.newValue(0), debugTarget.newValue(classBytes.length), debugTarget.nullValue(), // classloader + debugTarget.nullValue() // protection domain + }; + IJavaClassObject theClass = (IJavaClassObject) theUnsafe.sendMessage("defineClass", "(Ljava/lang/String;[BIILjava/lang/ClassLoader;Ljava/security/ProtectionDomain;)Ljava/lang/Class;", defineClassArgs, theThread, false); //$NON-NLS-1$//$NON-NLS-2$ + if (codeSnippetClassName.equals(className)) { + theMainClass = theClass; + } + } + } + return theMainClass; + } + + private IJavaReferenceType findType(String typeName, IJavaDebugTarget debugTarget) throws DebugException { + IJavaReferenceType theClass = tryLoadType(typeName, debugTarget); + if (theClass == 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)); + } + return theClass; + } + + private IJavaReferenceType tryLoadType(String typeName, IJavaDebugTarget debugTarget) throws DebugException { + IJavaReferenceType clazz = null; + IJavaType[] types = debugTarget.getJavaTypes(typeName); + if (types != null && types.length > 0) { + clazz = (IJavaReferenceType) types[0]; + } + return clazz; + } + + /** + * Initializes the value of instance variables in the 'code snippet object' that are used as place-holders for free variables and 'this' in the + * current stack frame. + * + * @param object + * instance of code snippet class that will be run + * @param boundValues + * popped values which should be injected into the code snippet object. + * @exception DebugException + * if an exception is thrown accessing the given object + */ + protected void initializeFreeVars(IJavaObject object, IJavaValue boundValues[]) throws DebugException { + if (boundValues.length != this.variableNames.size()) { + throw new DebugException(new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED, EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__4, null)); + } + + for (int i = 0; i < boundValues.length; ++i) { + IJavaVariable field = object.getField(new String(LOCAL_VAR_PREFIX) + this.variableNames.get(i), false); + if (field != null) { + IJavaValue bound = boundValues[i]; + field.setValue(bound); + } else { + // System.out.print(Arrays.asList(((IJavaReferenceType) object.getJavaType()).getAllFieldNames())); + throw new DebugException(new Status(IStatus.ERROR, JDIDebugModel.getPluginIdentifier(), DebugException.REQUEST_FAILED, EvaluationMessages.LocalEvaluationEngine_Evaluation_failed___unable_to_initialize_local_variables__4, null)); + } + } + } + + /** + * 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(IJavaThread theThread) throws DebugException { + IJavaDebugTarget debugTarget = ((IJavaDebugTarget) theThread.getDebugTarget()); + + IJavaObject object = null; + IJavaClassObject clazz = loadTheClasses(theThread); + if (clazz == null) { + // The class is not loaded on the target VM. + // Force the load of the class. + IJavaType[] types = debugTarget.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[] { debugTarget.newValue( + this.codeSnippetClassName) }; + IJavaObject classObject = (IJavaObject) classClass + .sendMessage( + "forName", "(Ljava/lang/String;)Ljava/lang/Class;", args, theThread); //$NON-NLS-2$ //$NON-NLS-1$ + object = (IJavaObject) classObject + .sendMessage( + "newInstance", "()Ljava/lang/Object;", null, theThread, false); //$NON-NLS-2$ //$NON-NLS-1$ + } else { + object = (IJavaObject) clazz.sendMessage("newInstance", "()Ljava/lang/Object;", null, theThread, false); //$NON-NLS-2$ //$NON-NLS-1$ + // object = clazz.newInstance("<init>", null, theThread); //$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(IJavaDebugTarget debugTarget, 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 debugTarget.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 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()]); + } + + /** + /** + * 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 codeSnippetClassName; + } + + public IJavaValue evaluate(IJavaThread theThread, IJavaValue[] args) throws DebugException { + IJavaObject codeSnippetInstance = null; + IJavaDebugTarget debugTarget = ((IJavaDebugTarget) theThread.getDebugTarget()); + try { + codeSnippetInstance = newInstance(theThread); + initializeFreeVars(codeSnippetInstance, args); + codeSnippetInstance.sendMessage(ICodeSnippetRequestor.RUN_METHOD, "()V", null, theThread, false); //$NON-NLS-1$ + + // 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(ICodeSnippetRequestor.RESULT_TYPE_FIELD)) { + resultType = (IJavaVariable) field; + } + if (field.getName().equals(ICodeSnippetRequestor.RESULT_VALUE_FIELD)) { + resultValue = (IJavaVariable) field; + } + } + IJavaValue result = convertResult(debugTarget, (IJavaClassObject) resultType.getValue(), (IJavaValue) resultValue.getValue()); + return result; + } catch (DebugException 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) debugTarget, theException); + v.sendMessage("printStackTrace", "()V", null, theThread, false); //$NON-NLS-2$ //$NON-NLS-1$ + } catch (DebugException de) { + JDIDebugPlugin.log(de); + } + } catch (RuntimeException re) { + JDIDebugPlugin.log(re); + } + } + } + throw e; + } + } + + public int getVariableCount() { + return this.variableNames.size(); + } + + public String getVariableName(int i) { + return this.variableNames.get(i); + } +} diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluatorBuilder.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluatorBuilder.java new file mode 100644 index 000000000..acc9761ba --- /dev/null +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/RemoteEvaluatorBuilder.java @@ -0,0 +1,2064 @@ +/******************************************************************************* + * Copyright (c) 2019 Jesper Steen Møller and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jesper Steen Møller - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.eval; + +import static org.eclipse.jdt.internal.eval.EvaluationConstants.LOCAL_VAR_PREFIX; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.core.dom.*; +import org.eclipse.jdt.core.eval.ICodeSnippetRequestor; + +/** + * A builder for a reuseable expression evaluator against a runnng VM. + */ + +@SuppressWarnings("rawtypes") +public class RemoteEvaluatorBuilder { + + private IJavaProject javaProject; + private ExpressionBinder binder; + private String enclosingTypeName; + private boolean isStatic; + private boolean isConstructor; + private List<String> argumentNames = new ArrayList<>(); + private List<String> argumentTypeNames = new ArrayList<>(); + + /** + * The names and bytecodes of the code snippet class to instantiate + */ + private LinkedHashMap<String, byte[]> classFiles = new LinkedHashMap<>(); + + /** + * The name of the code snippet class to instantiate + */ + private String codeSnippetClassName = null; + private String snippet = null; + + public RemoteEvaluatorBuilder(IJavaProject javaProject, ExpressionBinder binder, String enclosingTypeName, boolean isStatic, boolean isConstructor) { + this.javaProject = javaProject; + this.binder = binder; + this.enclosingTypeName = enclosingTypeName; + this.isStatic = isStatic; + this.isConstructor = isConstructor; + } + + public void acceptLambda(LambdaExpression lambda, ITypeBinding expectedResult) { + acceptFunctionalExpression(lambda, expectedResult); + } + + public void acceptMethodReference(MethodReference node, ITypeBinding expectedResult) { + acceptFunctionalExpression(node, expectedResult); + } + + private void acceptFunctionalExpression(Expression node, ITypeBinding expectedResult) { + FunctionalEvalVisitor visitor = new FunctionalEvalVisitor(); + node.accept(visitor); + String castExpression = "(" + expectedResult.getQualifiedName() + ")"; //$NON-NLS-1$//$NON-NLS-2$ + this.snippet = castExpression + "(" + visitor.buffer.toString() + ")"; //$NON-NLS-1$//$NON-NLS-2$ + } + + public String getSnippet() { + return snippet; + } + + private static Object EVALUATE_CODE_SNIPPET_LOCK = new Object(); + + public RemoteEvaluator build() throws JavaModelException { + + List<String> boundVariableNames = getVariableNames(); + List<String> boundVariableTypeNames = getVariableTypeNames(); + + List<String> errors = new ArrayList<>(); + IType enclosingType = this.javaProject.findType(enclosingTypeName); + + synchronized (EVALUATE_CODE_SNIPPET_LOCK) { + this.javaProject.newEvaluationContext().evaluateCodeSnippet(this.snippet, boundVariableTypeNames.toArray(new String[boundVariableNames.size()]), boundVariableNames.toArray(new String[boundVariableNames.size()]), new int[boundVariableNames.size()], enclosingType, isStatic, isConstructor, new ICodeSnippetRequestor() { + + @Override + public void acceptProblem(IMarker problemMarker, String fragmentSource, int fragmentKind) { + errors.add(problemMarker.toString()); + } + + @Override + public boolean acceptClassFiles(byte[][] classFileBytes, String[][] classFileCompoundNames, String mainCodeSnippetClassName) { + for (int i = 0; i < classFileCompoundNames.length; ++i) { + String className = makeClassName(classFileCompoundNames[i]); + classFiles.put(className, classFileBytes[i]); + } + if (mainCodeSnippetClassName != null) { + setCodeSnippetClassName(mainCodeSnippetClassName); + } + return true; + } + }, null); + } + + if (!errors.isEmpty()) { + //LE//$NON-NLS-1$ + //LE//$NON-NLS-1$ + throw new RuntimeException(errors.toString()); + } + + return new RemoteEvaluator(classFiles, codeSnippetClassName, getVariableNames()); + } + + private void setCodeSnippetClassName(String codeSnippetClassName) { + this.codeSnippetClassName = codeSnippetClassName; + } + + private static String makeClassName(String[] names) { + StringBuilder sb = new StringBuilder(); + for (int j = 0; j < names.length; j++) { + if (j > 0) { + sb.append('/'); + } + sb.append(names[j]); + } + return sb.toString(); + } + + public List<String> getVariableTypeNames() { + return Collections.unmodifiableList(argumentTypeNames); + } + + public List<String> getVariableNames() { + return Collections.unmodifiableList(argumentNames); + } + + public String allocateNewVariable(IVariableBinding binding) { + String varName = "bound$" + argumentNames.size(); //$NON-NLS-1$ + argumentNames.add(varName); + argumentTypeNames.add(binding.getType().getQualifiedName()); + return varName; + } + + public IJavaProject getJavaProject() { + return javaProject; + } + + public String getEnclosingTypeName() { + return enclosingTypeName; + } + + public boolean isStatic() { + return isStatic; + } + + /** + * Internal synonym for {@link MethodDeclaration#getReturnType()}. Use to alleviate deprecation warnings. + * + * @deprecated + * @since 3.4 + */ + @Deprecated + private static Type getReturnType(MethodDeclaration node) { + return node.getReturnType(); + } + + /** + * Internal synonym for {@link TypeDeclaration#getSuperclass()}. Use to alleviate deprecation warnings. + * + * @deprecated + * @since 3.4 + */ + @Deprecated + private static Name getSuperclass(TypeDeclaration node) { + return node.getSuperclass(); + } + + /** + * Internal synonym for {@link TypeDeclarationStatement#getTypeDeclaration()}. Use to alleviate deprecation warnings. + * + * @deprecated + * @since 3.4 + */ + @Deprecated + private static TypeDeclaration getTypeDeclaration(TypeDeclarationStatement node) { + return node.getTypeDeclaration(); + } + + /** + * Internal synonym for {@link MethodDeclaration#thrownExceptions()}. Use to alleviate deprecation warnings. + * + * @deprecated + * @since 3.10 + */ + @Deprecated + private static List<?> thrownExceptions(MethodDeclaration node) { + return node.thrownExceptions(); + } + + private class FunctionalEvalVisitor extends ASTVisitor { + + /** + * The string buffer into which the serialized representation of the AST is written. + */ + protected StringBuilder buffer = new StringBuilder(); + + /** + * Returns the string accumulated in the visit. + * + * @return the serialized + */ + // public String getResult() { + // return buffer.toString(); + // } + + // public Map<String, ITypeBinding> getCaptures() { + // return Collections.emptyMap(); + // } + + private int indent = 2; + + private Map<IBinding, String> localBindings = new HashMap<>(); + + public FunctionalEvalVisitor() { + } + + boolean isLocalBinding(IBinding binding) { + return localBindings.containsKey(binding); + } + + void addLocalBinding(IBinding binding, String name) { + localBindings.put(binding, name); + } + + void printIndent() { + for (int i = 0; i < indent; i++) { + buffer.append(" ");//$NON-NLS-1$ + } + } + + /** + * Internal synonym for {@link AST#JLS2}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private static final int JLS2 = AST.JLS2; + + /** + * Internal synonym for {@link AST#JLS3}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private static final int JLS3 = AST.JLS3; + + /** + * Internal synonym for {@link AST#JLS4}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private static final int JLS4 = AST.JLS4; + + /** + * Internal synonym for {@link AST#JLS8}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private static final int JLS8 = AST.JLS8; + + /** + * Internal synonym for {@link AST#JLS9}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private static final int JLS9 = AST.JLS9; + + /** + * Internal synonym for {@link ClassInstanceCreation#getName()}. Use to alleviate deprecation warnings. + * + * @deprecated + */ + @Deprecated + private Name getName(ClassInstanceCreation node) { + return node.getName(); + } + + /** + * Appends the text representation of the given modifier flags, followed by a single space. Used for JLS2 modifiers. + * + * @param modifiers + * the modifier flags + */ + void printModifiers(int modifiers) { + if (Modifier.isPublic(modifiers)) { + buffer.append("public ");//$NON-NLS-1$ + } + if (Modifier.isProtected(modifiers)) { + buffer.append("protected ");//$NON-NLS-1$ + } + if (Modifier.isPrivate(modifiers)) { + buffer.append("private ");//$NON-NLS-1$ + } + if (Modifier.isStatic(modifiers)) { + buffer.append("static ");//$NON-NLS-1$ + } + if (Modifier.isAbstract(modifiers)) { + buffer.append("abstract ");//$NON-NLS-1$ + } + if (Modifier.isFinal(modifiers)) { + buffer.append("final ");//$NON-NLS-1$ + } + if (Modifier.isSynchronized(modifiers)) { + buffer.append("synchronized ");//$NON-NLS-1$ + } + if (Modifier.isVolatile(modifiers)) { + buffer.append("volatile ");//$NON-NLS-1$ + } + if (Modifier.isNative(modifiers)) { + buffer.append("native ");//$NON-NLS-1$ + } + if (Modifier.isStrictfp(modifiers)) { + buffer.append("strictfp ");//$NON-NLS-1$ + } + if (Modifier.isTransient(modifiers)) { + buffer.append("transient ");//$NON-NLS-1$ + } + } + + /** + * Appends the text representation of the given modifier flags, followed by a single space. Used for 3.0 modifiers and annotations. + * + * @param ext + * the list of modifier and annotation nodes (element type: <code>IExtendedModifiers</code>) + */ + void printModifiers(List ext) { + for (Iterator it = ext.iterator(); it.hasNext();) { + ASTNode p = (ASTNode) it.next(); + p.accept(this); + buffer.append(" ");//$NON-NLS-1$ + } + } + + private void printTypes(List<Type> types, String prefix) { + if (types.size() > 0) { + buffer.append(" " + prefix + " ");//$NON-NLS-1$ //$NON-NLS-2$ + Type type = types.get(0); + type.accept(this); + for (int i = 1, l = types.size(); i < l; ++i) { + buffer.append(","); //$NON-NLS-1$ + type = types.get(0); + type.accept(this); + } + } + } + + /** + * reference node helper function that is common to all the difference reference nodes. + * + * @param typeArguments + * list of type arguments + */ + private void visitReferenceTypeArguments(List typeArguments) { + buffer.append("::");//$NON-NLS-1$ + if (!typeArguments.isEmpty()) { + buffer.append('<'); + for (Iterator it = typeArguments.iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(','); + } + } + buffer.append('>'); + } + } + + private void visitTypeAnnotations(AnnotatableType node) { + if (node.getAST().apiLevel() >= JLS8) { + visitAnnotationsList(node.annotations()); + } + } + + private void visitAnnotationsList(List annotations) { + for (Iterator it = annotations.iterator(); it.hasNext();) { + Annotation annotation = (Annotation) it.next(); + annotation.accept(this); + buffer.append(' '); + } + } + + /** + * Internal synonym for {@link TypeDeclaration#superInterfaces()}. Use to alleviate deprecation warnings. + * + * @deprecated + * @since 3.4 + */ + @Deprecated + private List superInterfaces(TypeDeclaration node) { + return node.superInterfaces(); + } + + @Override + public boolean visit(AnnotationTypeDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + printModifiers(node.modifiers()); + buffer.append("@interface ");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append(" {");//$NON-NLS-1$ + for (Iterator it = node.bodyDeclarations().iterator(); it.hasNext();) { + BodyDeclaration d = (BodyDeclaration) it.next(); + d.accept(this); + } + buffer.append("}\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(AnnotationTypeMemberDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + printModifiers(node.modifiers()); + node.getType().accept(this); + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append("()");//$NON-NLS-1$ + if (node.getDefault() != null) { + buffer.append(" default ");//$NON-NLS-1$ + node.getDefault().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(AnonymousClassDeclaration node) { + buffer.append("{\n");//$NON-NLS-1$ + indent++; + for (Iterator it = node.bodyDeclarations().iterator(); it.hasNext();) { + BodyDeclaration b = (BodyDeclaration) it.next(); + b.accept(this); + } + indent--; + printIndent(); + buffer.append("}\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ArrayAccess node) { + node.getArray().accept(this); + buffer.append("[");//$NON-NLS-1$ + node.getIndex().accept(this); + buffer.append("]");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ArrayCreation node) { + buffer.append("new ");//$NON-NLS-1$ + ArrayType at = node.getType(); + int dims = at.getDimensions(); + Type elementType = at.getElementType(); + elementType.accept(this); + for (Iterator it = node.dimensions().iterator(); it.hasNext();) { + buffer.append("[");//$NON-NLS-1$ + Expression e = (Expression) it.next(); + e.accept(this); + buffer.append("]");//$NON-NLS-1$ + dims--; + } + // add empty "[]" for each extra array dimension + for (int i = 0; i < dims; i++) { + buffer.append("[]");//$NON-NLS-1$ + } + if (node.getInitializer() != null) { + node.getInitializer().accept(this); + } + return false; + } + + @Override + public boolean visit(ArrayInitializer node) { + buffer.append("{");//$NON-NLS-1$ + for (Iterator it = node.expressions().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append("}");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ArrayType node) { + if (node.getAST().apiLevel() < JLS8) { + visitComponentType(node); + buffer.append("[]");//$NON-NLS-1$ + } else { + node.getElementType().accept(this); + List dimensions = node.dimensions(); + int size = dimensions.size(); + for (int i = 0; i < size; i++) { + Dimension aDimension = (Dimension) dimensions.get(i); + aDimension.accept(this); + } + } + return false; + } + + @Override + public boolean visit(AssertStatement node) { + printIndent(); + buffer.append("assert ");//$NON-NLS-1$ + node.getExpression().accept(this); + if (node.getMessage() != null) { + buffer.append(" : ");//$NON-NLS-1$ + node.getMessage().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(Assignment node) { + node.getLeftHandSide().accept(this); + buffer.append(node.getOperator().toString()); + node.getRightHandSide().accept(this); + return false; + } + + @Override + public boolean visit(Block node) { + buffer.append("{\n");//$NON-NLS-1$ + indent++; + for (Iterator it = node.statements().iterator(); it.hasNext();) { + Statement s = (Statement) it.next(); + s.accept(this); + } + indent--; + printIndent(); + buffer.append("}\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(BlockComment node) { + printIndent(); + buffer.append("/* */");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(BooleanLiteral node) { + if (node.booleanValue() == true) { + buffer.append("true");//$NON-NLS-1$ + } else { + buffer.append("false");//$NON-NLS-1$ + } + return false; + } + + @Override + public boolean visit(BreakStatement node) { + printIndent(); + buffer.append("break");//$NON-NLS-1$ + if (node.getLabel() != null) { + buffer.append(" ");//$NON-NLS-1$ + node.getLabel().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(CastExpression node) { + buffer.append("(");//$NON-NLS-1$ + node.getType().accept(this); + buffer.append(")");//$NON-NLS-1$ + node.getExpression().accept(this); + return false; + } + + @Override + public boolean visit(CatchClause node) { + buffer.append("catch (");//$NON-NLS-1$ + node.getException().accept(this); + buffer.append(") ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(CharacterLiteral node) { + buffer.append(node.getEscapedValue()); + return false; + } + + @Override + public boolean visit(ClassInstanceCreation node) { + if (node.getExpression() != null) { + node.getExpression().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + buffer.append("new ");//$NON-NLS-1$ + if (node.getAST().apiLevel() == JLS2) { + getName(node).accept(this); + } + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeArguments().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + node.getType().accept(this); + } + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + if (node.getAnonymousClassDeclaration() != null) { + node.getAnonymousClassDeclaration().accept(this); + } + return false; + } + + @Override + public boolean visit(CompilationUnit node) { + if (node.getAST().apiLevel() >= JLS9) { + if (node.getModule() != null) { + node.getModule().accept(this); + } + } + if (node.getPackage() != null) { + node.getPackage().accept(this); + } + for (Iterator it = node.imports().iterator(); it.hasNext();) { + ImportDeclaration d = (ImportDeclaration) it.next(); + d.accept(this); + } + for (Iterator it = node.types().iterator(); it.hasNext();) { + AbstractTypeDeclaration d = (AbstractTypeDeclaration) it.next(); + d.accept(this); + } + return false; + } + + @Override + public boolean visit(ConditionalExpression node) { + node.getExpression().accept(this); + buffer.append(" ? ");//$NON-NLS-1$ + node.getThenExpression().accept(this); + buffer.append(" : ");//$NON-NLS-1$ + node.getElseExpression().accept(this); + return false; + } + + @Override + public boolean visit(ConstructorInvocation node) { + printIndent(); + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeArguments().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + buffer.append("this(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(");\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ContinueStatement node) { + printIndent(); + buffer.append("continue");//$NON-NLS-1$ + if (node.getLabel() != null) { + buffer.append(" ");//$NON-NLS-1$ + node.getLabel().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(CreationReference node) { + node.getType().accept(this); + visitReferenceTypeArguments(node.typeArguments()); + buffer.append("new");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(Dimension node) { + List annotations = node.annotations(); + if (annotations.size() > 0) { + buffer.append(' '); + } + visitAnnotationsList(annotations); + buffer.append("[]"); //$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(DoStatement node) { + printIndent(); + buffer.append("do ");//$NON-NLS-1$ + node.getBody().accept(this); + buffer.append(" while (");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(");\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(EmptyStatement node) { + printIndent(); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(EnhancedForStatement node) { + printIndent(); + buffer.append("for (");//$NON-NLS-1$ + node.getParameter().accept(this); + buffer.append(" : ");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(") ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(EnumConstantDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + printModifiers(node.modifiers()); + node.getName().accept(this); + if (!node.arguments().isEmpty()) { + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + } + if (node.getAnonymousClassDeclaration() != null) { + node.getAnonymousClassDeclaration().accept(this); + } + return false; + } + + @Override + public boolean visit(EnumDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + printModifiers(node.modifiers()); + buffer.append("enum ");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append(" ");//$NON-NLS-1$ + if (!node.superInterfaceTypes().isEmpty()) { + buffer.append("implements ");//$NON-NLS-1$ + for (Iterator it = node.superInterfaceTypes().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + buffer.append("{");//$NON-NLS-1$ + for (Iterator it = node.enumConstants().iterator(); it.hasNext();) { + EnumConstantDeclaration d = (EnumConstantDeclaration) it.next(); + d.accept(this); + // enum constant declarations do not include punctuation + if (it.hasNext()) { + // enum constant declarations are separated by commas + buffer.append(", ");//$NON-NLS-1$ + } + } + if (!node.bodyDeclarations().isEmpty()) { + buffer.append("; ");//$NON-NLS-1$ + for (Iterator it = node.bodyDeclarations().iterator(); it.hasNext();) { + BodyDeclaration d = (BodyDeclaration) it.next(); + d.accept(this); + // other body declarations include trailing punctuation + } + } + buffer.append("}\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ExportsDirective node) { + return visit(node, "exports"); //$NON-NLS-1$ + } + + @Override + public boolean visit(ExpressionMethodReference node) { + node.getExpression().accept(this); + visitReferenceTypeArguments(node.typeArguments()); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(ExpressionStatement node) { + printIndent(); + node.getExpression().accept(this); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(FieldAccess node) { + node.getExpression().accept(this); + buffer.append(".");//$NON-NLS-1$ + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(FieldDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + node.getType().accept(this); + buffer.append(" ");//$NON-NLS-1$ + for (Iterator it = node.fragments().iterator(); it.hasNext();) { + VariableDeclarationFragment f = (VariableDeclarationFragment) it.next(); + f.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ForStatement node) { + printIndent(); + buffer.append("for (");//$NON-NLS-1$ + for (Iterator it = node.initializers().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append("; ");//$NON-NLS-1$ + if (node.getExpression() != null) { + node.getExpression().accept(this); + } + buffer.append("; ");//$NON-NLS-1$ + for (Iterator it = node.updaters().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(") ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(IfStatement node) { + printIndent(); + buffer.append("if (");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(") ");//$NON-NLS-1$ + node.getThenStatement().accept(this); + if (node.getElseStatement() != null) { + buffer.append(" else ");//$NON-NLS-1$ + node.getElseStatement().accept(this); + } + return false; + } + + @Override + public boolean visit(ImportDeclaration node) { + printIndent(); + buffer.append("import ");//$NON-NLS-1$ + if (node.getAST().apiLevel() >= JLS3) { + if (node.isStatic()) { + buffer.append("static ");//$NON-NLS-1$ + } + } + node.getName().accept(this); + if (node.isOnDemand()) { + buffer.append(".*");//$NON-NLS-1$ + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(InfixExpression node) { + node.getLeftOperand().accept(this); + buffer.append(' '); // for cases like x= i - -1; or x= i++ + ++i; + buffer.append(node.getOperator().toString()); + buffer.append(' '); + node.getRightOperand().accept(this); + final List extendedOperands = node.extendedOperands(); + if (extendedOperands.size() != 0) { + buffer.append(' '); + for (Iterator it = extendedOperands.iterator(); it.hasNext();) { + buffer.append(node.getOperator().toString()).append(' '); + Expression e = (Expression) it.next(); + e.accept(this); + } + } + return false; + } + + @Override + public boolean visit(Initializer node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(InstanceofExpression node) { + node.getLeftOperand().accept(this); + buffer.append(" instanceof ");//$NON-NLS-1$ + node.getRightOperand().accept(this); + return false; + } + + @Override + public boolean visit(IntersectionType node) { + for (Iterator it = node.types().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(" & "); //$NON-NLS-1$ + } + } + return false; + } + + @Override + public boolean visit(Javadoc node) { + printIndent(); + buffer.append("/** ");//$NON-NLS-1$ + for (Iterator it = node.tags().iterator(); it.hasNext();) { + ASTNode e = (ASTNode) it.next(); + e.accept(this); + } + buffer.append("\n */\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(LabeledStatement node) { + printIndent(); + node.getLabel().accept(this); + buffer.append(": ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(LambdaExpression node) { + boolean hasParentheses = node.hasParentheses(); + if (hasParentheses) { + buffer.append('('); + } + for (Iterator it = node.parameters().iterator(); it.hasNext();) { + VariableDeclaration v = (VariableDeclaration) it.next(); + v.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + if (hasParentheses) { + buffer.append(')'); + } + buffer.append(" -> "); //$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(LineComment node) { + buffer.append("//\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(MarkerAnnotation node) { + buffer.append("@");//$NON-NLS-1$ + node.getTypeName().accept(this); + return false; + } + + @Override + public boolean visit(MemberRef node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + } + buffer.append("#");//$NON-NLS-1$ + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(MemberValuePair node) { + node.getName().accept(this); + buffer.append("=");//$NON-NLS-1$ + node.getValue().accept(this); + return false; + } + + @Override + public boolean visit(MethodDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printIndent(); + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + if (!node.typeParameters().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeParameters().iterator(); it.hasNext();) { + TypeParameter t = (TypeParameter) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + if (!node.isConstructor()) { + if (node.getAST().apiLevel() == JLS2) { + getReturnType(node).accept(this); + } else { + if (node.getReturnType2() != null) { + node.getReturnType2().accept(this); + } else { + // methods really ought to have a return type + buffer.append("void");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + node.getName().accept(this); + buffer.append("(");//$NON-NLS-1$ + if (node.getAST().apiLevel() >= JLS8) { + Type receiverType = node.getReceiverType(); + if (receiverType != null) { + receiverType.accept(this); + buffer.append(' '); + SimpleName qualifier = node.getReceiverQualifier(); + if (qualifier != null) { + qualifier.accept(this); + buffer.append('.'); + } + buffer.append("this"); //$NON-NLS-1$ + if (node.parameters().size() > 0) { + buffer.append(','); + } + } + } + for (Iterator it = node.parameters().iterator(); it.hasNext();) { + SingleVariableDeclaration v = (SingleVariableDeclaration) it.next(); + v.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + int size = node.getExtraDimensions(); + if (node.getAST().apiLevel() >= JLS8) { + List dimensions = node.extraDimensions(); + for (int i = 0; i < size; i++) { + visit((Dimension) dimensions.get(i)); + } + } else { + for (int i = 0; i < size; i++) { + buffer.append("[]"); //$NON-NLS-1$ + } + } + if (node.getAST().apiLevel() < JLS8) { + if (!thrownExceptions(node).isEmpty()) { + buffer.append(" throws ");//$NON-NLS-1$ + for (Iterator it = thrownExceptions(node).iterator(); it.hasNext();) { + Name n = (Name) it.next(); + n.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + } else { + if (!node.thrownExceptionTypes().isEmpty()) { + buffer.append(" throws ");//$NON-NLS-1$ + for (Iterator it = node.thrownExceptionTypes().iterator(); it.hasNext();) { + Type n = (Type) it.next(); + n.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + } + if (node.getBody() == null) { + buffer.append(";\n");//$NON-NLS-1$ + } else { + node.getBody().accept(this); + } + return false; + } + + @Override + public boolean visit(MethodInvocation node) { + if (node.getExpression() != null) { + node.getExpression().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeArguments().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + node.getName().accept(this); + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(MethodRef node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + } + buffer.append("#");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.parameters().iterator(); it.hasNext();) { + MethodRefParameter e = (MethodRefParameter) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(MethodRefParameter node) { + node.getType().accept(this); + if (node.getAST().apiLevel() >= JLS3) { + if (node.isVarargs()) { + buffer.append("...");//$NON-NLS-1$ + } + } + if (node.getName() != null) { + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + } + return false; + } + + @Override + public boolean visit(Modifier node) { + buffer.append(node.getKeyword().toString()); + return false; + } + + @Override + public boolean visit(ModuleDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + printModifiers(node.annotations()); + if (node.isOpen()) { + buffer.append("open "); //$NON-NLS-1$ + } + buffer.append("module"); //$NON-NLS-1$ + buffer.append(" "); //$NON-NLS-1$ + node.getName().accept(this); + buffer.append(" {\n"); //$NON-NLS-1$ + indent++; + for (ModuleDirective stmt : (List<ModuleDirective>) node.moduleStatements()) { + stmt.accept(this); + } + indent--; + buffer.append("}"); //$NON-NLS-1$ + return false; + } + + @Override + /* + * @see ASTVisitor#visit(ModuleModifier) + * + * @since 3.14 + */ + public boolean visit(ModuleModifier node) { + buffer.append(node.getKeyword().toString()); + return false; + } + + private boolean visit(ModulePackageAccess node, String keyword) { + printIndent(); + buffer.append(keyword); + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + printTypes(node.modules(), "to"); //$NON-NLS-1$ + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(NameQualifiedType node) { + node.getQualifier().accept(this); + buffer.append('.'); + visitTypeAnnotations(node); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(NormalAnnotation node) { + buffer.append("@");//$NON-NLS-1$ + node.getTypeName().accept(this); + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.values().iterator(); it.hasNext();) { + MemberValuePair p = (MemberValuePair) it.next(); + p.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(NullLiteral node) { + buffer.append("null");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(NumberLiteral node) { + buffer.append(node.getToken()); + return false; + } + + @Override + public boolean visit(OpensDirective node) { + return visit(node, "opens"); //$NON-NLS-1$ + } + + @Override + public boolean visit(PackageDeclaration node) { + if (node.getAST().apiLevel() >= JLS3) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + for (Iterator it = node.annotations().iterator(); it.hasNext();) { + Annotation p = (Annotation) it.next(); + p.accept(this); + buffer.append(" ");//$NON-NLS-1$ + } + } + printIndent(); + buffer.append("package ");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ParameterizedType node) { + node.getType().accept(this); + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ParenthesizedExpression node) { + buffer.append("(");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(")");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(PostfixExpression node) { + node.getOperand().accept(this); + buffer.append(node.getOperator().toString()); + return false; + } + + @Override + public boolean visit(PrefixExpression node) { + buffer.append(node.getOperator().toString()); + node.getOperand().accept(this); + return false; + } + + @Override + public boolean visit(PrimitiveType node) { + visitTypeAnnotations(node); + buffer.append(node.getPrimitiveTypeCode().toString()); + return false; + } + + @Override + public boolean visit(ProvidesDirective node) { + printIndent(); + buffer.append("provides");//$NON-NLS-1$ + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + printTypes(node.implementations(), "with"); //$NON-NLS-1$ + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(QualifiedName node) { + node.getQualifier().accept(this); + buffer.append(".");//$NON-NLS-1$ + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(QualifiedType node) { + node.getQualifier().accept(this); + buffer.append(".");//$NON-NLS-1$ + visitTypeAnnotations(node); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(RequiresDirective node) { + printIndent(); + buffer.append("requires");//$NON-NLS-1$ + buffer.append(" ");//$NON-NLS-1$ + printModifiers(node.modifiers()); + node.getName().accept(this); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ReturnStatement node) { + printIndent(); + buffer.append("return");//$NON-NLS-1$ + if (node.getExpression() != null) { + buffer.append(" ");//$NON-NLS-1$ + node.getExpression().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(SimpleName node) { + IBinding binding = node.resolveBinding(); + if (!isLocalBinding(binding)) { + if (binding instanceof IVariableBinding) { + IVariableBinding vb = ((IVariableBinding) binding); + Object constant = vb.getConstantValue(); + if (constant != null) { + buffer.append(constant); + return false; + } + if (!vb.isField()) { + String newVarName = new String(LOCAL_VAR_PREFIX) + allocateNewVariable((IVariableBinding) binding); + binder.bind((IVariableBinding) binding, newVarName); + buffer.append("this."); //$NON-NLS-1$ + buffer.append(newVarName); + return false; + } + } + } + + buffer.append(node.getIdentifier()); + return false; + } + + @Override + public boolean visit(SimpleType node) { + visitTypeAnnotations(node); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(SingleMemberAnnotation node) { + buffer.append("@");//$NON-NLS-1$ + node.getTypeName().accept(this); + buffer.append("(");//$NON-NLS-1$ + node.getValue().accept(this); + buffer.append(")");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(SingleVariableDeclaration node) { + printIndent(); + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + node.getType().accept(this); + if (node.getAST().apiLevel() >= JLS3) { + if (node.isVarargs()) { + if (node.getAST().apiLevel() >= JLS8) { + List annotations = node.varargsAnnotations(); + if (annotations.size() > 0) { + buffer.append(' '); + } + visitAnnotationsList(annotations); + } + buffer.append("...");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + int size = node.getExtraDimensions(); + if (node.getAST().apiLevel() >= JLS8) { + List dimensions = node.extraDimensions(); + for (int i = 0; i < size; i++) { + visit((Dimension) dimensions.get(i)); + } + } else { + for (int i = 0; i < size; i++) { + buffer.append("[]"); //$NON-NLS-1$ + } + } + if (node.getInitializer() != null) { + buffer.append("=");//$NON-NLS-1$ + node.getInitializer().accept(this); + } + return false; + } + + @Override + public boolean visit(StringLiteral node) { + buffer.append(node.getEscapedValue()); + return false; + } + + @Override + public boolean visit(SuperConstructorInvocation node) { + printIndent(); + if (node.getExpression() != null) { + node.getExpression().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeArguments().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + buffer.append("super(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(");\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(SuperFieldAccess node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + buffer.append("super.");//$NON-NLS-1$ + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(SuperMethodInvocation node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + buffer.append("super.");//$NON-NLS-1$ + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeArguments().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeArguments().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + node.getName().accept(this); + buffer.append("(");//$NON-NLS-1$ + for (Iterator it = node.arguments().iterator(); it.hasNext();) { + Expression e = (Expression) it.next(); + e.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(")");//$NON-NLS-1$ + return false; + } + + /* + * @see ASTVisitor#visit(SuperMethodReference) + * + * @since 3.10 + */ + @Override + public boolean visit(SuperMethodReference node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + buffer.append('.'); + } + buffer.append("super");//$NON-NLS-1$ + visitReferenceTypeArguments(node.typeArguments()); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(SwitchCase node) { + if ((node.getAST().isPreviewEnabled())) { + if (node.isDefault()) { + buffer.append("default");//$NON-NLS-1$ + buffer.append(node.isSwitchLabeledRule() ? " ->" : ":");//$NON-NLS-1$ //$NON-NLS-2$ + } else { + buffer.append("case ");//$NON-NLS-1$ + for (Iterator it = node.expressions().iterator(); it.hasNext();) { + Expression t = (Expression) it.next(); + t.accept(this); + buffer.append(it.hasNext() ? ", " : //$NON-NLS-1$ + node.isSwitchLabeledRule() ? " ->" : ":");//$NON-NLS-1$ //$NON-NLS-2$ + } + } + } else { + if (node.isDefault()) { + buffer.append("default :\n");//$NON-NLS-1$ + } else { + buffer.append("case ");//$NON-NLS-1$ + getSwitchExpression(node).accept(this); + buffer.append(":\n");//$NON-NLS-1$ + } + } + indent++; // decremented in visit(SwitchStatement) + return false; + } + + /** + * @deprecated + */ + @Deprecated + private Expression getSwitchExpression(SwitchCase node) { + return node.getExpression(); + } + + private void visitSwitchNode(ASTNode node) { + buffer.append("switch (");//$NON-NLS-1$ + if (node instanceof SwitchExpression) { + ((SwitchExpression) node).getExpression().accept(this); + } else if (node instanceof SwitchStatement) { + ((SwitchStatement) node).getExpression().accept(this); + } + buffer.append(") ");//$NON-NLS-1$ + buffer.append("{\n");//$NON-NLS-1$ + indent++; + if (node instanceof SwitchExpression) { + for (Iterator it = ((SwitchExpression) node).statements().iterator(); it.hasNext();) { + Statement s = (Statement) it.next(); + s.accept(this); + indent--; // incremented in visit(SwitchCase) + } + } else if (node instanceof SwitchStatement) { + for (Iterator it = ((SwitchStatement) node).statements().iterator(); it.hasNext();) { + Statement s = (Statement) it.next(); + s.accept(this); + indent--; // incremented in visit(SwitchCase) + } + } + indent--; + printIndent(); + buffer.append("}\n");//$NON-NLS-1$ + + } + + @Override + public boolean visit(SwitchExpression node) { + visitSwitchNode(node); + return false; + } + + @Override + public boolean visit(SwitchStatement node) { + visitSwitchNode(node); + return false; + } + + @Override + public boolean visit(SynchronizedStatement node) { + buffer.append("synchronized (");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(") ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(TagElement node) { + return false; + } + + @Override + public boolean visit(TextBlock node) { + buffer.append(node.getEscapedValue()); + return false; + } + + @Override + public boolean visit(TextElement node) { + buffer.append(node.getText()); + return false; + } + + @Override + public boolean visit(ThisExpression node) { + if (node.getQualifier() != null) { + node.getQualifier().accept(this); + buffer.append(".");//$NON-NLS-1$ + } + buffer.append("this");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(ThrowStatement node) { + printIndent(); + buffer.append("throw ");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(TryStatement node) { + printIndent(); + buffer.append("try ");//$NON-NLS-1$ + if (node.getAST().apiLevel() >= JLS4) { + List resources = node.resources(); + if (!resources.isEmpty()) { + buffer.append('('); + for (Iterator it = resources.iterator(); it.hasNext();) { + Expression variable = (Expression) it.next(); + variable.accept(this); + if (it.hasNext()) { + buffer.append(';'); + } + } + buffer.append(')'); + } + } + node.getBody().accept(this); + buffer.append(" ");//$NON-NLS-1$ + for (Iterator it = node.catchClauses().iterator(); it.hasNext();) { + CatchClause cc = (CatchClause) it.next(); + cc.accept(this); + } + if (node.getFinally() != null) { + buffer.append(" finally ");//$NON-NLS-1$ + node.getFinally().accept(this); + } + return false; + } + + @Override + public boolean visit(TypeDeclaration node) { + if (node.getJavadoc() != null) { + node.getJavadoc().accept(this); + } + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + buffer.append(node.isInterface() ? "interface " : "class ");//$NON-NLS-2$//$NON-NLS-1$ + node.getName().accept(this); + if (node.getAST().apiLevel() >= JLS3) { + if (!node.typeParameters().isEmpty()) { + buffer.append("<");//$NON-NLS-1$ + for (Iterator it = node.typeParameters().iterator(); it.hasNext();) { + TypeParameter t = (TypeParameter) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(",");//$NON-NLS-1$ + } + } + buffer.append(">");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + if (node.getAST().apiLevel() == JLS2) { + if (getSuperclass(node) != null) { + buffer.append("extends ");//$NON-NLS-1$ + getSuperclass(node).accept(this); + buffer.append(" ");//$NON-NLS-1$ + } + if (!superInterfaces(node).isEmpty()) { + buffer.append(node.isInterface() ? "extends " : "implements ");//$NON-NLS-2$//$NON-NLS-1$ + for (Iterator it = superInterfaces(node).iterator(); it.hasNext();) { + Name n = (Name) it.next(); + n.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + } + if (node.getAST().apiLevel() >= JLS3) { + if (node.getSuperclassType() != null) { + buffer.append("extends ");//$NON-NLS-1$ + node.getSuperclassType().accept(this); + buffer.append(" ");//$NON-NLS-1$ + } + if (!node.superInterfaceTypes().isEmpty()) { + buffer.append(node.isInterface() ? "extends " : "implements ");//$NON-NLS-2$//$NON-NLS-1$ + for (Iterator it = node.superInterfaceTypes().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(" ");//$NON-NLS-1$ + } + } + buffer.append("{\n");//$NON-NLS-1$ + indent++; + for (Iterator it = node.bodyDeclarations().iterator(); it.hasNext();) { + BodyDeclaration d = (BodyDeclaration) it.next(); + d.accept(this); + } + indent--; + printIndent(); + buffer.append("}\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(TypeDeclarationStatement node) { + if (node.getAST().apiLevel() == JLS2) { + getTypeDeclaration(node).accept(this); + } + if (node.getAST().apiLevel() >= JLS3) { + node.getDeclaration().accept(this); + } + return false; + } + + @Override + public boolean visit(TypeLiteral node) { + node.getType().accept(this); + buffer.append(".class");//$NON-NLS-1$ + return false; + } + + /* + * @see ASTVisitor#visit(TypeMethodReference) + * + * @since 3.10 + */ + @Override + public boolean visit(TypeMethodReference node) { + node.getType().accept(this); + visitReferenceTypeArguments(node.typeArguments()); + node.getName().accept(this); + return false; + } + + @Override + public boolean visit(TypeParameter node) { + if (node.getAST().apiLevel() >= JLS8) { + printModifiers(node.modifiers()); + } + node.getName().accept(this); + if (!node.typeBounds().isEmpty()) { + buffer.append(" extends ");//$NON-NLS-1$ + for (Iterator it = node.typeBounds().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append(" & ");//$NON-NLS-1$ + } + } + } + return false; + } + + @Override + public boolean visit(UnionType node) { + for (Iterator it = node.types().iterator(); it.hasNext();) { + Type t = (Type) it.next(); + t.accept(this); + if (it.hasNext()) { + buffer.append('|'); + } + } + return false; + } + + @Override + public boolean visit(UsesDirective node) { + printIndent(); + buffer.append("uses");//$NON-NLS-1$ + buffer.append(" ");//$NON-NLS-1$ + node.getName().accept(this); + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(VariableDeclarationExpression node) { + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + node.getType().accept(this); + buffer.append(" ");//$NON-NLS-1$ + for (Iterator it = node.fragments().iterator(); it.hasNext();) { + VariableDeclarationFragment f = (VariableDeclarationFragment) it.next(); + f.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + return false; + } + + @Override + public boolean visit(VariableDeclarationFragment node) { + addLocalBinding(node.resolveBinding(), node.getName().getIdentifier()); + + buffer.append(node.getName().getIdentifier()); + int size = node.getExtraDimensions(); + if (node.getAST().apiLevel() >= JLS8) { + List dimensions = node.extraDimensions(); + for (int i = 0; i < size; i++) { + visit((Dimension) dimensions.get(i)); + } + } else { + for (int i = 0; i < size; i++) { + buffer.append("[]");//$NON-NLS-1$ + } + } + if (node.getInitializer() != null) { + buffer.append("=");//$NON-NLS-1$ + node.getInitializer().accept(this); + } + return false; + } + + @Override + public boolean visit(VariableDeclarationStatement node) { + printIndent(); + if (node.getAST().apiLevel() == JLS2) { + printModifiers(node.getModifiers()); + } + if (node.getAST().apiLevel() >= JLS3) { + printModifiers(node.modifiers()); + } + node.getType().accept(this); + buffer.append(" ");//$NON-NLS-1$ + for (Iterator it = node.fragments().iterator(); it.hasNext();) { + VariableDeclarationFragment f = (VariableDeclarationFragment) it.next(); + f.accept(this); + if (it.hasNext()) { + buffer.append(", ");//$NON-NLS-1$ + } + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + @Override + public boolean visit(WhileStatement node) { + printIndent(); + buffer.append("while (");//$NON-NLS-1$ + node.getExpression().accept(this); + buffer.append(") ");//$NON-NLS-1$ + node.getBody().accept(this); + return false; + } + + @Override + public boolean visit(WildcardType node) { + visitTypeAnnotations(node); + buffer.append("?");//$NON-NLS-1$ + Type bound = node.getBound(); + if (bound != null) { + if (node.isUpperBound()) { + buffer.append(" extends ");//$NON-NLS-1$ + } else { + buffer.append(" super ");//$NON-NLS-1$ + } + bound.accept(this); + } + return false; + } + + @Override + public boolean visit(YieldStatement node) { + if ((node.getAST().isPreviewEnabled()) && node.isImplicit() && node.getExpression() == null) { + return false; + } + printIndent(); + buffer.append("yield"); //$NON-NLS-1$ + if (node.getExpression() != null) { + buffer.append(" ");//$NON-NLS-1$ + node.getExpression().accept(this); + } + buffer.append(";\n");//$NON-NLS-1$ + return false; + } + + /** + * @deprecated + */ + @Deprecated + private void visitComponentType(ArrayType node) { + node.getComponentType().accept(this); + } + } + +} diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java index 8139dcb24..7a506532d 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTEvaluationEngine.java @@ -625,7 +625,7 @@ public class ASTEvaluationEngine implements IAstEvaluationEngine { } ASTInstructionCompiler visitor = new ASTInstructionCompiler( - mapper.getSnippetStart(), snippet); + mapper.getSnippetStart(), snippet, getJavaProject()); unit.accept(visitor); return visitor.getInstructions(); diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java index f22644053..4cd2d8952 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/engine/ASTInstructionCompiler.java @@ -24,10 +24,15 @@ import java.util.Stack; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.Flags; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.core.Signature; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.*; import org.eclipse.jdt.internal.debug.core.JDIDebugPlugin; +import org.eclipse.jdt.internal.debug.eval.ExpressionBinder; +import org.eclipse.jdt.internal.debug.eval.RemoteEvaluator; +import org.eclipse.jdt.internal.debug.eval.RemoteEvaluatorBuilder; import org.eclipse.jdt.internal.debug.eval.ast.instructions.AndAssignmentOperator; import org.eclipse.jdt.internal.debug.eval.ast.instructions.AndOperator; import org.eclipse.jdt.internal.debug.eval.ast.instructions.ArrayAllocation; @@ -87,6 +92,7 @@ import org.eclipse.jdt.internal.debug.eval.ast.instructions.PushThis; import org.eclipse.jdt.internal.debug.eval.ast.instructions.PushType; import org.eclipse.jdt.internal.debug.eval.ast.instructions.RemainderAssignmentOperator; import org.eclipse.jdt.internal.debug.eval.ast.instructions.RemainderOperator; +import org.eclipse.jdt.internal.debug.eval.ast.instructions.RemoteOperator; import org.eclipse.jdt.internal.debug.eval.ast.instructions.ReturnInstruction; import org.eclipse.jdt.internal.debug.eval.ast.instructions.RightShiftAssignmentOperator; import org.eclipse.jdt.internal.debug.eval.ast.instructions.RightShiftOperator; @@ -153,14 +159,17 @@ public class ASTInstructionCompiler extends ASTVisitor { // internal index used to create unique variable name private int fUniqueIdIndex = 0; + private IJavaProject fJavaProject; + /** * Create a new AST instruction compiler */ - public ASTInstructionCompiler(int startPosition, String snippet) { + public ASTInstructionCompiler(int startPosition, String snippet, IJavaProject javaProject) { fStartPosition = startPosition; fInstructions = new InstructionSequence(snippet); fStack = new Stack<>(); fCompleteInstructions = new ArrayList<>(); + fJavaProject = javaProject; } /** @@ -223,6 +232,7 @@ public class ASTInstructionCompiler extends ASTVisitor { ((CompoundInstruction) instruction).setEnd(fCounter); } fInstructions.add(instruction); + //System.out.println("Added: " + instruction.toString()); //$NON-NLS-1$ verbose("Add " + instruction.toString()); //$NON-NLS-1$ } } @@ -2151,8 +2161,15 @@ public class ASTInstructionCompiler extends ASTVisitor { if (!isActive()) { return true; } - setHasError(true); - addErrorMessage(EvaluationEngineMessages.ASTInstructionCompiler_Reference_expressions_cannot_be_used_in_an_evaluation_expression); + RemoteEvaluatorBuilder builder = makeBuilder(node); + builder.acceptMethodReference(node, node.resolveTypeBinding()); + try { + RemoteEvaluator remoteEvaluator = builder.build(); + push(new RemoteOperator(builder.getSnippet(), node.getStartPosition(), remoteEvaluator)); + storeInstruction(); + } catch (JavaModelException e) { + addErrorMessage(e.getMessage()); + } return false; } @@ -2351,11 +2368,56 @@ public class ASTInstructionCompiler extends ASTVisitor { if (!isActive()) { return true; } - setHasError(true); - addErrorMessage(EvaluationEngineMessages.ASTInstructionCompiler_Reference_expressions_cannot_be_used_in_an_evaluation_expression); + + RemoteEvaluatorBuilder builder = makeBuilder(node); + builder.acceptMethodReference(node, node.resolveTypeBinding()); + try { + RemoteEvaluator remoteEvaluator = builder.build(); + push(new RemoteOperator(builder.getSnippet(), node.getStartPosition(), remoteEvaluator)); + storeInstruction(); + } catch (JavaModelException e) { + addErrorMessage(e.getMessage()); + } + return false; } + private RemoteEvaluatorBuilder makeBuilder(ASTNode node) { + RemoteEvaluatorBuilder builder = new RemoteEvaluatorBuilder(fJavaProject, new ExpressionBinder() { + @Override + public void bind(IVariableBinding variableBinding, String asVariableName) { + String variableId = variableBinding.getName(); + ITypeBinding declaringTypeBinding = variableBinding.getDeclaringClass(); + if (variableBinding.isField()) { + if (Modifier.isStatic(variableBinding.getModifiers())) { + push(new PushStaticFieldVariable(variableId, getTypeName(declaringTypeBinding), fCounter)); + } else { + push(new PushFieldVariable(variableId, getTypeSignature(declaringTypeBinding), fCounter)); + push(new PushThis(getEnclosingLevel(node, declaringTypeBinding))); + storeInstruction(); + } + } else { + push(new PushLocalVariable(variableId)); + } + storeInstruction(); + } + }, getEnclosingClass(node).getQualifiedName(), false, false); + return builder; + } + + private ITypeBinding getEnclosingClass(ASTNode node) { + while (node != null) { + if (node instanceof MethodDeclaration) { + return ((MethodDeclaration) node).resolveBinding().getDeclaringClass(); + } + if (node instanceof TypeDeclaration) { + return ((TypeDeclaration) node).resolveBinding(); + } + node = node.getParent(); + } + return null; + } + /** * @see ASTVisitor#visit(ExpressionStatement) */ @@ -2862,10 +2924,19 @@ public class ASTInstructionCompiler extends ASTVisitor { if (!isActive()) { return true; } - setHasError(true); - addErrorMessage(EvaluationEngineMessages.ASTInstructionCompiler_Lambda_expressions_cannot_be_used_in_an_evaluation_expression); + + RemoteEvaluatorBuilder builder = makeBuilder(node); + builder.acceptLambda(node, node.resolveTypeBinding()); + try { + RemoteEvaluator remoteEvaluator = builder.build(); + push(new RemoteOperator(builder.getSnippet(), node.getStartPosition(), remoteEvaluator)); + storeInstruction(); + } catch (JavaModelException e) { + addErrorMessage(e.getMessage()); + } return false; } + /* * (non-Javadoc) * @@ -4248,8 +4319,17 @@ public class ASTInstructionCompiler extends ASTVisitor { if (!isActive()) { return true; } - setHasError(true); - addErrorMessage(EvaluationEngineMessages.ASTInstructionCompiler_Reference_expressions_cannot_be_used_in_an_evaluation_expression); + + RemoteEvaluatorBuilder builder = makeBuilder(node); + builder.acceptMethodReference(node, node.resolveTypeBinding()); + try { + RemoteEvaluator remoteEvaluator = builder.build(); + push(new RemoteOperator(builder.getSnippet(), node.getStartPosition(), remoteEvaluator)); + storeInstruction(); + } catch (JavaModelException e) { + addErrorMessage(e.getMessage()); + } + return false; } diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.java index b97de5f70..9d92c16a2 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.java +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.java @@ -29,6 +29,7 @@ public class InstructionsEvaluationMessages extends NLS { public static String AssignmentOperator_operator_1; public static String Cast_ClassCastException__Cannot_cast__0__as__1___1; public static String Cast_cast_3; + public static String Run_Remote_1; public static String ConditionalJump_conditional_jump_1; public static String Constructor_constructor__1; public static String LocalVariableCreation_create_local_variable__0___1___1; diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.properties b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.properties index ed26ba7e9..15f12608a 100644 --- a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.properties +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/InstructionsEvaluationMessages.properties @@ -24,6 +24,7 @@ Cast_ClassCastException__Cannot_cast__0__as__1___1=ClassCastException: Cannot ca Cast_cast_3=cast ConditionalJump_conditional_jump_1=conditional jump Constructor_constructor__1=constructor +Run_Remote_1=run remote code LocalVariableCreation_create_local_variable__0___1___1=Create local variable {1} {0} DivideAssignmentOperator_operator_1='/=\' operator DivideOperator_Divide_by_zero_1=Divide by zero diff --git a/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/RemoteOperator.java b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/RemoteOperator.java new file mode 100644 index 000000000..c47f06559 --- /dev/null +++ b/org.eclipse.jdt.debug/eval/org/eclipse/jdt/internal/debug/eval/ast/instructions/RemoteOperator.java @@ -0,0 +1,53 @@ +/******************************************************************************* + * Copyright (c) 2020, Jesper Steen Møller and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Jesper Steen Møller - initial API and implementation + *******************************************************************************/ +package org.eclipse.jdt.internal.debug.eval.ast.instructions; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.jdt.debug.core.IJavaValue; +import org.eclipse.jdt.internal.debug.eval.RemoteEvaluator; + +/** + * Invokes a method on a object in a class injected into the debug target. The arguments are on the stack in reverse order, followed by the type. + * Pushes the result onto the stack + */ +public class RemoteOperator extends CompoundInstruction { + + private RemoteEvaluator fEvaluator; + private String fSignature; + + public RemoteOperator(String body, int start, RemoteEvaluator evaluator) { + super(start); + fSignature = body; + fEvaluator = evaluator; + } + + @Override + public void execute() throws CoreException { + int variableCount = fEvaluator.getVariableCount(); + IJavaValue[] args = new IJavaValue[variableCount]; + // args are in reverse order + for (int i = variableCount - 1; i >= 0; i--) { + args[i] = popValue(); + } + IJavaValue result = fEvaluator.evaluate(this.getContext().getThread(), args); + push(result); + } + + @Override + public String toString() { + return InstructionsEvaluationMessages.Run_Remote_1 + + fSignature; + } + +} |