RuntimeJastInterpreter.java
/*
** Module : RuntimeJastInterpreter.java
** Abstract : Interpreter for a JAST tree that contains a compiling unit.
**
** Copyright (c) 2015-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM 20150123 Created initial version.
** 002 OM 20150210 Added support for anonymous WhereExpression and LogicalExpression.
** Added support for calling methods in a super class. Refactoring method names.
** 003 OM 20150306 The strings literals are generated as ready to be written to java sources and
** they had to be escaped to obtain their real value.
** 004 OM 20150318 Added Date and IntegerExpression implementation.
** 005 ECF 20150401 Removed Aast node instance member from InterpreterException class and made
** this inner class static to allow it to be serialized, in case it needs to be
** sent to a client over the DAP.
** 006 OM 20150610 Added support for calling methods with parameters.
** Added support for accessing array variables and parameters.
** 007 OM 20150617 Added evaluation of expressions having JavaTokenTypes.LPARENS as root.
** 008 OM 20150731 Added support for interpreting JavaTokenTypes.MEMBER.
** 009 OM 20150904 Added support for interpreting (P2JQuery.Parameter) JavaTokenTypes.LAMBDA
** expressions.
** 010 GES 20150923 Javadoc fix.
** 011 OM 20160201 Added support for interpreting remaining datatypes Expressions.
** 012 GES 20160505 Removed javaEscape() and replaced usage with StringHelper.processEscapes().
** 013 OM 20160526 Improved support for interpreted LAMBDA nodes (added LogicalOp interfaces).
** 014 GES 20160818 Replaced WhereExpression usage with lambda support for client where clause
** support. Fixed varargs matching if the method call is annotated suitably.
** 20160824 Reworked to handle delegated query substitution parameters that are general
** purpose lambdas (as a subclass of Resolvable).
** 015 IAS 20160930 Fix for a regression caused by H014, a constructor can be the referent for a
** method call.
** 016 ECF 20171227 Performance improvement and format cleanup.
** 017 OM 20181207 Added interpretIfExist() method.
** 018 OM 20190321 Specifically evaluate dynamic function calls on query open.
** Throw ABL exceptions instead of InterpreterException.
** 019 ECF 20190829 Added optional logging of interpreter exceptions.
** 020 ECF 20190728 Log a failure to resolve a query substitution parameter.
** CA 20190812 Changes to allow for mutable buffers; any API which receives a Buffer instance
** and is invoked from converted code must resolve the runtime instance before
** saving the instance.
** 021 OM 20200109 Cached the evaluation of DYNAMIC-FUNCTIONS in here (there is one RJI for
** each dynamic query) so that the JAST trees can be cached and reused.
** OM 20200123 Mild optimizations and code upgrade.
** 022 VVT 20200203 Invalid assertion removed.
** 023 OM 20200725 Fixed method resolution.
** 024 CA 20200924 Replaced Method.invoke with ReflectASM.
** OM 20210219 Added support for persist.***Expr types to getClass().
** CA 20210310 A dynamic predicate must set the default lock to NONE, instead of SHARE.
** ECF 20210504 Check for all ControlFlowOps dynamic function method names, not just the basic one.
** ECF 20210915 Allow recursion in callMethod.
** AL2 20220412 Do proxy checks for BDT when evaluating.
** CA 20221006 Added JMX instrumentation for interprate. Refs #6814
** TJD 20220504 Upgrade do Java 11 minor changes
** 025 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 026 IAS 20230921 Added support for the P2JQuery.ParamResolver.
** 027 HC 20240222 Enabled JMX on FWD Client.
** 028 AL2 20230222 Made constructor look-up more relaxed. It fallbacks to the first constructor, not null.
** 029 CA 20240324 Performance improvement for runtime annotations: are stored using an integer instead of
** string.
** 030 TT 20240411 Modified the logging when a second constructor is found in findMatchingConstructor().
** 031 CA 20240809 Skip using JMX timers when JMX_DEBUG flag is not set.
** 032 AL2 20240925 Fixed NPE when calling static method. If the static class was cached, but the method was
** not, the method was no longer looked-up. Now, the method is looked-up properly.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.persist;
import com.goldencode.ast.*;
import com.goldencode.expr.*;
import com.goldencode.util.*;
import com.goldencode.p2j.jmx.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.ErrorManager;
import com.goldencode.p2j.util.logging.CentralLogger;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.Consumer;
import java.util.function.Supplier; // importing all conflicts with Function class
import java.util.logging.*;
/**
* This class interprets a JAST tree. It is designed to be as generic as possible.
*
* It will use the annotations from a memory stored JAST if they are found. Otherwise it will
* use reflection to select the appropriate method/constructor based on the actual parameters.
* In the second case, the newly discovered piece of information will be cached back in the tree
* for a fast evaluation in successive executions.
*
* The interpreter is week-typed, it only distinct two types of data: table buffers and variable.
* variables can store any kind of data, the scalars are stored in their respective wrappers,
* access being done using automatic boxing/unboxing. The datatypes are evaluated at runtime based
* on the actual variable value.
*
* Known issues. At this moment:
* <ul>
* <li>only sequential methods can be processed. Future versions will add support for loops
* and conditionals;
* <li>there is no [this] notion. In consequence other methods cannot be called. Not even the
* static ones;
* <li>no support for arrays;
* <li>no support for constructors. Objects of this class cannot be built/returned. All
* methods are called as they were static.
* <li>the root of the JAST is a COMPILE-UNIT that can hold only one (public) class;
* <li>others TBA.
* </ul>
*
* Usage. There are 4 steps when working with the interpreter.
* <ol>
* <li>Initialization. When an object is constructed, two optional types of data can be
* optionally supplied: a list of buffers and a set of pre-initialized variables that will
* be used in the process of evaluation.
* <li>Preparation. The JAST is supplied. The first traversal will collect paths, class
* variables and method names.
* <li>Interpretation. A method can be called. At this moment it cannot have any parameters.
* As workaround, they can be provided as pre-initialized variables in Initialization.
* This step can be repeated with different methods. If they are functions, the evaluated
* values is provided in the returned value.
* <li>Get the variable value. Extract the value of a variable. This can be combined with
* [Interpretation], and can be called multiple times. If the method invoked is a function,
* this step is optional, a value is obtained as teh result of the [Interpretation].
* </ol>
*/
public class RuntimeJastInterpreter
{
/** Instrumentation for {@link #interpret}. */
private static final NanoTimer QUERY_INTERPRET = NanoTimer.getInstance(FwdServerJMX.TimeStat.OrmDynQueryInterpret);
/**
* This is a marker for not evaluated nodes. It is used to avoid conflicts with possible
* {@code null} valid values already evaluated.
*/
private final Object NOT_EVALUATED = new Object();
/** Logger. */
private static final CentralLogger LOG = CentralLogger.get(RuntimeJastInterpreter.class.getName());
/** Cache of classes for faster lookup than {@code Class.forName} alone */
private static final Map<String, Class<?>> classCache = new ConcurrentHashMap<>();
/** The list of known buffers, by their name. */
private Map<String, Buffer> buffers = null;
/** The list of the variables, by their name. */
private Map<String, Object> variables = null;
/**
* The stack of parameters from calling methods with actual values.
* <p>
* <b>NOTE:</b>This is not a perfect emulation of a java scopes.
*/
private ScopedDictionary<String, Object> methodArgs = new ScopedDictionary<>();
/** The list of import paths. Used for class and method dynamic resolution. */
private String[] imports =
{
"java.lang.", // always present
"com.goldencode.p2j.util.", // usually needed
};
/** Identifies a DYNAMIC-FUNCTION call. */
private final String DYNAMIC_CALL = "ControlFlowOps.invokeDynamicFunction";
/**
* The set of all DYNAMIC-FUNCTION calls from this compile unit. If {@code null} then it
* contains no DYNAMIC-FUNCTION calls. These nodes will be evaluated only once, when the query
* is open and the result is cached mapped to respective node. These values are valid for this
* interpreter, ie for an individual dynamic query.
*/
private Map<Aast, Object> dynamicCalls = null;
/** The list of statically imported classes. Used for method dynamic resolution. */
private Class<?>[] staticImports =
{
java.lang.Math.class, // always present
};
/** The list of the method defined in this class. */
private Map<String, Aast> classMethods = new HashMap<>();
/** A consumer to process newly created instances. */
private Consumer<Object> instanceProcessor = null;
/**
* The default constructor. Initiate the list of buffers and variables to empty sets.
*/
public RuntimeJastInterpreter()
{
this.buffers = new HashMap<>();
this.variables = new HashMap<>();
}
/**
* A constructor that receives a set of buffers and pre-initialized variables.
*
* @param buffers
* The list of buffers to be used. Must be open and ready to use.
* @param variables
* The list of variables (values mapped by their variable names).
*/
public RuntimeJastInterpreter(List<Buffer> buffers, Map<String, Object> variables)
{
setBuffers(buffers);
setVariables(variables);
}
/**
* Get the class with the given name. The class cache is checked first, then the class is
* retrieved from the current classloader, if not found in the cache.
*
* @param className
* Fully qualified class name.
*
* @return Class associated with {@code className}, if found.
*
* @throws ClassNotFoundException
* if a class with the given name is not found.
*/
private static Class<?> classForName(String className)
throws ClassNotFoundException
{
Class<?> cls = classCache.get(className);
if (cls == null)
{
cls = Class.forName(className);
classCache.putIfAbsent(className, cls);
}
return cls;
}
/**
* Updates the set of known buffers. The old list is lost.
*
* @param buffers
* The new list of buffers to be used. Must be open and ready to use.
*/
public void setBuffers(List<Buffer> buffers)
{
// map buffers for quick access by their alias
this.buffers = new HashMap<>();
if (buffers != null)
{
for (Buffer buf : buffers)
{
RecordBuffer rbuf = ((BufferImpl) buf).buffer();
this.buffers.put(rbuf.getDMOAlias(), ((BufferImpl) buf).ref());
}
}
}
/**
* Updates the set of known variables. The old list is lost.
*
* @param variables
* The new list of variables (values mapped by their variable names).
*/
public void setVariables(Map<String, Object> variables)
{
this.variables = new HashMap<>();
if (variables != null)
{
this.variables.putAll(variables);
}
}
/**
* The preparation step. The node provided must be a COMPILE-UNIT. The method does a fast first
* tree traversal (max two level deep) and will collect paths, class variables and method names
* that will be used in future processing.
*
* @param jast
* The JAST tree to be processed. It must be a COMPILE-UNIT node.
* @param dynamicEvaluation
* Use {@code true} when there is at least on DYNAMIC-FUNCTION in interpreted
* expression.
*
* @throws RuntimeJastInterpreter.InterpreterException
* if the argument is null or not a COMPILE-UNIT node.
*/
public void prepare(JavaAst jast, boolean dynamicEvaluation)
{
if (jast == null || jast.getType() != JavaTokenTypes.COMPILE_UNIT)
{
throw new InterpreterException("The argument for prepare must be a COMPILE-UNIT jast.", jast, null);
}
compileUnit(jast);
if (dynamicEvaluation)
{
dynamicCalls = new HashMap<>();
// scan for dynamic calls
Iterator<Aast> it = jast.iterator();
while (it.hasNext())
{
Aast next = it.next();
String text = next.getText();
if (text != null && text.startsWith(DYNAMIC_CALL))
{
dynamicCalls.put(next, NOT_EVALUATED);
}
}
if (dynamicCalls.isEmpty())
{
LOG.warning("Failed to detect the DYNAMIC-FUNCTION calls.");
}
}
}
/**
* Obtain the value of a variable. This method can be called for each needed variable, at any
* given moment.
*
* @param varName
* the name of the variable whose value will be returned.
*
* @return the value of requested variable.
*
* @throws RuntimeJastInterpreter.InterpreterException
* if the argument is null or not a variable name.
*/
public Object getVariableValue(String varName)
{
if (variables.containsKey(varName))
{
return variables.get(varName);
}
throw new InterpreterException("No variable named '" + varName + "' was defined", null, null);
}
/**
* Executes a method. If the method returns a value (function) it it will be obtained as the
* result.
* Only simple methods, without parameters are supported. As workaround, the parameters can be
* added to the list of initial pre-initialized variables.
* <p>
* This method does not throw any exception if no method if sound to match the requested name
* and signature as {@link #interpret} does. In this case this method returns {@code null}.
*
* @param methodName
* The name of the method to be called.
* @param args
* The list of actual values for the parameters.
*
* @return if the method defines a RETURN statement, that value is returned, otherwise null.
*/
public Object interpretIfExist(String methodName, Object... args)
{
Aast aMethod = classMethods.get(methodName);
if (aMethod != null)
{
return execMethod(aMethod, args);
}
return null;
}
/**
* Executes a method. If the method returns a value (function) it it will be obtained as the
* result.
* Only simple methods, without parameters are supported. As workaround, the parameters can be
* added to the list of initial pre-initialized variables.
*
* @param methodName
* The name of the method to be called.
* @param args
* The list of actual values for the parameters.
*
* @return if the method defines a RETURN statement, that value is returned, otherwise null.
*
* @throws RuntimeJastInterpreter.InterpreterException
* if the argument is null or not a method name or other exception occurred during
* the evaluation.
*/
public Object interpret(String methodName, Object... args)
{
Aast aMethod = classMethods.get(methodName);
if (aMethod != null)
{
if (FwdServerJMX.JMX_DEBUG)
{
Object[] res = new Object[1];
QUERY_INTERPRET.timer(() -> res[0] = execMethod(aMethod, args));
return res[0];
}
else
{
return execMethod(aMethod, args);
}
}
throw new InterpreterException("No such method defined", null, null);
}
public void evaluateDynamicCalls()
{
if (dynamicCalls == null)
{
return;
}
try
{
Set<Aast> nodeSet = new HashSet<>(dynamicCalls.keySet());
for (Aast dynamicCall : nodeSet)
{
if (dynamicCalls.get(dynamicCall) == NOT_EVALUATED)
{
// replace the NOT_EVALUATED value with the result of the method evaluation
dynamicCalls.put(dynamicCall, callStaticMethod(dynamicCall));
}
}
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
ErrorManager.recordOrThrowError(223, err);
}
}
/**
* Set a function which will process any newly created instances.
*
* @param expr
* The function.
*/
void processInstancesWith(Consumer<Object> expr)
{
this.instanceProcessor = expr;
}
/**
* Prepares the JAST for use. The method does a fast first tree traversal (max two level deep)
* and will collect paths, class variables and method names that will be used in future
* processing.
*
* @param cu
* The main node of a tree. It must be a COMPILE-UNIT node.
*/
private void compileUnit(Aast cu)
{
// iterate to 'learn' how to resolve classes and (static) method calls
collectImportPaths(cu);
collectStaticImports(cu);
Aast mainClass = cu.getImmediateChild(JavaTokenTypes.KW_CLASS, null);
if (mainClass == null)
{
throw new InterpreterException("Main class could not be determined", cu, null);
}
// store instance vars if any (the query should be listed here, normally)
Aast classVars = mainClass.getImmediateChild(JavaTokenTypes.CS_INSTANCE_VARS, null);
if (classVars != null)
{
declareVars(classVars);
}
// collect methods (all)
Aast allMethods = mainClass.getImmediateChild(JavaTokenTypes.CS_INSTANCE_METHODS, null);
if (allMethods == null)
{
throw new InterpreterException("No methods are defined in the class", cu, null);
}
Aast aMethod = allMethods.getImmediateChild(JavaTokenTypes.METHOD_DEF, null);
while (aMethod != null)
{
classMethods.put(aMethod.getText(), aMethod);
aMethod = (Aast) aMethod.getNextSibling();
}
}
/**
* Collects the import paths used for symbol resolution. Iterates KW_IMPORT nodes.
* Previous collection of paths is dropped. The default "java.lang.*" package is automatically
* inserted.
*
* @param cu
* The main node of a tree. It must be a COMPILE-UNIT node.
*/
private void collectImportPaths(Aast cu)
{
Aast importPath = cu.getImmediateChild(JavaTokenTypes.KW_IMPORT, null);
imports = new String[cu.getNumImmediateChildren(JavaTokenTypes.KW_IMPORT) + 1];
int k = 0;
imports[k++] = "java.lang.";
while (importPath != null)
{
String packageName = importPath.getText();
imports[k++] = packageName.substring(0, packageName.length() - 1);
importPath = cu.getImmediateChild(JavaTokenTypes.KW_IMPORT, importPath);
}
}
/**
* Collects the static import paths used for symbol resolution. Iterates STATIC_IMPORT nodes.
* Previous collection of classes is dropped. The default "java.lang.Math" package is
* automatically inserted at the end.
*
* @param cu
* The main node of a tree. It must be a COMPILE-UNIT node.
*/
private void collectStaticImports(Aast cu)
{
Aast staticImport = cu.getImmediateChild(JavaTokenTypes.STATIC_IMPORT, null);
staticImports = new Class[cu.getNumImmediateChildren(JavaTokenTypes.STATIC_IMPORT) + 1];
int k = 0;
while (staticImport != null)
{
String className = staticImport.getText();
className = className.substring(0, className.length() - 2);
try
{
staticImports[k++] = classForName(className);
}
catch (ClassNotFoundException e)
{
throw new InterpreterException("Failed to load static import", staticImport, e);
}
staticImport = cu.getImmediateChild(JavaTokenTypes.STATIC_IMPORT, staticImport);
}
staticImports[k] = Math.class; // add as a last resort
}
/**
* Collects the variables defined in this class. Table buffers are ignored.
* Iterates the children of CS_INSTANCE_VARS nodes, adding them to the list of internal
* variables. If the case the initialization is performed.
*
* @param classVars
* The main node of a tree. It must be a CS_INSTANCE_VARS node.
*/
private void declareVars(Aast classVars)
{
Aast varDef = (Aast) classVars.getFirstChild();
while (varDef != null)
{
// add it to our class variables set
Aast varRef = (Aast) varDef.getFirstChild();
if (varRef != null)
{
if (!varRef.getText().endsWith(".Buf"))
{
String varName = (String) varRef.getAnnotation("name");
variables.put(varName, null);
doAssign(varName, (Aast) varRef.getNextSibling());
}
// skip buffers, they are already collected
}
else
{
// simple declaration, without assignment
String varName = (String) varDef.getAnnotation("name");
variables.put(varName, null);
}
varDef = (Aast) varDef.getNextSibling();
}
}
/**
* Executes a method from this class by interpreting the sequence of statements from its body.
* <p>
* Each statement from this method definition is executed/evaluated in sequence. If the method
* returns a value (function) it it will be obtained as the result.
* <p>
* Only simple methods, without parameters are supported. As workaround, the parameters can be
* added to the list of initial pre-initialized variables.
*
* @param aMethod
* The method node to be processed.
* @param params
* The arguments for this method.
*
* @return if the method defines a RETURN statement, that value is returned, otherwise null.
*/
private Object execMethod(Aast aMethod, Object... params)
{
assert (aMethod.getType() == JavaTokenTypes.METHOD_DEF);
Aast methBlock = aMethod.getImmediateChild(JavaTokenTypes.BLOCK, null);
assert (methBlock != null);
Aast parList = aMethod.getImmediateChild(JavaTokenTypes.LPARENS, null);
try
{
// optimization: add a new scope only if the method has parameters
if (parList != null)
{
// push the parameters to the scoped dictionary:
methodArgs.addScope(aMethod.getText());
// iterate all parameters and map them to their value in the methodArgs dictionary
Aast param = (Aast) parList.getFirstChild();
int pIndex = 0;
while (param != null)
{
if (params == null || pIndex >= params.length)
{
throw new InterpreterException(
"Invalid parameter list in method call", aMethod, null);
}
methodArgs.addEntry(false, (String) param.getAnnotation("name"), params[pIndex++]);
param = (Aast) param.getNextSibling();
}
}
// iterate all statements from the method block
Aast statement = (Aast) methBlock.getFirstChild();
while (statement != null)
{
switch (statement.getType())
{
case JavaTokenTypes.KW_RETURN:
if (statement.getNumImmediateChildren() == 0)
{
return null;
}
else
{
// this a function definition; returning the result
Object res = evalExpression((Aast) statement.getFirstChild());
return res;
}
case JavaTokenTypes.METHOD_CALL:
callMethod(statement);
break;
case JavaTokenTypes.STATIC_METHOD_CALL:
callStaticMethod(statement);
break;
case JavaTokenTypes.ASSIGN:
Aast varRef = (Aast) statement.getFirstChild();
doAssign(varRef.getText(), (Aast) varRef.getNextSibling());
break;
default:
throw new InterpreterException("Unknown statement type", statement, null);
}
statement = (Aast) statement.getNextSibling();
}
}
finally
{
if (parList != null)
{
// remove the last scope from the parameter dictionary if the method had parameters
methodArgs.deleteScope();
}
}
return null;
}
/**
* Performs a variable assignment. The variable must be already defined.
*
* @param varName
* The name of the variable.
* @param assignNode
* The node that contain the tree for evaluation of the variable value.
*
* @return in order to support chaining, the evaluated value of the variable is returned.
*/
private Object doAssign(String varName, Aast assignNode)
{
if (!variables.containsKey(varName))
{
throw new InterpreterException(
"No variable named '" + varName + "' was defined", assignNode, null);
}
Object val = evalExpression(assignNode);
variables.put(varName, val);
return val;
}
/**
* Call a constructor and returns the built object.
* <p>
* The method will try to use the cached annotation from the node to speed up evaluation.
* If not available, it will use reflection to resolve it. Once computed, the Constructor
* object will be stored for future calls.
* <p>
* Before calling the constructor, all children nodes are (recursively) evaluated.
*
* @param ctorNode
* A CONSTRUCTOR node used to build an object.
*
* @return the newly constructed object is returned. Not null.
*/
private Object callCtor(Aast ctorNode)
{
assert (ctorNode.getType() == JavaTokenTypes.CONSTRUCTOR);
Constructor<?> ctor = null;
Class<?> queryClass = null;
Object[] parValues = null;
// try to use the runtime annotation to speed up the interpreter
Object cachedCtor = getCache(ctorNode, Aast.RUNTIME_CONSTRUCTOR);
if (cachedCtor instanceof Constructor)
{
ctor = (Constructor<?>) cachedCtor;
}
else
{
// try to use the cached class name, at least
Object cachedClass = getCache(ctorNode, Aast.RUNTIME_CLASS);
if (cachedClass instanceof Class)
{
queryClass = (Class<?>) cachedClass;
}
else
{
// TODO: check if the classname contains the package to load it directly
for (String path : imports)
{
// load class, prepare constructor
String ctorClass = path + ctorNode.getText();
try
{
queryClass = classForName(ctorClass);
break; // found it
}
catch (ClassNotFoundException e)
{
// not the right package this class, try next package
}
}
if (queryClass == null)
{
throw new InterpreterException(
"Failed to load the class for the constructor", ctorNode, null);
}
// cache the class:
ctorNode.setRuntimeAnnotation(Aast.RUNTIME_CLASS, queryClass);
}
int parCount = ctorNode.getNumImmediateChildren();
Class<?>[] parTypes = new Class[parCount];
parValues = collectParameters(ctorNode, parTypes, 0);
// look for the constructor
try
{
ctor = queryClass.getDeclaredConstructor(parTypes);
}
catch (NoSuchMethodException e)
{
// attempt to do it manually (because of null values most likely)
ctor = findMatchingConstructor(queryClass, parTypes);
if (ctor == null)
{
throw new InterpreterException(
"Failed to lookup the constructor with required signature", ctorNode, null);
}
}
// cache the c'tor:
ctorNode.setRuntimeAnnotation(Aast.RUNTIME_CONSTRUCTOR, ctor);
}
if (parValues == null)
{
// collect parameters now if not done already
parValues = collectParameters(ctorNode, null, 0);
}
Object res = null;
// invoke the constructor
try
{
res = ctor.newInstance(parValues);
if (instanceProcessor != null)
{
instanceProcessor.accept(res);
}
}
catch (ReflectiveOperationException e)
{
throw new InterpreterException(
"Error invoking constructor: " + ctor.toString(), ctorNode, e);
}
return res;
}
/**
* Collects the list of parameters to a method/staticMethod/constructor and, optionally, the
* signature in a pre-allocated table.
*
* @param methNode
* The method node whose children will be inspected. It can be any type of node,
* but the operation only makes sense for METHOD_CALL, STATIC_METHOD_CALL and
* CONSTRUCTOR nodes.
* @param signature
* Optional. If not null, at return, it will be populated with the method signature.
* If provided (not null), it must be allocated to match the signature size.
* @param startAt
* The number of children to be ignored. In the case of standard METHOD_CALLs, the
* first child is the object on which the method will be applied so it must be
* skipped (startAt = 1). Otherwise should be 0.
*
* @return an array with the values of the arguments for this method.
*/
private Object[] collectParameters(Aast methNode, Class<?>[] signature, int startAt)
{
int parCount = methNode.getNumImmediateChildren() - startAt;
Object[] parValues = new Object[parCount];
assert (signature == null || signature.length == parCount);
// iterate constructor parameters:
for (int k = 0; k < parCount; k++)
{
parValues[k] = evalExpression(methNode.getChildAt(k + startAt));
if (signature != null)
{
signature[k] = (parValues[k] == null) ? null : parValues[k].getClass();
}
}
return parValues;
}
/**
* Call a normal member method. If the method returns a value (function) it it will be
* obtained as the result.
* <p>
* The method will try to use the cached annotation from the node to speed up evaluation.
* If not available, it will use reflection to resolve it. Once computed, the Method
* object will be stored for future calls.
* <p>
* Before calling the method, all children nodes are (recursively) evaluated.
*
* @param aMethod
* The method node to be evaluated.
*
* @return the result of calling the method on fist child, possible null
*/
private Object callMethod(Aast aMethod)
{
assert (aMethod.getType() == JavaTokenTypes.METHOD_CALL);
Method method = null;
Class<?> aClass = null;
Object[] parValues = null;
Object obj = null;
int minargs = readMinArgs(aMethod);
// 1st: find the object on which we invoke the method
Aast ref = (Aast) aMethod.getFirstChild();
if (ref.getType() == JavaTokenTypes.LPARENS)
{
ref = (Aast) ref.getFirstChild();
}
switch (ref.getType())
{
case JavaTokenTypes.CONSTRUCTOR:
obj = callCtor(ref);
break;
case JavaTokenTypes.METHOD_CALL:
obj = callMethod(ref); // recursive call
break;
case JavaTokenTypes.REFERENCE:
// basic invocation: this should be a variable
String refName = ref.getText();
if (variables.containsKey(refName))
{
obj = variables.get(refName);
break;
}
else if (buffers.containsKey(refName))
{
obj = buffers.get(refName);
// NOTE: in dynamic queries, : and :: return some kind of value that is neither
// null, neither not null. The EQ operator always returns FALSE and NE always
// returns TRUE, even if compared with ? (unknown value).
break;
}
else
{
throw new InterpreterException("Failed to execute method on reference", ref, null);
}
default:
// TODO: chaining not implemented
throw new InterpreterException("Unknown type of object to execute a method", ref, null);
}
aClass = obj.getClass();
aMethod.setRuntimeAnnotation(Aast.RUNTIME_CLASS, aClass);
// 2nd: find the method to be called
Object cachedMethod = getCache(aMethod, Aast.RUNTIME_METHOD);
if (cachedMethod instanceof Method)
{
method = (Method) cachedMethod;
}
else
{
String methodName = aMethod.getText();
int parCount = aMethod.getNumImmediateChildren() - 1;
Class<?>[] parTypes = new Class[parCount];
parValues = collectParameters(aMethod, parTypes, 1); // skip the 1st child
Class<?> itClass = aClass;
// attempt to use simple reflection for all super classes
while (itClass != null)
{
try
{
method = itClass.getDeclaredMethod(methodName, parTypes);
// if we found the method, exit the while loop
break;
}
catch (NoSuchMethodException e)
{
// do nothing, expected exception if method not defined here
}
itClass = itClass.getSuperclass();
}
if (method == null)
{
// attempt to do it manually (because of null values most likely)
method = findMatchingMethod(aClass, methodName, parTypes, minargs);
}
if (method != null)
{
aMethod.setRuntimeAnnotation(Aast.RUNTIME_METHOD, method);
}
else
{
throw new InterpreterException(
"Failed to detect the method to be called", aMethod, null);
}
}
if (parValues == null)
{
// collect parameters now if not done already
parValues = collectParameters(aMethod, null, 1);
}
// 3rd: do the dew: invoke the method on the object
Object res = null;
try
{
parValues = fixupParameters(method, false, parValues);
res = Utils.invoke(method, obj, parValues);
}
catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e)
{
throw new InterpreterException("Failed to execute method", aMethod, e);
}
return res;
}
/**
* Check the given node for a "minargs" annotation and return it if present.
*
* @param node
* The node to check.
*
* @return The value of the annotation or -1 if the annotation is not present.
*/
private int readMinArgs(Aast node)
{
int minargs = -1;
// support varargs if minargs is set
if (node.isAnnotation("minargs"))
{
minargs = ((Long) node.getAnnotation("minargs")).intValue();
}
return minargs;
}
/**
* For varargs methods, rework the last parameters into an array of the proper type.
*
* @param method
* The method to be considered.
* @param isStatic
* <code>true</code> if this is a static method, <code>false</code> for an instance
* method.
* @param parValues
* The parameters as passed by the converted code.
*
* @return The original paramter list if no changes are needed. For varargs calls where
* fixups are needed, a rewritten array will be returned (and the original
* array should not be used in the subsequent invocation of the method).
*/
private Object[] fixupParameters(Method method, boolean isStatic, Object[] parValues)
{
Object[] parms = parValues;
// we only do something if the method uses varargs
if (method.isVarArgs())
{
int num = method.getParameterCount();
int last = parValues.length - 1;
int from = num - 1; // may be the same as last, or smaller
boolean big = (parValues.length > num);
Class<?>[] types = method.getParameterTypes();
// there are 2 ways to detect if fixups are needed:
// - given argument list is a different length than required
// - the type of the last given parameter does not match the array type required
if (big || !(parValues[last].getClass().equals(types[from])))
{
// allocate the parameter array for the expected parameter list (may be smaller than
// the original)
parms = Arrays.copyOf(parValues, num);
// the last element must be converted to an array of the proper type (will hold the
// varargs)
parms[from] = Array.newInstance(types[from].getComponentType(),
parValues.length - from);
// copy the elements from the original array into the varargs array
for (int i = from; i < parValues.length; i++)
{
int n = i - from;
((Object[])(parms[from]))[n] = parValues[i];
}
}
if (LOG.isLoggable(Level.FINER))
{
LOG.finer(String.format("Processing method %s with %d formal parms.", method.getName(), num));
for (Class<?> cls : types)
{
LOG.finer(String.format(" %s (expected)", cls.toString()));
}
for (Object obj : parValues)
{
LOG.finer(String.format(" '%s' of type %s (given)", obj.toString(), obj.getClass().toString()));
if (obj.getClass().isArray())
{
Object[] vals = (Object[])obj;
for (Object val : vals)
{
LOG.finer(String.format(" element '%s' of type %s", val.toString(), val.getClass().toString()));
}
}
}
for (Object obj : parms)
{
LOG.finer(String.format(" '%s' of type %s (fixed up)", obj.toString(), obj.getClass().toString()));
if (obj.getClass().isArray())
{
Object[] vals = (Object[])obj;
for (Object val : vals)
{
LOG.finer(String.format(" element '%s' of type %s", val.toString(), val.getClass().toString()));
}
}
}
}
}
return parms;
}
/**
* Call a static method. If the method returns a value (function) it it will be
* obtained as the result.
* <p>
* The method will try to use the cached annotation from the node to speed up evaluation.
* If not available, it will use reflection to resolve it. Once computed, the Method
* object will be stored for future calls.
* <p>
* Before calling the method, all children nodes are (recursively) evaluated.
*
* @param staticMethod
* The method node to be evaluated.
*
* @return the result of calling the static method, possible null
*/
private Object callStaticMethod(Aast staticMethod)
{
assert (staticMethod.getType() == JavaTokenTypes.STATIC_METHOD_CALL);
Method method = null;
Class<?> staticClass = null;
Object[] parValues = null;
int minargs = readMinArgs(staticMethod);
String methodName = staticMethod.getText();
// the dynamic calls do not cache the "runtime-value", instead the dynamicCalls map is used
boolean dynamicCall = (dynamicCalls != null) && methodName.startsWith(DYNAMIC_CALL);
if (dynamicCall)
{
Object cached = dynamicCalls.get(staticMethod);
// was it already evaluated?
if (cached != NOT_EVALUATED)
{
return cached;
}
// else evaluate it normally and cache the result in dynamicCalls map
}
// try to use the runtime annotation to speed up the interpreter
Object cachedMethod = getCache(staticMethod, Aast.RUNTIME_METHOD);
// check if method is already cached.
if (cachedMethod instanceof Method)
{
method = (Method) cachedMethod;
}
else
{
// identify the static class for this static method
// try to use the cached class name, at least
Object cachedClass = getCache(staticMethod, Aast.RUNTIME_CLASS);
if (cachedClass instanceof Class)
{
// cache hit
staticClass = (Class<?>) cachedClass;
}
else
{
// cache missed: try to infer static class from the quantified method name
// TODO: check if the classname contains the package to load it directly
int k = methodName.indexOf('.');
if (k != -1)
{
// <Class>.<methodName>: extract the class name and find the right package
String className = methodName.substring(0, k);
methodName = methodName.substring(k + 1);
for (String path : imports)
{
try
{
staticClass = classForName(path + className);
break;
}
catch (ClassNotFoundException e)
{
// don't worry, try next import path
}
}
if (staticClass == null)
{
throw new InterpreterException(
"Failed to find class for static method", staticMethod, null);
}
// cache the class object
staticMethod.setRuntimeAnnotation(Aast.RUNTIME_CLASS, staticClass);
}
}
int parCount = staticMethod.getNumImmediateChildren();
Class<?>[] parTypes = new Class[parCount];
parValues = collectParameters(staticMethod, parTypes, 0);
if (staticClass != null)
{
// method is actually quantified with a static class; do the resolution NOW!
try
{
method = staticClass.getDeclaredMethod(methodName, parTypes);
}
catch (NoSuchMethodException e)
{
// attempt to do it manually (because of null values most likely)
method = findMatchingMethod(staticClass, methodName, parTypes, minargs);
if (method == null)
{
throw new InterpreterException(
"Failed to find static method from class", staticMethod, e);
}
}
}
// still no method found; maybe it wasn't quantified with a static class
// check the static imports then
if (method == null)
{
// we do not know the classname, we will use the static import list
for (Class<?> aClass : staticImports)
{
try
{
method = aClass.getDeclaredMethod(methodName, parTypes);
}
catch (NoSuchMethodException e)
{
// attempt to do it manually (because of [null] values, most likely)
method = findMatchingMethod(aClass, methodName, parTypes, minargs);
}
if (method != null)
{
// yes, we found it!
staticClass = aClass;
staticMethod.setRuntimeAnnotation(Aast.RUNTIME_CLASS, staticClass);
break;
}
// otherwise, no problemo, try the next class
}
}
// too bad; no method was found
if (method == null)
{
throw new InterpreterException(
"Failed to find static method from class", staticMethod, null);
}
// cache the method name for later use
staticMethod.setRuntimeAnnotation(Aast.RUNTIME_METHOD, method);
}
if (parValues == null)
{
// collect parameters now if not done already
parValues = collectParameters(staticMethod, null, 0);
}
Object res = null;
// invoke the method
try
{
parValues = fixupParameters(method, true, parValues);
res = Utils.invoke(method, null, parValues);
}
catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e)
{
throw new InterpreterException("Failed to execute static method", staticMethod, e);
}
if (dynamicCall)
{
dynamicCalls.put(staticMethod, res);
}
return res;
}
/**
* Evaluates an expression to a value that is returned.
* If the node evaluates to a constant, it is cashed for fast access.
* If it is a reference to a buffer, the list of buffers is checked. Similar for variables.
*
* @param exprNode
* The JAST node that contains the expression.
*
* @return the value of the expression.
*/
private Object evalExpression(Aast exprNode)
{
if (exprNode.isRuntimeAnnotation(Aast.RUNTIME_VALUE))
{
// this node has been already processed and decided the result is constant
Object res = getCache(exprNode, Aast.RUNTIME_VALUE);
return res;
}
String nodeText = exprNode.getText();
Object res = null;
switch (exprNode.getType())
{
case JavaTokenTypes.CONSTRUCTOR:
res = callCtor(exprNode);
break;
case JavaTokenTypes.METHOD_CALL:
res = callMethod(exprNode); // keep the result (function case)
break;
case JavaTokenTypes.LPARENS:
res = evalExpression((Aast) exprNode.getFirstChild());
break;
case JavaTokenTypes.PLACEHOLDER:
res = evalExpression((Aast) exprNode.getFirstChild());
break;
case JavaTokenTypes.CAST:
res = evalExpression((Aast) exprNode.getFirstChild());
if (!exprNode.isRuntimeAnnotation(Aast.RUNTIME_CLASS))
{
// if not already set
exprNode.setRuntimeAnnotation(Aast.RUNTIME_CLASS, getClass(nodeText));
}
break;
case JavaTokenTypes.STRING:
res = StringHelper.processEscapes(nodeText);
// avoid processing java escapes the next time
exprNode.setRuntimeAnnotation(Aast.RUNTIME_VALUE, res);
break;
case JavaTokenTypes.EXPRESSION:
res = evalExpression((Aast) exprNode.getFirstChild());
break;
case JavaTokenTypes.MEMBER:
Class<?> queryClass = null;
Aast of = (Aast) exprNode.getFirstChild();
for (String path : imports)
{
// load class | TODO: (extract common code for class by name from imports)
String ctorClass = path + of.getText();
try
{
queryClass = classForName(ctorClass);
break; // found it
}
catch (ClassNotFoundException e)
{
// not the right package this class, try next package
}
}
if (queryClass == null)
{
throw new InterpreterException(
"Failed to load the class for the member evaluation", exprNode, null);
}
try
{
Field member = queryClass.getDeclaredField(exprNode.getText());
res = member.get(null); // MEMEBER are always static ?
}
catch (NoSuchFieldException | IllegalAccessException e)
{
throw new InterpreterException(
"Failed to find member in parent class", exprNode, e);
}
break;
case JavaTokenTypes.REFERENCE_DEF:
if ("new Object".equals(nodeText))
{
Aast initializer = (Aast) exprNode.getNextSibling();
int childrenCount = initializer.getNumImmediateChildren();
Object[] objectArray = new Object[childrenCount];
for (int k = 0; k < childrenCount; k++)
{
objectArray[k] = evalExpression(initializer.getChildAt(k));
}
res = objectArray;
break;
}
throw new InterpreterException("Unknown REFERENCE_DEF type", exprNode, null);
case JavaTokenTypes.REFERENCE:
// first check if this is a buffer
if (buffers.containsKey(nodeText))
{
res = buffers.get(nodeText);
break;
}
// prepare for array identifiers
String identName = null;
int arrayIndex = -1;
int lsb = nodeText.indexOf('[');
int rsb = nodeText.indexOf(']');
if (lsb == -1 && rsb == -1)
{
identName = nodeText;
}
else if (lsb != -1 && rsb != -1 && lsb < rsb)
{
identName = nodeText.substring(0, lsb);
arrayIndex = Integer.parseInt(nodeText.substring(lsb + 1, rsb));
}
if (identName != null)
{
boolean checkArray = false;
// or perhaps a method parameter name ?
if (methodArgs.locate(identName) != -1)
{
res = methodArgs.lookup(identName);
checkArray = true;
}
// or perhaps a variable name ?
else if (variables.containsKey(identName))
{
res = variables.get(identName);
checkArray = true;
}
if (checkArray) // check if only an item from array is requested
{
if (arrayIndex >= 0 && res instanceof Object[])
{
Object[] ufo = (Object[]) res;
res = ufo[arrayIndex];
}
break;
}
}
throw new InterpreterException(
"No REFERENCE named (" + nodeText + ")", exprNode, null);
case JavaTokenTypes.NUM_LITERAL:
if (nodeText.endsWith("L"))
{
nodeText = nodeText.substring(0, nodeText.length() - 1);
res = Long.parseLong(nodeText);
}
else
{
res = Integer.parseInt(nodeText);
}
exprNode.setRuntimeAnnotation(Aast.RUNTIME_VALUE, res); // save constant to cache
break;
case JavaTokenTypes.BOOL_FALSE:
res = Boolean.FALSE;
exprNode.setRuntimeAnnotation(Aast.RUNTIME_VALUE, Boolean.FALSE); // save const to cache
break;
case JavaTokenTypes.BOOL_TRUE:
res = Boolean.TRUE;
exprNode.setRuntimeAnnotation(Aast.RUNTIME_VALUE, Boolean.TRUE); // save const to cache
break;
case JavaTokenTypes.NULL_LITERAL:
// a null parameter. It will make it difficult to select the correct method/c'tor
res = null;
exprNode.setRuntimeAnnotation(Aast.RUNTIME_VALUE, null); // save const to cache
break;
case JavaTokenTypes.STATIC_METHOD_CALL:
res = callStaticMethod(exprNode);
break;
case JavaTokenTypes.TERN_IF_ELSE:
Aast cond = (Aast) exprNode.getFirstChild();
Object bool = evalExpression(cond);
if (bool instanceof Boolean)
{
Aast first = (Aast) cond.getNextSibling();
if ((Boolean) bool)
{
res = evalExpression(first);
}
else
{
res = evalExpression((Aast) first.getNextSibling());
}
break;
}
else
{
throw new InterpreterException(
"Not a logical expression type in ternary IF ", exprNode, null);
}
// TODO: Can this be removed? If it is only ever used for WHERE clauses, then this
// is probably dead code. However, these kinds of constructors can be generated
// for complex BY clauses in conversion. Are they in play here?
case JavaTokenTypes.ANON_CTOR:
// we handle only the well known cases:
switch (exprNode.getText())
{
case "CharacterExpression":
res = buildTypedExpression(exprNode, "character");
break;
case "DateExpression":
res = buildTypedExpression(exprNode, "date");
break;
case "DatetimeExpression":
res = buildTypedExpression(exprNode, "datetime");
break;
case "DatetimeTzExpression":
res = buildTypedExpression(exprNode, "datetimetz");
break;
case "DecimalExpression":
res = buildTypedExpression(exprNode, "decimal");
break;
case "Int64Expression":
res = buildTypedExpression(exprNode, "int64");
break;
case "IntegerExpression":
res = buildTypedExpression(exprNode, "integer");
break;
case "LogicalExpression":
res = buildTypedExpression(exprNode, "logical");
break;
case "RawExpression":
res = buildTypedExpression(exprNode, "raw");
break;
case "RecidExpression":
res = buildTypedExpression(exprNode, "recid");
break;
case "RowidExpression":
res = buildTypedExpression(exprNode, "rowid");
break;
default:
throw new InterpreterException("Unknown anonymous constructor", exprNode, null);
}
break;
case JavaTokenTypes.LAMBDA:
if (exprNode.isAnnotation("client_where") &&
(Boolean) exprNode.getAnnotation("client_where"))
{
res = new ClientWhere(this, exprNode);
break;
}
Aast parentJast = exprNode.getParent();
if (parentJast.getType() == JavaTokenTypes.CAST)
{
String cast = parentJast.getText();
switch (cast)
{
case "P2JQuery.Parameter":
res = new P2JQueryParameter(this, exprNode);
break;
case "CharacterExpr":
res = new CharacterExprAdapter(this, exprNode);
break;
case "DateExpr":
res = new DateExprAdapter(this, exprNode);
break;
case "DatetimeExpr":
res = new DatetimeExprAdapter(this, exprNode);
break;
case "DatetimeTzExpr":
res = new DatetimeTzExprAdapter(this, exprNode);
break;
case "DecimalExpr":
res = new DecimalExprAdapter(this, exprNode);
break;
case "Int64Expr":
res = new Int64ExprAdapter(this, exprNode);
break;
case "IntegerExpr":
res = new IntegerExprAdapter(this, exprNode);
break;
case "LogicalExpr":
res = new LogicalExprAdapter(this, exprNode);
break;
case "RawExpr":
res = new RawExprAdapter(this, exprNode);
break;
case "RecidExpr":
res = new RecidExprAdapter(this, exprNode);
break;
case "RowidExpr":
res = new RowidExprAdapter(this, exprNode);
break;
case "P2JQuery.ParamResolver":
res = new P2JQueryParamResolver(this, exprNode);
break;
default:
String msg = "Unknown CAST expression type %s ";
throw new InterpreterException(String.format(msg, cast), exprNode, null);
}
break;
// TODO: collect other CAST-ed lambda types
}
// default to LogicalLambda class
res = new LogicalLambda(this, exprNode);
break;
default:
throw new InterpreterException("Unknown expression type", exprNode, null);
}
if (res instanceof BaseDataType && BaseDataType.isProxy((BaseDataType) res))
{
res = ((BaseDataType) res).val();
}
return res;
}
/**
* Build an anonymous object that extends some kind of typed expression:
* {@link IntegerExpression}, {@link LogicalExpression}, {@link DateExpression} etc.
*
* @param typedExpr
* The JAST node that describe the new object to be built.
* @param retType
* The return type JAST of the {@code execute} method of the expected class type.
* Must not be {@code null} or empty.
*
* @return the object built
*/
private Resolvable buildTypedExpression(Aast typedExpr, String retType)
{
assert (typedExpr.getType() == JavaTokenTypes.ANON_CTOR);
if (LOG.isLoggable(Level.FINER))
{
LOG.finer(typedExpr.dumpTree(true));
}
Aast allMethods = typedExpr.getImmediateChild(JavaTokenTypes.CS_INSTANCE_METHODS, null);
assert (allMethods != null && allMethods.getNumImmediateChildren() != 0);
JavaAst executeMethod = null;
Aast meth = allMethods.getImmediateChild(JavaTokenTypes.METHOD_DEF, null);
while (meth != null)
{
if ("execute".equals(meth.getText()) &&
retType.equals(meth.getAnnotation("rettype")))
{
// TODO: the full signature is not checked, no other overloaded method should exist
executeMethod = (JavaAst) meth;
}
meth = allMethods.getImmediateChild(JavaTokenTypes.METHOD_DEF, meth);
}
switch (retType)
{
case "logical":
return new LogicalExpressionAdapter(this, executeMethod);
case "integer":
return new IntegerExpressionAdapter(this, executeMethod);
case "int64":
return new Int64ExpressionAdapter(this, executeMethod);
case "date":
return new DateExpressionAdapter(this, executeMethod);
case "datetime":
return new DatetimeExpressionAdapter(this, executeMethod);
case "datetimetz":
return new DatetimeTzExpressionAdapter(this, executeMethod);
case "character":
return new CharacterExpressionAdapter(this, executeMethod);
case "decimal":
return new DecimalExpressionAdapter(this, executeMethod);
case "rowid":
return new RowidExpressionAdapter(this, executeMethod);
// case "handle":
// return new HandleExpressionAdapter(this, executeMethod);
case "raw":
return new RawExpressionAdapter(this, executeMethod);
default:
throw new InterpreterException("Unknown anonymous constructor", typedExpr, null);
}
}
/**
* Obtain the class by its name.
* <p>
* At this moment this is only used by CAST nodes.
* <p>
* For performance issues, the classes are hardcoded in a String switch. Using the reflection
* and iterating through all all packages imported uses a great amount of CPU cycles. Other
* classes will be possible added in the future as they occur in the client code.
*
* @param nodeText
* The class name.
*
* @return The requested Class object.
*/
private Class<?> getClass(String nodeText)
{
// this is a fast out
switch (nodeText)
{
// java.lang:
case "String":
return java.lang.String.class;
// BaseDataType
case "logical":
return com.goldencode.p2j.util.logical.class;
case "integer":
return com.goldencode.p2j.util.integer.class;
case "int64":
return com.goldencode.p2j.util.int64.class;
case "date":
return com.goldencode.p2j.util.date.class;
case "datetime":
return com.goldencode.p2j.util.datetime.class;
case "datetimetz":
return com.goldencode.p2j.util.datetimetz.class;
case "character":
return com.goldencode.p2j.util.character.class;
case "decimal":
return com.goldencode.p2j.util.decimal.class;
case "raw":
return com.goldencode.p2j.util.raw.class;
case "recid":
return com.goldencode.p2j.util.recid.class;
case "rowid":
return com.goldencode.p2j.util.rowid.class;
// Resolvable types:
case "LogicalExpr":
return com.goldencode.p2j.persist.LogicalExpr.class;
case "IntegerExpr":
return com.goldencode.p2j.persist.IntegerExpr.class;
case "Int64Expr":
return com.goldencode.p2j.persist.Int64Expr.class;
case "DateExpr":
return com.goldencode.p2j.persist.DateExpr.class;
case "DatetimeExpr":
return com.goldencode.p2j.persist.DatetimeExpr.class;
case "DatetimeTzExpr":
return com.goldencode.p2j.persist.DatetimeTzExpr.class;
case "CharacterExpr":
return com.goldencode.p2j.persist.CharacterExpr.class;
case "DecimalExpr":
return com.goldencode.p2j.persist.DecimalExpr.class;
case "RawExpr":
return com.goldencode.p2j.persist.RawExpr.class;
case "RecidExpr":
return com.goldencode.p2j.persist.RecidExpr.class;
case "RowidExpr":
return com.goldencode.p2j.persist.RowidExpr.class;
// other P2J specific types
case "P2JQuery.Parameter":
return com.goldencode.p2j.persist.P2JQuery.Parameter.class;
case "P2JQuery.ParamResolver":
return com.goldencode.p2j.persist.P2JQuery.ParamResolver.class;
default:
throw new InterpreterException("Unknown class type: " + nodeText, null, null);
}
}
/**
* Look into the list of constructors for a class for the one that matches the list of
* argument types. Only return a valid object if there is a clean choice. In case of
* collisions (multiple matching) null is returned as error.
*
* @param aClass
* The class whose constructor list is checked.
* @param signature
* An array with the java types of the arguments of the required constructor. Use null
* as a wildcard for unknown or null parameter type.
*
* @return the matching constructor. If none is found, null is returned. null is also returned
* if multiple constructors are found to match the signature
*/
private static Constructor<?> findMatchingConstructor(Class<?> aClass, Class<?>[] signature)
{
Constructor<?> ret = null;
for (Constructor<?> ctor : aClass.getDeclaredConstructors())
{
Class<?>[] required = ctor.getParameterTypes();
if (Function.matchSignature(required, signature, -1, true))
{
if (ret != null)
{
if (LOG.isLoggable(Level.WARNING))
{
LOG.log(Level.WARNING,
"Two matching constructors were found for class " +
aClass.getName() +
" in run-time JAST interpreter");
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, "Location: ", new Throwable());
}
}
break;
}
// do not return yet, check for sanity
ret = ctor;
}
}
return ret;
}
/**
* Look into the list of methods of a class for the one that matches name and the list of
* argument types. Only return a valid object if there is a clean choice. In case of
* collisions (multiple matching) null is returned as error.
* The process is rather costly. the worse case scenario involves a second pass to re-analyze
* the list overloaded methods if the first one (in strict mode) provided no solution.
* <p>
* This method calls {@link #findMatchingMethodFromClass}, for each super class until a method
* with requested name is found, or reaching the top of the hierarchy, in which case
* {@code null} is returned.
*
* @param aClass
* The class whose constructor list is checked.
* @param methodName
* The method name being searched for.
* @param signature
* An array with the java types of the arguments of the required constructor. Use null
* as a wildcard for unknown or null parameter type.
* @param minargs
* The minimum number of arguments for this call (used to detect varargs matches) or
* -1 if no varargs support is used.
*
* @return the matching constructor. If none is found, null is returned. null is also returned
* if multiple constructors are found to match the signature
*/
private Method findMatchingMethod(Class<?> aClass,
String methodName,
Class<?>[] signature,
int minargs)
{
while (aClass != null)
{
Method ret = findMatchingMethodFromClass(aClass, methodName, signature, minargs);
if (ret != null)
{
return ret;
}
aClass = aClass.getSuperclass();
}
return null;
}
/**
* Look into the list of methods of a class for the one that matches name and the list of
* argument types. Only return a valid object if there is a clean choice. In case of
* collisions (multiple matching) null is returned as error.
* The process is rather costly. the worse case scenario involves a second pass to re-analyze
* the list overloaded methods if the first one (in strict mode) provided no solution.
* <p>
* This method is called only from {@link #findMatchingMethod}.
*
* @param aClass
* The class whose constructor list is checked.
* @param methodName
* The method name being searched for.
* @param signature
* An array with the java types of the arguments of the required constructor. Use
* {@code null} as a wildcard for unknown or {@code null} parameter type.
* @param minargs
* The minimum number of arguments for this call (used to detect varargs matches) or
* -1 if no varargs support is used.
*
* @return the matching constructor. If none is found, {@code null} is returned. {@code null}
* is also returned if multiple constructors are found to match the signature.
*/
private Method findMatchingMethodFromClass(Class<?> aClass,
String methodName,
Class<?>[] signature,
int minargs)
{
Method ret = null;
Set<Method> overloaded = new HashSet<>();
Set<Method> strictCheck = new HashSet<>();
for (Method meth : aClass.getDeclaredMethods())
{
if (meth.isSynthetic() || !meth.getName().equals(methodName))
{
continue; // the name does not match
}
overloaded.add(meth); // collect overloaded methods for a second iteration
Class<?>[] required = meth.getParameterTypes();
// try strict checking first
if (Function.matchSignature(required, signature, minargs, true))
{
strictCheck.add(meth);
ret = meth;
}
}
if (strictCheck.size() == 1)
{
// only one perfect match:
return ret;
}
else if (strictCheck.size() != 0)
{
// TODO: choose the most specific form of overloaded method
// * simple case with a single parameter:
// how to pick between: valueOf(Object), valueOf(BDT) when the argument is char ?
// * complex case:
// four classes: a extends A, b extends B
// two methods: m(A, b), m(a, B)
// which method is called when m(a, b) is invoked?
// temporary fix: return one of them, assuming they are well written (valueOf(Object) tests the
// parameter type and invokes valueOf(BDT) when appropriate)
return ret;
}
if (ret == null && !overloaded.isEmpty())
{
// second chance, check no-strict for overloaded methods
for (Method meth : overloaded)
{
Class<?>[] required = meth.getParameterTypes();
// now try non-strict checking
if (Function.matchSignature(required, signature, minargs, false))
{
// TODO: this is not correct we should refine the search the same way java does
// Ex: if the sig is an Integer and there are two methods: Long & Double
// Solution: sort overloaded on auto-promotions and choose the first match ?
ret = meth;
break;
}
}
}
return ret;
}
/***
* Inspects the node annotation for a runtime cached value. By convention, such annotations
* have "runtime-" prefix.
*
* The reason of existence of this method is debugging, to print the already computed
* runtime-annotations for a node.
*
* @param node
* The JAST node to be inspected.
* @param key
* The annotation name.
*
* @return the annotation value, or null, if annotation is not present.
*/
private Object getCache(Aast node, int key)
{
Object o = node.getRuntimeAnnotation(key);
if (o != null && LOG.isLoggable(Level.FINER))
{
LOG.finer("Found cached " + key + " for " + node.toString() +
"(" + node.getText() + ") = " + o);
}
return o;
}
/**
* Exception thrown within the RuntimeJastInterpreter. It contains the optional JAST node that
* caused the exceptional situation beside of normal message.
*/
static class InterpreterException
extends RuntimeException
{
/** Suffix to append to the error message of the cause; may be <code>null</code>. */
private String messageSuffix;
/**
* The constructor.
*
* @param message
* Message that explains the situation.
* @param node
* The node that caused the exception, possible null in some case.
* @param cause
* Another Throwable that caused this exception, possible none.
*/
public InterpreterException(String message, Aast node, Throwable cause)
{
super(message, cause);
if (node != null)
{
messageSuffix = " in [" + node.toString() + ": " + node.getText() + "].";
}
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, message, cause);
if (node != null)
{
LOG.fine(System.lineSeparator() + node.dumpTree());
}
}
}
/**
* Returns a short description of this exception, the message and node type and text that
* caused it if the node was provided at construction.
*
* @return Short description of this exception.
*/
@Override
public String toString()
{
String s = "Interpreter exception: " + super.getMessage();
if (messageSuffix != null)
{
s += messageSuffix;
}
return s;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link LogicalExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
class LogicalExpressionAdapter
extends LogicalExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public LogicalExpressionAdapter(RuntimeJastInterpreter interpreter,
JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public logical execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (logical) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link DateExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class DateExpressionAdapter
extends DateExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public DateExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public date execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (date) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link DatetimeExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class DatetimeExpressionAdapter
extends DatetimeExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public DatetimeExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public datetime execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (datetime) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link DatetimeTzExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class DatetimeTzExpressionAdapter
extends DatetimeTzExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public DatetimeTzExpressionAdapter(RuntimeJastInterpreter interpreter,
JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public datetimetz execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (datetimetz) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link IntegerExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class IntegerExpressionAdapter
extends IntegerExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public IntegerExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public integer execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (integer) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link Int64Expression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class Int64ExpressionAdapter
extends Int64Expression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public Int64ExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public int64 execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (int64) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link DecimalExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class DecimalExpressionAdapter
extends DecimalExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public DecimalExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public decimal execute()
{
if (executeMethod == null)
{
// not overridden ? call super class' method
return super.execute();
}
Object res = interpreter.execMethod(executeMethod);
return (decimal) res;
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link CharacterExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class CharacterExpressionAdapter
extends CharacterExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public CharacterExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
if (executeMethod == null)
{
throw new InterpreterException("Anonymous class does not override 'execute' method " +
"from CharacterExpression abstract superclass",
null, null);
}
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public character execute()
{
return (character) interpreter.execMethod(executeMethod);
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link RawExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class RawExpressionAdapter
extends RawExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public RawExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
if (executeMethod == null)
{
throw new InterpreterException("Anonymous class does not override 'execute' method " +
"from RawExpression abstract superclass",
null, null);
}
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public raw execute()
{
return (raw) interpreter.execMethod(executeMethod);
}
}
/**
* Adapter for an adapter class built using an anonymous constructor of the class
* {@link RowidExpression}. Overriding JASTs should implement at least the {@code execute}
* method. The evaluation of result of the overridden method is delegated to the interpreter.
* <p>
* This class is immutable so the instances of it are constants, once created, the internal
* data cannot be changed.
*/
static class RowidExpressionAdapter
extends RowidExpression
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this anonymous
* object. Must not be {@code null}.
*/
private RuntimeJastInterpreter interpreter;
/**
* The JAST tree that describes the {@code execute} method to be implemented by this object.
*/
private JavaAst executeMethod;
/**
* The only constructor.
* Takes a parameters the interpreter to which the evaluation of the {@code execute} method
* is delegated and the JAST of the implemented method.
*
* @param interpreter
* The {@link RuntimeJastInterpreter} responsible with interpretation of the
* methods overridden by this anonymous object.
* @param executeMethod
* The JAST that describes the {@code execute} method.
*/
public RowidExpressionAdapter(RuntimeJastInterpreter interpreter, JavaAst executeMethod)
{
if (executeMethod == null)
{
throw new InterpreterException("Anonymous class does not override 'execute' method " +
"from RowidExpression abstract superclass",
null, null);
}
this.interpreter = interpreter;
this.executeMethod = executeMethod;
}
/**
* Evaluates the {@code execute} method. If overridden, the {@code interpreter} is
* responsible for interpretation of the JAST, otherwise the super method is called.
*
* @return the interpreted result of the method.
*/
@Override
public rowid execute()
{
return (rowid) interpreter.execMethod(executeMethod);
}
}
/**
* Implements an interpreted generic lambda expression stored as a JAST node, using the
* {@code RuntimeJastInterpreter}.
*/
abstract static class Lambda
{
/**
* The {@code RuntimeJastInterpreter} that will handle interpretation of this lambda
* expression. Must not be {@code null}.
*/
protected RuntimeJastInterpreter interpreter;
/**
* The JAST node that contains the expression this lambda expression evaluates to.
* Always not {@code null}.
*/
protected Aast valueNode;
/** The list of parameters for this lambda expression */
protected Aast paramNodes;
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public Lambda(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
// validate the arguments
if (interpreter == null ||
lambdaNode == null ||
lambdaNode.getNumImmediateChildren() != 2 ||
lambdaNode.getChildAt(0).getType() != JavaTokenTypes.LPARENS)
{
throw new InterpreterException("Invalid lambda expression", lambdaNode, null);
}
this.interpreter = interpreter;
this.valueNode = lambdaNode.getChildAt(1);
}
}
/**
* Implements an interpreted {@link P2JQuery.Parameter} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class P2JQueryParameter
extends Lambda
implements P2JQuery.Parameter
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public P2JQueryParameter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
/**
* Evaluate the lambda expression.
* <p>
* Use the {@code RuntimeJastInterpreter} set in constructor to evaluate the node. That
* result is the value of the lambda expression.
*
* @return The value of the {@code (P2JQuery.Parameter) ()} lambda expression.
*/
@Override
public Object resolve()
{
try
{
return interpreter.evalExpression(valueNode);
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, err, ie);
}
else if (LOG.isLoggable(Level.WARNING))
{
String msg = err + " Use FINE level logging for a stack trace";
LOG.log(Level.WARNING, msg);
}
ErrorManager.recordOrThrowError(223, err);
return new unknown();
}
}
}
/**
* Implements an interpreted {@link P2JQuery.Parameter} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class P2JQueryParamResolver
extends Lambda
implements P2JQuery.ParamResolver
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public P2JQueryParamResolver(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
/**
* Evaluate the lambda expression.
* <p>
* Use the {@code RuntimeJastInterpreter} set in constructor to evaluate the node. That
* result is the value of the lambda expression.
*
* @return The value of the {@code (P2JQuery.Parameter) ()} lambda expression.
*/
@Override
public BaseDataType resolve()
{
try
{
return (BaseDataType)interpreter.evalExpression(valueNode);
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
if (LOG.isLoggable(Level.FINE))
{
LOG.log(Level.FINE, err, ie);
}
else if (LOG.isLoggable(Level.WARNING))
{
String msg = err + " Use FINE level logging for a stack trace";
LOG.log(Level.WARNING, msg);
}
ErrorManager.recordOrThrowError(223, err);
return new unknown();
}
}
}
/**
* Implements an interpreted {@code Supplier<logical>} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class ClientWhere
extends Lambda
implements Supplier<logical>
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public ClientWhere(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
/**
* Evaluate the lambda expression.
*
* @return The value of the {@code (Supplier<logical>) ()} lambda expression.
*/
@Override
public logical get()
{
try
{
return (logical) interpreter.evalExpression(valueNode);
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
ErrorManager.recordOrThrowError(223, err);
return new logical();
}
}
}
/**
* Implements an interpreted {@link LogicalOp} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class LogicalLambda
extends Lambda
implements LogicalOp
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public LogicalLambda(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
/**
* Evaluate the lambda expression.
*
* @return The value of the {@code (LogicalOp) ()} lambda expression.
*/
@Override
public logical evaluate()
{
try
{
return (logical) interpreter.evalExpression(valueNode);
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
ErrorManager.recordOrThrowError(223, err);
return new logical();
}
}
}
/**
* Implements an interpreted {@link Resolvable} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
abstract static class ResolvableAdapter
extends Lambda
implements Resolvable
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public ResolvableAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
/**
* Evaluate the lambda expression.
* <p>
* Use the {@code RuntimeJastInterpreter} set in constructor to evaluate the node. That
* result is the value of the lambda expression.
*
* @return The value of the lambda expression.
*/
@Override
public BaseDataType resolve()
{
try
{
return (BaseDataType) interpreter.evalExpression(valueNode);
}
catch (InterpreterException ie)
{
String err = "Incompatible data types in expression or assignment.";
ErrorManager.recordOrThrowError(223, err);
return new unknown();
}
}
}
/**
* Implements an interpreted {@link CharacterExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class CharacterExprAdapter
extends ResolvableAdapter
implements CharacterExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public CharacterExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link DateExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class DateExprAdapter
extends ResolvableAdapter
implements DateExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public DateExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link DatetimeExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class DatetimeExprAdapter
extends ResolvableAdapter
implements DatetimeExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public DatetimeExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link DatetimeTzExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class DatetimeTzExprAdapter
extends ResolvableAdapter
implements DatetimeTzExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public DatetimeTzExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link DecimalExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class DecimalExprAdapter
extends ResolvableAdapter
implements DecimalExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public DecimalExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link Int64Expr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class Int64ExprAdapter
extends ResolvableAdapter
implements Int64Expr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public Int64ExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link IntegerExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class IntegerExprAdapter
extends ResolvableAdapter
implements IntegerExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public IntegerExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link LogicalExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class LogicalExprAdapter
extends ResolvableAdapter
implements LogicalExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public LogicalExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link RawExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class RawExprAdapter
extends ResolvableAdapter
implements RawExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public RawExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link RecidExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class RecidExprAdapter
extends ResolvableAdapter
implements RecidExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public RecidExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
/**
* Implements an interpreted {@link RowidExpr} lambda expression stored as a JAST node,
* using the {@code RuntimeJastInterpreter}.
*/
static class RowidExprAdapter
extends ResolvableAdapter
implements RowidExpr
{
/**
* Constructor.
*
* @param interpreter
* The {@code RuntimeJastInterpreter} that will handle interpretation of this
* lambda expression. Must not be {@code null}.
* @param lambdaNode
* A JAST node with {@link com.goldencode.p2j.uast.JavaTokenTypes#LAMBDA} type and
* no arguments (they will be ignored, anyway).
*/
public RowidExprAdapter(RuntimeJastInterpreter interpreter, Aast lambdaNode)
{
super(interpreter, lambdaNode);
}
}
}