/* * Copyright 2016 Nabarun Mondal * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package com.noga.njexl.lang; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.Callable; import com.noga.njexl.lang.extension.oop.ScriptClass; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour; import com.noga.njexl.lang.extension.oop.ScriptMethod; import com.noga.njexl.lang.parser.*; /** * Instances of ExpressionImpl are created by the {@link JexlEngine}, * and this is the default implementation of the {@link Expression} and * {@link Script} interface. * @since 1.0 */ public class ExpressionImpl implements Expression, Script , ScriptClassBehaviour.Executable { String location; String importName; final HashMap<String,ScriptClass> classes; final HashMap<String,ScriptMethod> methods; final HashMap<String,ASTImportStatement> imports; final HashMap<String,Integer> jumps; /** * This is to ensure that we can return values w/o * defaulting to the exact need * @param prop name of the class or method * @return the class or method, else null */ @Override public Object get(String prop){ if ( classes.containsKey(prop)){ return classes.get(prop); } if ( methods.containsKey(prop)){ return methods.get(prop); } return null; } /** The engine for this expression. */ protected final JexlEngine jexl; /** * Original expression stripped from leading and trailing spaces. */ protected final String expression; /** * The resulting AST we can interpret. */ protected final ASTJexlScript script; protected void findInnerObjects(JexlNode node){ if ( node instanceof ASTImportStatement ){ ASTImportStatement importDef = (ASTImportStatement)node; String as = importDef.jjtGetChild(1).image ; imports.put( as , importDef ); } else if ( node instanceof ASTClassDef){ ScriptClass classDef = new ScriptClass(interpreter, (ASTClassDef)node,importName); classes.put( classDef.name , classDef ); } else if ( node instanceof ASTMethodDef){ if ( node.jjtGetParent() instanceof ASTAssignment ){ // should be taken care later, don't worry }else { ScriptMethod methodDef = new ScriptMethod((ASTMethodDef) node); methods.put(methodDef.name, methodDef); } }else { int numChild = node.jjtGetNumChildren(); for ( int i =0; i < numChild; i++ ){ findInnerObjects(node.jjtGetChild(i)); } } } protected void findLabels(JexlNode node){ int n = node.jjtGetNumChildren(); for ( int i = 0 ; i < n ; i++ ){ JexlNode child = node.jjtGetChild(i); if ( child instanceof ASTLabelledStatement ){ jumps.put( child.jjtGetChild(0).image , i ); } } } protected ExpressionImpl(String from, String as, JexlEngine engine, String expr, ASTJexlScript ref) { jexl = engine; expression = expr; script = ref; methods = new HashMap<>(); imports = new HashMap<>(); classes = new HashMap<>(); jumps = new HashMap<>(); location = from ; if ( location != null && !location.isEmpty() ){ location = location.replace("\\","/"); // windows? location = location.substring(0,location.lastIndexOf("/")); } importName = as; findLabels(script); } /** * Do not let this be generally instantiated with a 'new'. * * @param engine the interpreter to evaluate the expression * @param expr the expression. * @param ref the parsed expression. */ protected ExpressionImpl(JexlEngine engine, String expr, ASTJexlScript ref) { this("", Script.DEFAULT_IMPORT_NAME, engine, expr, ref); } /** * {@inheritDoc} */ public Object evaluate(JexlContext context) { if (script.jjtGetNumChildren() < 1) { return null; } Interpreter interpreter = jexl.createInterpreter(context); interpreter.setFrame(script.createFrame((Object[]) null)); interpreter.current = this; this.interpreter = interpreter ; return interpreter.interpret(script.jjtGetChild(0)); } /** * {@inheritDoc} */ public String dump() { Debugger debug = new Debugger(); boolean d = debug.debug(script); return debug.data() + (d ? " /*" + debug.start() + ":" + debug.end() + "*/" : "/*?:?*/ "); } /** * {@inheritDoc} */ public String getExpression() { return expression; } /** * Provide a string representation of this expression. * @return the expression or blank if it's null. */ @Override public String toString() { String expr = getExpression(); return expr == null ? "" : expr; } /** * {@inheritDoc} */ public String getText() { return toString(); } Interpreter interpreter; public Interpreter interpreter(){ return interpreter ; } ScriptMethod getMethod(String method){ ScriptMethod methodDef = methods.get(method); if ( methodDef != null ){ return methodDef; } // by argument? if ( interpreter.context.has(method)){ Object m = interpreter.context.get(method); if ( m!= null ){ if ( m instanceof ScriptMethod ) return (ScriptMethod)m; String[] arr = m.toString().split(":"); // ignore first, call by last. return methods.get(arr[arr.length-1]); } } return null; } @Override public Object execMethod(String method,Interpreter i, Object[] args){ try { ScriptMethod methodDef = getMethod(method); if (methodDef == null) { if ( MY_MAIN_I.equals(method) ){ JexlContext copy = i.context.copy(); copy.set(ARGS, args); try { return execute(copy, args); }finally { copy.clear(); copy = null; } } throw new NoSuchMethodException("Method : '" + method + "' is not found in : " + this.importName); } return methodDef.invoke(null,i, args); }catch (Throwable e){ throw new Error(e); } } /** * {@inheritDoc} */ public Object execute(JexlContext context) { return execute(context, (Object[]) null); } public void setup(JexlContext context){ interpreter = jexl.createInterpreter(context); findInnerObjects(this.script); interpreter.current = this; interpreter.setFrame(script.createFrame(new Object[]{})); interpreter.imports.put(importName, this); interpreter.imports.put(null, this); //only execute the import statements for ( String i : imports.keySet() ){ ASTImportStatement importStatement = imports.get(i); interpreter.visit( importStatement, this); } interpreter.functions.put(importName,this); } /** * {@inheritDoc} * @since 2.1 */ public Object execute(JexlContext context, Object... args) { interpreter = jexl.createInterpreter(context); findInnerObjects(this.script); interpreter.current = this; interpreter.setFrame(script.createFrame(args)); // the following are key for calling methods ... interpreter.imports.put(importName, this); interpreter.imports.put(Script.SELF, this); interpreter.imports.put(null, this); interpreter.functions.put(importName,this); return interpreter.interpret(script); } /** * {@inheritDoc} * @since 2.1 */ public String[] getParameters() { return script.getParameters(); } /** * {@inheritDoc} * @since 2.1 */ public String[] getLocalVariables() { return script.getLocalVariables(); } /** * {@inheritDoc} * @since 2.1 */ public Set<List<String>> getVariables() { return jexl.getVariables(this); } /** * {@inheritDoc} * @since 2.1 */ public Callable<Object> callable(JexlContext context) { return callable(context, (Object[]) null); } /** * {@inheritDoc} * @since 2.1 */ public Callable<Object> callable(JexlContext context, Object... args) { final Interpreter interpreter = jexl.createInterpreter(context); interpreter.setFrame(script.createFrame(args)); return new Callable<Object>() { /** Use interpreter as marker for not having run. */ private Object result = interpreter; public Object call() throws Exception { if (result == interpreter) { result = interpreter.interpret(script); } return result; } }; } @Override public String location() { return location; } @Override public HashMap<String, ScriptMethod> methods() { return methods; } @Override public HashMap<String, ScriptClass> classes() { return classes; } @Override public HashMap<String, ASTImportStatement> imports() { return imports; } @Override public ASTJexlScript script(){ return script; } @Override public String name() { return importName; } @Override public Map<String, Integer> jumps() { return jumps; } }