/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * 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.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle 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<PreprocessedInfo> {

    /**
     * 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<PreprocessedInfo> 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<PreprocessedInfo> {

        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 <code>null</code>.
         */
        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:
     * <ul>
     *    <li>JPDA Debugger API (which is a safe abstraction of JDI,
     *                           but with limited functionality)<br>
     *        Provided {@link CallStackFrame} and {@link ObjectVariable}
     *        can be used to compute the resulting {@link Variable}.
     *    </li>
     *    <li>JDI API (providing full access, but can throw unexpected exceptions
     *                 and clients must notify the context about method invocations)<br>
     *        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.
     *    </li>
     * </ul>
     */
    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 <code>null</code>.
         */
        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 <code>null</code>.
         */
        public ObjectReference getContextObject() {
            return contextObject;
        }

        /**
         * This method is <b>required</b> to be called before a call to JDI
         * that cause the current thread (<code>sf.thread()</code>) 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 <code>null</code>.
         */
        public Variable getVariable() {
            return var;
        }

        /**
         * Get the result value
         * @return the value or <code>null</code>.
         */
        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.
     */
    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE})
    public @interface Registration {
        /**
         * The language to register this evaluator for.
         */
        String language();

    }

}
