/* * 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.extension.oop; import com.noga.njexl.lang.*; import com.noga.njexl.lang.extension.SetOperations; import com.noga.njexl.lang.extension.TypeUtility; import com.noga.njexl.lang.extension.datastructures.ListSet; import com.noga.njexl.lang.extension.datastructures.XList; import com.noga.njexl.lang.parser.ASTBlock; import com.noga.njexl.lang.parser.ASTIdentifier; import com.noga.njexl.lang.parser.ASTMethodDef; import com.noga.njexl.lang.parser.JexlNode; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Eventing.Event; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Eventing; import com.noga.njexl.lang.extension.oop.ScriptClassBehaviour.Executable; import java.io.Serializable; import java.util.HashMap; import java.util.List; import java.util.Set; /** * Also supports closure... * Created by noga on 08/04/15. */ public class ScriptMethod implements Serializable { public static final String IDENTITY = "def(){ __args__ ; }" ; public static final String COMPOSE = "def(){ _L_ = def() %s ; _R_ = def() %s ; _a_ = __args__ ; " + " r = _R_( __args__ = _a_ ) ; _L_( __args__ = array(r) ) ; }" ; public static final String POW = "def(){ _ME_ = def() %s ; _a_ = __args__ ; for( i : [0:%d] ) " + " { _a_ = _ME_( __args__ = _a_ ) } ; _a_ }" ; public final transient String name; public final transient ASTBlock astBlock; public final transient boolean instance; public final transient HashMap<String, Object> defaultValues; public final transient ListSet<String> params; public final transient boolean nested ; public final transient JexlContext parentContext; public final String definitionText ; /** * In case this is defined inside a class */ Executable declaringObject = null; public static boolean isNested(JexlNode current){ while ( current != null ){ current = current.jjtGetParent(); if ( current instanceof ASTMethodDef) return true ; } return false ; } /** * Creates a method back from definition text ! * @param definitionText the text of the method * @param context a @{JexlContext} within which the method would be created * @return a @{ScriptMethod} */ public static ScriptMethod fromDefinitionText(String definitionText, JexlContext context){ JexlEngine e = new JexlEngine(); Script s = e.createScript( definitionText ); s.execute(context); return s.methods().values().iterator().next() ; } /** * Compose functions f of g , f is myself * @param other other, the g * @return composition */ public ScriptMethod compose( ScriptMethod other){ String me = Debugger.getText( astBlock ); String him = Debugger.getText( other.astBlock ); String defText = String.format( COMPOSE , me, him ); return fromDefinitionText( defText, parentContext ); } /** * Compose f^l(x) * @param l the power in long * @return a function composition of power */ public ScriptMethod pow( long l ){ if ( l < 0 ) throw new IllegalArgumentException("Power of a function must be >= 0 !"); if ( l == 0 ) return fromDefinitionText( IDENTITY , parentContext ); if ( l == 1 ) return this ; String me = Debugger.getText( astBlock ); String defText = String.format( POW , me, l ); return fromDefinitionText( defText, parentContext ); } public ScriptMethod(ASTMethodDef def){ this(def,null); } public ScriptMethod(ASTMethodDef def, JexlContext parent) { definitionText = Debugger.getText(def); JexlNode n = def.jjtGetChild(0); int start ; if ( n instanceof ASTIdentifier ){ start = 1 ; name = n.image ; }else{ name = "" ; // anonymous, so no name start = 0 ; } nested = isNested(def); // is it a closure definition ? parentContext = parent ; // set it up for closure defaultValues = new HashMap<>(); params = new ListSet<>(); int numChild = def.jjtGetNumChildren(); for (int i = start ; i < numChild - 1; i++) { // process params String argName = def.jjtGetChild(i).jjtGetChild(0).image; params.add(argName); Object value = null; if (def.jjtGetChild(i).jjtGetNumChildren() == 2) { value = def.jjtGetChild(i).jjtGetChild(1); } defaultValues.put(argName, value); } astBlock = (ASTBlock) def.jjtGetChild(numChild - 1); instance = ( !params.isEmpty() && ScriptClass.SELF.equals(params.get(0))); if ( instance ) { // This was always dummy... params.remove(0); } before = new XList<>(); after = new XList<>(); } public final transient List before; public final transient List after; private void dispatch(String p, Interpreter interpreter, Object[] args, Object result, Throwable error, List listeners){ Event event = new Event( p, this, args, result, error ); Object[] argv = new Object[]{ event } ; for ( Object l : listeners ){ if ( l instanceof ScriptClassBehaviour.Executable ){ ScriptClassBehaviour.Executable e = (ScriptClassBehaviour.Executable)l; try { e.execMethod(p, interpreter, args ); }catch (Throwable t){ } } if ( l instanceof ScriptMethod ){ ScriptMethod m = (ScriptMethod)l; try { m.invoke( null , interpreter, argv ); }catch (Throwable t){ } } } } private Object getDefault(String paramName,Interpreter interpreter) { if (defaultValues.containsKey(paramName)) { Object defValue = defaultValues.get(paramName); if ( defValue instanceof JexlNode) { defValue = ((JexlNode)defValue).jjtAccept(interpreter, null); } return defValue; } return null; } private HashMap<String, Object> getParamValues(Interpreter interpreter, Object[] args) throws Exception { HashMap<String, Object> map = new HashMap(); boolean namedArgs = false ; for (int i = 0; i < args.length; i++) { if ( args[i] instanceof Interpreter.AnonymousParam ){ continue; } if (args[i] instanceof Interpreter.NamedArgs) { Interpreter.NamedArgs na = (Interpreter.NamedArgs) args[i]; map.put(na.name, na.value); namedArgs = true ; continue; } if ( namedArgs ) { // should never reach here....! throw new Exception("All Parameters are not using '=' "); } } boolean __args__ = map.containsKey( Script.ARGS) ; if ( namedArgs && ! __args__ ) { // now put the back stuff Set<String> remaining = SetOperations.set_d(defaultValues.keySet(), map.keySet()); for (String name : remaining) { Object defValue = getDefault(name,interpreter); map.put(name, defValue); } } else{ if ( __args__ ){ // I specified an array to overwrite, so : args = TypeUtility.array(map.get(Script.ARGS)); // unwrap! } for ( int i = 0 ; i < params.size();i++ ){ String name = params.get(i); if ( i < args.length ){ map.put(name,args[i]); }else{ Object defValue = getDefault(name,interpreter); map.put(name, defValue); } } } map.put(Script.ARGS,args); return map; } private void addToContext(JexlContext context, HashMap<String, Object> map) { for (String key : map.keySet()) { context.set(key, map.get(key)); } } protected boolean checkIfReflectiveCall(Object[] args){ try{ ScriptClassInstance instance = (ScriptClassInstance)args[0]; return instance.$.methods.containsKey(this.name); }catch (Exception e){ return false ; } } public Object invoke(Object me, Interpreter interpreter, Object[] args) throws Exception { dispatch(Eventing.BEFORE, interpreter,args,null,null,before); JexlContext oldContext = interpreter.getContext(); JexlContext toBeCopiedContext = oldContext ; if ( nested ){ // closure and nested functions //TODO : how do we integrate interpreter context changes here? toBeCopiedContext = parentContext ; } //process params : copy context JexlContext context = toBeCopiedContext.copy(); HashMap map = getParamValues(interpreter, args); if ( me != null ){ map.put(ScriptClass.SELF, me); } else if(declaringObject != null ){ if ( instance ) { boolean reflective = checkIfReflectiveCall(args); if (reflective) { me = args[0]; map.put(ScriptClass.SELF, me); args = TypeUtility.shiftArrayLeft(args, 1); } else { throw new Exception("Instance Method called with null instance!"); } }else{ // static map.put(ScriptClass.SELF, declaringObject ); } } addToContext(context, map); Object ret = null; Throwable error = null; try { interpreter.setContext(context); ret = astBlock.jjtAccept(interpreter,null); return ret; }catch (Exception e){ if ( e instanceof JexlException.Return){ return ((JexlException.Return) e).getValue(); } error = e; e.printStackTrace(); // ensure that we have an issue which is to be noted down... } finally { dispatch(Eventing.AFTER, interpreter,args,ret, error , after); //finally remove interpreter.setContext(oldContext); } return null; } @Override public String toString() { return "ScriptMethod{" + " name='" + name + '\'' + ", instance=" + instance + ", body=" + definitionText + '}'; } @Override protected void finalize() throws Throwable { this.after.clear(); this.before.clear(); this.defaultValues.clear(); this.params.clear(); super.finalize(); } }