# CallbackResolver.java

    /*
    ** Module   : CallbackResolver.java
    ** Abstract : provides a dictionary of method callbacks to implement user-
    **            defined functions without tight coupling between caller and
    **            called
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------Description-----------------------------
    ** 001 GES 20041116   @18655 First version, with support for resolving:
    **                           - optionally abbreviated keywords
    **                             representing the function name to match
    **                           - static and instance method callbacks
    **                           - method resolution via reflection
    ** 002 GES 20041122   @18719 Minor changes to allow for overloaded
    **                           methods (within the same target class):
    **                           - removed the caching of Method instances
    **                             in lookupCallback()
    **                           - modified the javadoc to explain the
    **                             situations in which overloading will work
    **                             and the technique by which one can use it
    **                           This was needed to fully handle the defined()
    **                           preprocessor function.
    ** 003 GES 20070319   @32497 Improved error handling code. Provided new
    **                           support for invoking constructors. Moved
    **                           away from use of certain J2SE reflection
    **                           interfaces in favor of more appropriate
    **                           search algorithms that are implemented in
    **                           ReflectionHelper.
    ** 004 IAS 20160331          Fixed potential NPE
    ** 005 GES 20190929          Added a copy constructor.
    ** 006 TJD 20220504          Java 11 compatibility minor changes
    ** 007 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */

    /*
    ** 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.uast;

    import com.goldencode.p2j.convert.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.util.*;

    import java.io.*;
    import java.lang.reflect.*;
    import java.util.*;
    import java.util.logging.*;

    /**
     * Provides a mechanism for storage and retrieval of method callbacks as
     * represented by the <code>{@link Callback}</code> object.  Each callback is
     * stored in a dictionary and can be found by a literal match with a function
     * name or can be optionally matched with a set of valid function name
     * abbreviations.
     * <p>
     * The namespace for the dictionary is flat.  For one function name that is
     * matched in the resolver dictionary, there is only one <code>Callback</code>
     * object that can be found.  This means that any features that are coded
     * in a callback definition must be implemented the same way for all target
     * methods.  This is especially important when overloading function names.
     * <p>
     * Limitations on function overloading:
     * <ul>
     *    <li> All overloaded functions must be implemented in the same instance
     *         object (they can resolve to any parent in the class hierarchy or
     *         to the class of the instance) if non-static.
     *    <li> If the method is defined as static, all overloaded functions must
     *         be in the same class.
     *    <li> All overloaded functions need to be static or all non-static
     *         since there is only one instance object in a callback definition
     *         and this instance determines how the method is called (if it is
     *         <code>null</code> it is static, otherwise it is used as the
     *         instance for the method call.
     *    <li> Overloading *cannot* be done using the
     *         <code>{@link #addCallback(Keyword,Object,Method)}</code> method
     *         since this hardcodes the <code>Method</code> object to be used
     *         for the invocation. In this case, the same exact method will
     *         *always* by invoked.
     *    <li> To implement overloading, use the
     *         <code>{@link #addCallback(Keyword,Object,String,String)}</code>
     *         method.  This delays the resolution of the <code>Method</code>
     *         object until <code>{@link #lookupCallback}</code> is invoked
     *         which is the time at which the proper signature is known. Each
     *         time the lookup is done, the signature that is passed in is used
     *         to resolve to the correct target method with the same name as
     *         that coded in the callback definition.
     *    <li> Due to the flat namespace, there is only one mapping of a keyword
     *         to a method.  This means that for every Progress keyword that must
     *         be overloaded, there is only one <code>Keyword</code> object that
     *         can be found and this single object can have only 1 associated
     *         target method name.  This single method name must have 1 signature
     *         for every match to a valid signature that will be found in the
     *         Progress function call.
     * </ul>
     * WARNING: this class is NOT thread-safe!!!!  In particular, the
     * <code>invokeCallback</code> method reads a callback definition and then
     * calculates and uses some information from it.  There is an inherent
     * race condition in between obtaining and using this callback information.
     *
     * @author GES
     */
    public class CallbackResolver
    {
       /** Logger */
       private static final ConversionStatus LOG = ConversionStatus.get(CallbackResolver.class);

       /**
        * Stores the set of all <code>Keywords</code> with each keyword
        * corresponding to a specific <code>Callback</code> object.
        */
       private KeywordDictionary kwDict;

       /**
        * Default constructor which instantiates a <code>CallbackResolver</code>
        * object which matches on a case-insensitive basis.
        */
       public CallbackResolver()
       {
          this(false);
       }

       /**
        * Instantiates a <code>CallbackResolver</code> object.
        *
        * @param    matchCase
        *           Controls lookup algorithm case-sensitivity.
        */
       public CallbackResolver(boolean matchCase)
       {
          kwDict = new KeywordDictionary(matchCase);
       }

       /**
        * Copy constructor, makes an independent duplicate of the given instance.
        *
        * @param    other
        *           Instance to duplicate.
        */
       public CallbackResolver(CallbackResolver other)
       {
          this.kwDict = new KeywordDictionary(other.kwDict);
       }

       /**
        * Creates a <code>{@link Callback}</code> object using the
        * {@link Callback#Callback(Object,String,String)} constructor and then
        * stores this instance in the <code>Keyword</code> object and adds this
        * associated <code>Keyword</code> object to the dictionary.
        * <p>
        * This method MUST be used to add an overloaded function to the
        * dictionary, since this is the only way to add a callback definition in
        * which the signature of the target is NOT hard coded.
        *
        * @param     word
        *            Keyword object to add. Must not be <code>null</code>.
        * @param     instance
        *            Target object on which to invoke the callback or
        *            <code>null</code> if the method is static.
        * @param     className
        *            Fully qualified class name of the target object's class.
        *            Must not be <code>null</code>.
        * @param     methodName
        *            Method name of the method to invoke on the target object.
        *            Use <code>null</code> or an empty string to specify a
        *            constructor.
        */
       public void addCallback(Keyword word,
                               Object  instance,
                               String  className,
                               String  methodName)
       {
          word.setExtra(new Callback(instance, className, methodName));
          kwDict.addKeyword(word);
       }

       /**
        * Creates a <code>{@link Callback}</code> object using the
        * {@link Callback#Callback(Object,Method)} constructor and then
        * stores this instance in the <code>Keyword</code> object and adds this
        * associated <code>Keyword</code> object to the dictionary.
        * <p>
        * This method CANNOT be used to add an overloaded function to the
        * dictionary, since the signature of the target is hard coded into the
        * <code>Method</code> object.  Use
        * <code>{@link #addCallback(Keyword,Object,String,String)}</code>
        * instead.
        *
        * @param     word
        *            Keyword object to add.
        * @param     instance
        *            Target object on which to invoke the callback or null if the
        *            method is static.
        * @param     method
        *            Method object which can be used to invoke the target
        *            method via reflection.
        */
       public void addCallback(Keyword word,
                               Object  instance,
                               Method  method)
       {
          word.setExtra(new Callback(instance, method));
          kwDict.addKeyword(word);
       }

       /**
        * Processes a dictionary lookup for a specified <code>Callback</code>.
        * If a match is found, the associated <code>Callback</code> object is
        * returned.  Otherwise <code>null</code> is returned.
        * <p>
        *
        * @param    funcName
        *           The function name to be used as a key in the lookup. If case-
        *           sensitivity is off, this may be in any case (including
        *           mixed case) and the result will always match.
        * @return   A <code>Callback</code> object or null if no match was found.
        */
       public Callback lookupCallback(String funcName)
       {
          Keyword word = kwDict.lookupKeyword(funcName);

          if (word == null)
          {
             // no possible matches
             return null;
          }

          return (Callback) word.getExtra();
       }

       /**
        * Lookup the callback definition associated with the function name and
        * invoke the target method using the passed arguments.
        * <p>
        * The lookup function will find the callback definition, if it exists.
        * <p>
        * Once obtained, the <code>Callback.getMethod</code> will be called. If
        * it returns a valid <code>Method</code> object then the caller-passed
        * arguments are *not* checked and this <code>Method</code> is used.  For
        * this reason function overloading cannot be used when the callback
        * definition has a hard-coded <code>Method</code> object and no class
        * and method names encoded.
        * <p>
        * If {@link Callback#getMethod} is called and the method is
        * <code>null</code>, then the reflection services are used to obtain a
        * <code>java.lang.reflect.Method</code> object based on the class name,
        * method name and the signature definition passed in as a parameter by
        * the caller.
        * <p>
        * Once the <code>Method</code> is obtained, reflection is used to invoke
        * that method and the arguments array is passed.
        * <p>
        * NO caching of the <code>Method</code> object is done at the time of
        * a lookup.  This allows one to code a callback definition using only
        * the class and method names and then to defer all signature processing
        * until this lookup method is called.  In this case, the signature
        * passed into the lookup is used to resolve the target method using
        * reflection and if the correct method exists it will be found.
        *
        * @param    funcName
        *           The function name to be used as a key in the lookup. If case-
        *           sensitivity is off, this may be in any case (including
        *           mixed case) and the result will always match.
        * @param    returnType
        *           The <code>Class</code> object representing the class of the
        *           method's return type.
        * @param    signature
        *           An array of <code>Class</code> objects representing the class
        *           of each parameter in addition to the sequence of the
        *           parameters.
        * @param    args
        *           An array of <code>Object</code> objects representing each
        *           parameter in the proper sequence expected by the target
        *           method.
        * @return   Result from the target method invocation.
        */
       public Object invokeCallback(String   funcName,
                                    Class    returnType,
                                    Class[]  signature,
                                    Object[] args)
       throws ReflectiveOperationException,
              NullPointerException
       {
          Callback cb = lookupCallback(funcName);

          if (cb == null)
          {
             String sig = ReflectionHelper.toStringSignature(funcName,
                                                             signature,
                                                             returnType);

             LOG.log(Level.SEVERE, "Missing function or variable " + sig);
             return null;
          }

          // honor any method instance that was explicitly provided by the caller
          Method method = cb.getMethod();

          if (method == null)
          {
             // attempt to find a match dynamically

             String ctxt = cb.getClassName();
             String mtxt = cb.getMethodName();

             Class cls = Class.forName(ctxt);

             // null or empty method name is a constructor
             if (mtxt == null || mtxt.length() == 0)
             {
                // lookup a constructor
                Constructor cmethods[] = cls.getConstructors();
                Constructor found      = null;

                // we must manually lookup the constructor since the J2SE 1.4.x
                // Class.getConstructor() doesn't check for assignability so
                // a signature based on child classes won't match a compatible
                // signature that uses super classes

                outer:
                for (int j = 0; j < cmethods.length; j++)
                {
                   Class[] possible = cmethods[j].getParameterTypes();

                   if (ReflectionHelper.testSignature(possible, signature))
                   {
                      found = cmethods[j];
                      break outer;
                   }
                }

                return found != null ? found.newInstance(args) : null;
             }
             else
             {
                // normal method
                Method methods[] = cls.getMethods();

                // we must manually lookup the constructor since the J2SE 1.4.x
                // Class.getMethod() doesn't check for assignability so
                // a signature based on child classes won't match a compatible
                // signature that uses super classes

                outer:
                for (int j = 0; j < methods.length; j++)
                {
                   if (methods[j].getName().equals(mtxt))
                   {
                      Class[] possible = methods[j].getParameterTypes();

                      if (ReflectionHelper.testSignature(possible, signature))
                      {
                         method = methods[j];
                         break outer;
                      }
                   }
                }

                if (method == null)
                {
                   String sig = ReflectionHelper.toStringSignature(mtxt,
                                                                   signature,
                                                                   returnType);

                   LOG.log(Level.SEVERE, "Method '" + sig + "' not found.");
                }
             }

             // DO NOT cache this for possible use later since this would
             // disable overloaded function support!!!
          }

          return method == null ? null : method.invoke(cb.getTargetInstance(), args);
       }

       /**
        * Prints a report to <code>stderr</code> which includes 1 line per
        * entry in the dictionary.  If a <code>Callback</code> object has an
        * associated <code>Keyword</code> that supports abbreviations, then
        * multiple entries will be found that refer to the same
        * <code>Keyword</code>.
        */
       public void dump()
       {
          StringOutputStream outputStream = new StringOutputStream();
          PrintStream ps = new PrintStream(outputStream);
          ps.println( "\n-------------Function Listing-------------" );
          kwDict.dump(ps);
          LOG.log(Level.INFO, outputStream.getData());
       }

       /**
        * The Lexer and Parser that processes an expression must be aware of the
        * same <code>Keyword</code> objects as this instance. This helper method
        * obtains the list of <code>Keyword</code> objects from the keyword
        * dictionary and adds each to the <code>SymbolResolver</code> to
        * properly populate it.
        *
        * @param   sym
        *          Object to populate with full list of <code>Keywords</code>.
        */
       public void populateSymbolResolver(SymbolResolver sym)
       {
          Iterator elements = kwDict.keywordList();

          while (elements.hasNext())
          {
             sym.addKeyword((Keyword) elements.next());
          }
       }
    }