diff -r f5c6a7959124 api.debugger.jpda/apichanges.xml --- a/api.debugger.jpda/apichanges.xml Mon Sep 07 17:19:02 2009 +0200 +++ b/api.debugger.jpda/apichanges.xml Tue Sep 08 15:41:21 2009 +0200 @@ -728,6 +728,24 @@ + + + API for ability to plug-in evaluator engine. + + + + + +

+ An Evaluator interface is introduced, with + evaluate() method. It's implementation should be registered + for the desired language that compiles into bytecode. +

+
+ + +
+ diff -r f5c6a7959124 api.debugger.jpda/manifest.mf --- a/api.debugger.jpda/manifest.mf Mon Sep 07 17:19:02 2009 +0200 +++ b/api.debugger.jpda/manifest.mf Tue Sep 08 15:41:21 2009 +0200 @@ -1,6 +1,6 @@ Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.api.debugger.jpda/2 OpenIDE-Module-Localizing-Bundle: org/netbeans/api/debugger/jpda/Bundle.properties -OpenIDE-Module-Specification-Version: 2.20 +OpenIDE-Module-Specification-Version: 2.21 OpenIDE-Module-Package-Dependencies: com.sun.jdi[VirtualMachineManager] diff -r f5c6a7959124 api.debugger.jpda/src/org/netbeans/modules/debugger/jpda/apiregistry/DebuggerProcessor.java --- a/api.debugger.jpda/src/org/netbeans/modules/debugger/jpda/apiregistry/DebuggerProcessor.java Mon Sep 07 17:19:02 2009 +0200 +++ b/api.debugger.jpda/src/org/netbeans/modules/debugger/jpda/apiregistry/DebuggerProcessor.java Tue Sep 08 15:41:21 2009 +0200 @@ -41,6 +41,9 @@ package org.netbeans.modules.debugger.jpda.apiregistry; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; import java.util.Set; import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; @@ -51,17 +54,20 @@ import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import org.netbeans.api.debugger.jpda.JPDADebugger; +import org.netbeans.spi.debugger.ContextProvider; import org.netbeans.spi.debugger.jpda.EditorContext; +import org.netbeans.spi.debugger.jpda.Evaluator; import org.netbeans.spi.debugger.jpda.SmartSteppingCallback; import org.netbeans.spi.debugger.jpda.SourcePathProvider; import org.netbeans.spi.debugger.jpda.VariablesFilter; -import org.openide.filesystems.annotations.LayerBuilder.File; +import org.openide.filesystems.annotations.LayerBuilder; import org.openide.filesystems.annotations.LayerGeneratingProcessor; import org.openide.filesystems.annotations.LayerGenerationException; import org.openide.util.lookup.ServiceProvider; @@ -76,7 +82,8 @@ "org.netbeans.spi.debugger.jpda.SmartSteppingCallback.Registration", //NOI18N "org.netbeans.spi.debugger.jpda.SourcePathProvider.Registration", //NOI18N "org.netbeans.spi.debugger.jpda.EditorContext.Registration", //NOI18N - "org.netbeans.spi.debugger.jpda.VariablesFilter.Registration"}) //NOI18N + "org.netbeans.spi.debugger.jpda.VariablesFilter.Registration", //NOI18N + "org.netbeans.spi.debugger.jpda.Evaluator.Registration"}) //NOI18N public class DebuggerProcessor extends LayerGeneratingProcessor { public static final String SERVICE_NAME = "serviceName"; // NOI18N @@ -127,6 +134,13 @@ handleProviderRegistration(e, VariablesFilter.class, path); cnt++; } + for (Element e : env.getElementsAnnotatedWith(Evaluator.Registration.class)) { + Evaluator.Registration reg = e.getAnnotation(Evaluator.Registration.class); + + final String language = reg.language(); + handleEvaluatorRegistration(e, language); + cnt++; + } return cnt == annotations.size(); } @@ -140,12 +154,30 @@ } else { path = "Debugger"; } - layer(e).instanceFile(path, null, providerClass). - stringvalue(SERVICE_NAME, className). - stringvalue("serviceClass", providerClass.getName()). - methodvalue("instanceCreate", providerClass.getName()+"$ContextAware", "createService"). - //methodvalue("instanceCreate", "org.netbeans.modules.debugger.ui.registry."+providerClass.getSimpleName()+"ContextAware", "createService"). - write(); + LayerBuilder lb = layer(e); + String basename = className.replace('.', '-'); + LayerBuilder.File f = lb.file(path + "/" + basename + ".instance"); + f.stringvalue(SERVICE_NAME, className). + stringvalue("serviceClass", providerClass.getName()). + methodvalue("instanceCreate", providerClass.getName()+"$ContextAware", "createService"). + write(); + } + + private void handleEvaluatorRegistration(Element e, String language) throws IllegalArgumentException, LayerGenerationException { + String className = instantiableClassOrMethod(e); + if (!implementsInterface(e, Evaluator.class.getName())) { + throw new IllegalArgumentException("Annotated element "+e+" is not an instance of " + Evaluator.class); + } + String path = "Debugger/netbeans-JPDASession/"+language; // NOI18N + LayerBuilder lb = layer(e); + String basename = className.replace('.', '-'); + LayerBuilder.File f = lb.file(path + "/" + basename + ".instance"); + //LayerBuilder.File f = lb.instanceFile(path, null, Evaluator.class); + f.stringvalue(SERVICE_NAME, className). + stringvalue("serviceClass", Evaluator.class.getName()). + stringvalue("instanceOf", Evaluator.class.getName()). + methodvalue("instanceCreate", "org.netbeans.spi.debugger.ContextAwareSupport", "createService"). + write(); } private boolean isClassOf(Element e, Class providerClass) { @@ -184,6 +216,43 @@ } } + private boolean implementsInterface(Element e, String interfaceName) { + switch (e.getKind()) { + case CLASS: { + TypeElement te = (TypeElement) e; + List interfs = te.getInterfaces(); + for (TypeMirror tm : interfs) { + e = ((DeclaredType) tm).asElement(); + String clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) e).toString(); + if (interfaceName.equals(clazz)) { + return true; + } + } + break; + } + case METHOD: { + TypeMirror retType = ((ExecutableElement) e).getReturnType(); + if (retType.getKind().equals(TypeKind.NONE)) { + return false; + } else { + TypeElement te = (TypeElement) ((DeclaredType) retType).asElement(); + List interfs = te.getInterfaces(); + for (TypeMirror tm : interfs) { + e = ((DeclaredType) tm).asElement(); + String clazz = processingEnv.getElementUtils().getBinaryName((TypeElement) e).toString(); + if (interfaceName.equals(clazz)) { + return true; + } + } + } + break; + } + default: + throw new IllegalArgumentException("Annotated element is not loadable as an instance: " + e); + } + return false; + } + private String instantiableClassOrMethod(Element e) throws IllegalArgumentException, LayerGenerationException { switch (e.getKind()) { case CLASS: { @@ -193,14 +262,26 @@ } { boolean hasDefaultCtor = false; + boolean hasContextCtor = false; for (ExecutableElement constructor : ElementFilter.constructorsIn(e.getEnclosedElements())) { - if (constructor.getParameters().isEmpty()) { + List params = constructor.getParameters(); + if (params.isEmpty()) { hasDefaultCtor = true; break; } + if (params.size() == 1) { + String type = params.get(0).asType().toString(); + //System.err.println("Param type = "+type); + //TypeElement te = processingEnv.getElementUtils().getTypeElement(params.get(0).asType().toString()); + //if (te != null && isClassOf(te, ContextProvider.class)) { + if (ContextProvider.class.getName().equals(type)) { + hasContextCtor = true; + break; + } + } } - if (!hasDefaultCtor) { - throw new LayerGenerationException(clazz + " must have a no-argument constructor", e); + if (!(hasDefaultCtor || hasContextCtor)) { + throw new LayerGenerationException(clazz + " must have a no-argument constructor or constuctor taking "+ContextProvider.class.getName()+" as a parameter.", e); } } /*propType = processingEnv.getElementUtils().getTypeElement("java.util.Properties").asType(); diff -r f5c6a7959124 api.debugger.jpda/src/org/netbeans/spi/debugger/jpda/Evaluator.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/api.debugger.jpda/src/org/netbeans/spi/debugger/jpda/Evaluator.java Tue Sep 08 15:41:21 2009 +0200 @@ -0,0 +1,294 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2009 Sun Microsystems, Inc. + */ + +package org.netbeans.spi.debugger.jpda; + +import com.sun.jdi.ClassType; +import com.sun.jdi.ObjectReference; +import com.sun.jdi.StackFrame; +import com.sun.jdi.Value; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.netbeans.api.debugger.jpda.CallStackFrame; +import org.netbeans.api.debugger.jpda.InvalidExpressionException; +import org.netbeans.api.debugger.jpda.ObjectVariable; +import org.netbeans.api.debugger.jpda.Variable; +import org.netbeans.spi.debugger.ContextProvider; +import org.openide.util.Lookup; + +/** + * Evaluator service for a language that compiles into bytecode. + * Implementation class should register using the annotation {@link Registration} for the desired language. + * + * @author Martin Entlicher + * + * @since 2.21 + */ +public interface Evaluator { + + /** + * Evaluates given expression and provide the result. + * + * @param expression the expression to be evaluated + * @param context the context in which the expression is evaluated + * @return value of evaluated expression + * @throws InvalidExpressionException when the expression is invalid or other + * error occurs during the evaluation process. + */ + Result evaluate(Expression expression, Context context) throws InvalidExpressionException; + + /** + * Representation of an expression that is a subject of evaluation. + * String expressions are evaluated. But this class allows to attach + * a custom preprocessed structure to the expression, + * which can be used on subsequent evaluations of the same expression. + * Clients can, but does not have to, set a pre-processed object and + * during repeated evaluations of the same expression use that + * pre-processed object to speed up the evaluation. The preprocessed + * object should not be {@link Context}-sensitive. + */ + public static final class Expression { + + private String expression; + private PreprocessedInfo preprocessed; + + /** + * Creates a new expression from the string representation. + * @param expression The string expression. + */ + public Expression(String expression) { + this.expression = expression; + } + + /** + * Get the string representation of this expression. + * @return string expression + */ + public String getExpression() { + return expression; + } + + /** + * Set a pre-processed object of the string expression. + * A custom, client-specific object is expected, which represents + * the expression. This can be a syntax tree structure or whatever + * that speeds up repeated evaluations of the same expression. + * + * @param preprocessed object holding the information about pre-processed + * expression + */ + public void setPreprocessedObject(PreprocessedInfo preprocessed) { + this.preprocessed = preprocessed; + } + + /** + * Get the pre-processed object of the string expression. + * The object set by {@link #setPreprocessedObject(java.lang.Object)} is + * returned. + * + * @return the preprocessed object or null. + */ + public PreprocessedInfo getPreprocessedObject() { + return preprocessed; + } + } + + /** + * Context of the evaluation. + * This class provides the evaluation context - stack frame and context variable. + * Two sets of APIs can be used during the evaluation: + *
    + *
  • JPDA Debugger API (which is a safe abstraction of JDI, + * but with limited functionality)
    + * Provided {@link CallStackFrame} and {@link ObjectVariable} + * can be used to compute the resulting {@link Variable}. + *
  • + *
  • JDI API (providing full access, but can throw unexpected exceptions + * and clients must notify the context about method invocations)
    + * Provided {@link StackFrame} and {@link ObjectReference} can be used + * to compute the resulting {@link Value}. When a method invocation + * is necessary, {@link Context#notifyMethodToBeInvoked()} must be called + * before the method invocation. + *
  • + *
+ */ + public static final class Context { + + private CallStackFrame callStackFrame; + private ObjectVariable contextVariable; + private StackFrame stackFrame; + private int stackDepth; + private ObjectReference contextObject; + private Runnable methodToBeInvokedNotifier; + + /** Creates the context, do not call directly */ + public Context(Lookup context) { + this.callStackFrame = context.lookup(CallStackFrame.class); + this.contextVariable = context.lookup(ObjectVariable.class); + this.stackFrame = context.lookup(StackFrame.class); + this.stackDepth = context.lookup(Integer.class); + this.contextObject = context.lookup(ObjectReference.class); + this.methodToBeInvokedNotifier = context.lookup(Runnable.class); + } + + /** + * Get the context call stack frame. + * This frame corresonds to the JDI frame returned from {@link #getStackFrame()}. + * @return call stack frame in which the evaluation is performed + */ + public CallStackFrame getCallStackFrame() { + return callStackFrame; + } + + /** + * Get an optional context variable. When non-null, + * all methods and fields should be treated relative to the variable + * instance. + * This variable corresonds to the JDI reference returned from {@link #getContextObject()}. + * @return optional context variable or null. + */ + public ObjectVariable getContextVariable() { + return contextVariable; + } + + /** + * Get the context stack frame in JDI APIs. + * This frame corresonds to the JPDA frame returned from {@link #getCallStackFrame()}. + * @return stack frame in which the evaluation is performed + */ + public StackFrame getStackFrame() { + return stackFrame; + } + + /** + * Get the depth of stack frame returned from {@link #getStackFrame()}. + * @return the depth of stack frame + */ + public int getStackDepth() { + return stackDepth; + } + + /** + * Get an optional context object. When non-null, + * all methods and fields should be treated relative to the object + * instance. + * This object corresonds to the JPDA variable returned from {@link #getContextVariable()}. + * @return optional context object or null. + */ + public ObjectReference getContextObject() { + return contextObject; + } + + /** + * This method is required to be called before a call to JDI + * that cause the current thread (sf.thread()) to resume - e.g. + * {@link ObjectReference#invokeMethod(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)}, + * {@link ClassType#invokeMethod(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)}, + * {@link ClassType#newInstance(com.sun.jdi.ThreadReference, com.sun.jdi.Method, java.util.List, int)}. + */ + public void notifyMethodToBeInvoked() { + methodToBeInvokedNotifier.run(); + } + } + + /** + * Evaluation result. + * Depending on the APIs used by the evaluation, result is either + * a {@link Variable} or {@link Value}. + */ + public static final class Result { + + private Variable var; + private Value v; + + /** + * Create result from {@link Variable}. + * @param var result variable + */ + public Result(Variable var) { + this.var = var; + } + + /** + * Create result from {@link Value}. + * @param v result value + */ + public Result(Value v) { + this.v = v; + } + + /** + * Get the result variable + * @return the variable or null. + */ + public Variable getVariable() { + return var; + } + + /** + * Get the result value + * @return the value or null. + */ + public Value getValue() { + return v; + } + } + + /** + * Declarative registration of Evaluator implementation. + * By marking the implementation class with this annotation, + * you automatically register that implementation for use by debugger. + * The class must be public and have a public constructor which takes + * no arguments or takes {@link ContextProvider} as an argument. + * + * @author Martin Entlicher + */ + @Retention(RetentionPolicy.SOURCE) + @Target({ElementType.TYPE}) + public @interface Registration { + /** + * The language to register this evaluator for. + */ + String language(); + + } + +}