# NamedFunction.java

    /*
    ** Module   : NamedFunction.java
    ** Abstract : encapsulates the data and logic of named expressions
    **
    ** Copyright (c) 2005-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description------------------------------------
    ** 001 GES 20050601   @21358 Created initial version. Implements a scope,
    **                           stores the expression text and the list
    **                           of parameter names. Most importantly, it
    **                           provides the functionality of executing the
    **                           expression (first handling all variable
    **                           assignments on behalf of the "caller" which
    **                           is an expression in a rule-set).
    ** 002 GES 20050602   @21390 Converted from NamedExpression to
    **                           NamedFunction.  Added an optional return
    **                           variable, local variables and a list of
    **                           expressions which are executed in order.
    ** 003 ECF 20050604   @21418 Create expressions using AstSymbolResolver's
    **                           createExpression method.
    ** 004 GES 20050719   @21736 Added support for containing and executing
    **                           rules, which provides the ability to embed
    **                           complex logic inside a function.  This
    **                           allows one to create "callable" rules.
    ** 005 ECF 20050728   @21971 Fixed scoping and added variable resets upon
    **                           each execution. Enclosing scope now reports
    **                           the scope of the caller which invoked this
    **                           function. Local variables are reset to their
    **                           initial values upon each execution of the
    **                           function.
    ** 006 ECF 20080730   @39268 Ensure variables are cleared at the end of
    **                           the execute() method. Reduced memory
    **                           footprint.
    ** 007 GES 20130206          Reduced code duplication and reworked variable
    **                           registration for the new initializer approach.
    ** 008 ECF 20150219          Protect integrity of enclosingScopes deque by popping in a finally
    **                           block. Implemented generics.
    ** 009 HC  20160907          Implemented a simple tracing support for rules processing.
    ** 010 ECF 20171228          Improve performance by reducing context-local lookups.
    ** 011 CA  20221010          Avoid using iterators on ArrayList - instead, use a 'for' loop and get the
    **                           element by index, as iterators are more expensive in terms of memory and
    **                           performance.  Refs #6813
    ** 012 CA  20230223          Avoid the LinkedList overhead, use ArrayDeque instead, for 'enclosingScopes'.
    ** 013 CA  20230531          Moved the scope state from the resolver to the actual scope; this avoids using
    **                           a WeakHashMap to determine the scope's state associated with a certain resolver,
    **                           as long as a single resolver instance uses that scope (common for conversion).
    ** 014 CA  20231129          Small changes to avoid 'invokeinterface' in the compiled bytecode.
    */
    /*
    ** 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.pattern;

    import java.util.*;
    import com.goldencode.expr.*;

    /**
     * Provides a container to manage, store and execute a named function.  The
     * function can have one or more expressions which are executed. Such
     * functions can have an optional fixed list of named parameters,
     * local variables and a return variable which can be referenced in the
     * given expressions and rules.  These parameters are replaced at execution
     * time which allows a "function call" type semantic for rule set users.
     * <p>
     * By virtue of the fact that rules can be added to the execute list,
     * functions support the ability to embed complex logic including control
     * flow (e.g. conditional execution and looping) and actions. Almost all of
     * the capabilities of rules can be used including nesting. The sole
     * exception is that rule types are completely ignored (even if specified
     * explicitly using an attribute).  This makes sense since there should be
     * no limitation on when this function can be called.  Such support allows
     * one to create "callable" rules using functions.
     * <p>
     * Please note that variable and return value initializers (the
     * <code>init</code> attribute) are executed at the time that the function
     * is loaded rather than at runtime.  This means that parameters that are
     * passed to the function CANNOT be accessed in such initializers since the
     * parameters will not yet have values until the function is actually called.
     * <p>
     * <b>Known limitation:</b> currently, recursive calls to a single named
     * function are not supported, since only one local variable pool per named
     * function is maintained in the symbol resolver. This limitation includes
     * direct recursion (named function calls itself) as well as indirect
     * recursion (named function calls some other function which eventually calls
     * original named function). This limitation may be removed in future
     * development.
     *
     * @author    GES
     * @version   1.0.4
     */
    class NamedFunction
    extends Scope
    {
       /** List of parameter names. */
       private ArrayList<String> params = new ArrayList<>(1);

       /** Stack of enclosing scopes;  scopes associated with call stack */
       private ArrayDeque<Scope> enclosingScopes = new ArrayDeque<>();

       /** The list of expressions to execute. */
       private ArrayList expr = new ArrayList(2);

       /**
        * The name of the return variable or <code>null</code> if the result
        * of the last expression evaluation should be returned.
        */
       private String returnVar = null;

       /**
        * Constructor which assigns this container's enclosing scope and the
        * infix text of the first expression for this function.
        *
        * @param   parent
        *          Rule container which provides enclosing scope to this user
        *          function. May be <code>null</code>.
        * @param   expr
        *          The infix text of the expression associated with this
        *          instance.
        */
       NamedFunction(RuleContainer parent, String expr)
       {
          enclosingScopes.push(parent);
          registerExpression(expr);
       }

       /**
        * Return the scope of the object in whose scope this function currently
        * is enclosed. This is typically the object which executed this function.
        *
        * @return  This function's enclosing scope. Should not be
        *          <code>null</code>.
        */
       public Scope getEnclosingScope()
       {
          // Although this technically can throw EmptyStackException, it is
          // not reported, since this would represent an architectural defect
          // rather than a legitimate runtime exception.
          return enclosingScopes.peek();
       }

       /**
        * Register a new expression for this named function.  This will be added
        * to the list of expressions/rules to be executed later in order.
        *
        * @param   text
        *          The infix text of the expression.
        */
       public void registerExpression(String text)
       {
          if (text != null)
          {
             expr.add(AstSymbolResolver.createExpression(text));
          }
       }

       /**
        * Register a new parameter for this named expression. If the parameter's
        * name is already in use within this rule container's scope, it cannot
        * be re-used and this method will fail.
        *
        * @param   name
        *          Name by which this parameter will be referenced in the
        *          expression.
        * @param   type
        *          Data type of the variable. Cannot be <code>null</code>.
        *
        * @throws  SymbolException
        *          if the parameter's name is already in use in this container's
        *          scope.
        * @throws  IllegalArgumentException
        *          if <code>type</code> is not provided.
        */
       public void registerParameter(String name, Class<?> type)
       {
          AstSymbolResolver resolver = AstSymbolResolver.getResolver();
          resolver.registerVariable(this, name, type, null, false);

          // store the parameter name in the correct order (this is essentially
          // our "signature")
          params.add(name);
       }

       /**
        * Register a variable which will be returned from this function. If the
        * return variable's name is already in use within this function's scope,
        * it cannot be re-used and this method will fail. If both
        * <code>type</code> and <code>expression</code> are provided,
        * <code>type</code> must match the return type of
        * <code>expression</code>.
        *
        * @param   name
        *          Name by which this variable will be referenced in expressions.
        * @param   type
        *          Data type of the variable. May be <code>null</code> if and
        *          only if <code>expression</code> is not <code>null</code>
        *          <b>and</b> <code>expression</code>'s return type is not
        *          <code>null</code>.
        * @param   expression
        *          Expression which will be used to initialize the variable each
        *          time it is {@link com.goldencode.expr.Variable#reset reset}
        *          by the pattern engine. May be <code>null</code> if and only
        *          if <code>type</code> is not <code>null</code>.
        *
        * @throws  SymbolException
        *          if the variable's name is already in use in this container's
        *          scope.
        * @throws  ExpressionException
        *          if <code>type</code> is a class which is not assignable from
        *          <code>expression</code>'s return type.
        * @throws  IllegalArgumentException
        *          if <code>type</code> is not provided and it cannot be
        *          determined from <code>expression</code>.
        *
        * @see     com.goldencode.expr.SymbolResolver#resetVariables
        */
       public void registerReturn(String name, Class<?> type, String expression)
       {
          registerVariable(name, type, expression);

          // save the name so we can lookup this variable later
          returnVar = name;
       }

       /**
        * Register a new user-defined local variable with this function. If the
        * variable's name is already in use within this rule function's scope,
        * it cannot be re-used and this method will fail. If both
        * <code>type</code> and <code>expression</code> are provided,
        * <code>type</code> must match the return type of
        * <code>expression</code>.
        *
        * @param   name
        *          Name by which this variable will be referenced in expressions.
        * @param   type
        *          Data type of the variable. May be <code>null</code> if and
        *          only if <code>expression</code> is not <code>null</code>
        *          <b>and</b> <code>expression</code>'s return type is not
        *          <code>null</code>.
        * @param   expression
        *          Expression which will be used to initialize the variable each
        *          time it is {@link com.goldencode.expr.Variable#reset reset}
        *          by the pattern engine. May be <code>null</code> if and only
        *          if <code>type</code> is not <code>null</code>.
        *
        * @throws  SymbolException
        *          if the variable's name is already in use in this function's
        *          scope.
        * @throws  ExpressionException
        *          if <code>type</code> is a class which is not assignable from
        *          <code>expression</code>'s return type.
        * @throws  IllegalArgumentException
        *          if <code>type</code> is not provided and it cannot be
        *          determined from <code>expression</code>.
        *
        * @see     com.goldencode.expr.SymbolResolver#resetVariables
        */
       public void registerVariable(String name, Class<?> type, String expression)
       {
          ExpressionInitializer ei = null;

          if (expression != null)
          {
             Expression expr = AstSymbolResolver.createExpression(expression);
             ei = new ExpressionInitializer(expr);
          }

          AstSymbolResolver resolver = AstSymbolResolver.getResolver();
          resolver.registerVariable(this, name, type, ei, false);
       }

       /**
        * Register a rule in the list of expressions/rules that the function
        * will execute in order.
        *
        * @param   expression
        *          Logical expression which will be compiled into the condition
        *          expression of the rule.
        * @param   type
        *          Type of rule to add: init, walk, post, etc., using the
        *          <code>RULE_*</code> constants.
        * @param   file
        *          The file the rule is defined in, used for tracing.
        * @param   line
        *          The file line the rule is defined at, used for tracing.
        *
        * @throws  IllegalArgumentException
        *          if the rule type is unrecognized.
        */
       public Rule registerRule(String expression, int type, String file, int line)
       {
          switch (type)
          {
             case RuleContainer.RULE_WALK:
             case RuleContainer.RULE_INIT:
             case RuleContainer.RULE_POST:
             case RuleContainer.RULE_DESCENT:
             case RuleContainer.RULE_NEXT_CHILD:
             case RuleContainer.RULE_ASCENT:
                break;
             default:
                throw new IllegalArgumentException("Invalid rule type: " + type);
          }

          Rule rule = new Rule(expression, type, file, line);

          expr.add(rule);

          return rule;
       }

       /**
        * Execute this named function, handling all user provided parameter
        * assignments before the actual execution.  The execution proceeds
        * through all expressions (defined via {@link #registerExpression}) and
        * rules (defined via {@link #registerRule}) in the order in which they
        * were added.  Rules are not simple expressions but instead they can
        * encode complex logical control flow (conditional processing and
        * looping) in addition to an arbitrary action execution.  This means
        * that there is no single return value that can be captured from a
        * rule.  For this reason, if a rule needs to be used to set a return
        * value for the function, then an explicit assignment will be needed.
        * If no explicit assignment to the return variable has occurred, then
        * the result of the last executed expression will be used as the return
        * value.
        *
        * @param   resolver
        *          AST symbol resolver.
        * @param   callerScope
        *          Scope of the caller; becomes this function's current,
        *          enclosing scope for the duration of this execution.
        * @param   args
        *          The array of <code>Object</code> instances of the correct
        *          data type and in the correct order to match our parameter
        *          list.  Each element of the array will be assigned into the
        *          matching parameter in our parameter list before the expressions
        *          are executed.
        *
        * @return  The result of executing the expressions.
        *
        * @throws  ExpressionException
        *          if the expression cannot be compiled.
        * @throws  IllegalArgumentException
        *          if the number of passed parameters is different than the
        *          number of expected parameters.
        * @throws  IllegalStateException
        *          if this named function appears anywhere in the call stack
        *          leading up to this invocation.
        */
       public Object execute(AstSymbolResolver resolver, Scope callerScope, Object[] args)
       {
          // assert arguments passed match the number expected.
          if (args.length != params.size())
          {
             throw new IllegalArgumentException("Expected " + params.size() +
                                                " parameters, found " +
                                                args.length + ".");
          }

          // detect recursion and throw an exception if this named function
          // appears higher up in the call stack.
          Scope tmpScope = callerScope;
          while (tmpScope != null)
          {
             if (tmpScope == this)
             {
                throw new IllegalStateException(
                   "Function recursion detected;  not currently supported");
             }
             tmpScope = tmpScope.getEnclosingScope();
          }

          enclosingScopes.push(callerScope);

          try
          {
             // reset local variables to their initial states (note:  this will
             // also reset parameters and return, since these are implemented
             // as variables registered to this function's scope).
             resolver.resetVariables(this, false);

             String[] list = (String[]) params.toArray(new String[0]);

             // assign each argument into the associated parameter
             for (int i = 0; i < list.length; i++)
             {
                Variable parm = resolver.getVariable(this, list[i]);
                parm.setValue(args[i]);
             }

             Object result = null;

             // execute all expressions in order
             int size = expr.size();
             Object item;
             for (int i = 0; i < size; i++)
             {
                item = expr.get(i);

                if (item instanceof Expression)
                {
                   Expression e = (Expression) item;
                   result = e.execute();
                }
                else
                {
                   // must be a rule
                   Rule r = (Rule) item;
                   r.apply(resolver, RuleContainer.RULE_WALK);
                }
             }

             // honor the user-defined return variable if defined
             if (returnVar != null)
             {
                Variable ret = resolver.getVariable(this, returnVar);
                result = ret.getValue();
             }

             return result;
          }
          finally
          {
             // there is a slight chance that resetting variables can throw an exception, so protect
             // the integrity of enclosing scope deque in a finally block
             try
             {
                resolver.resetVariables(this, true);
             }
             finally
             {
                enclosingScopes.pop();
             }
          }
       }

       /**
        * Termination hook to allow resources to be cleaned up when this object is
        * about to go out of service.
        */
       public void cleanup()
       {
          params = null;
          enclosingScopes = null;
          expr = null;
       }
    }