# FuzzyMethodRt.java

    /*
    ** Module   : FuzzyMethodRt.java
    ** Abstract : Implements runtime method resolution when there are multiple overloads which may match.
    **
    ** Copyright (c) 2023-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 CA  20230928 Initial version.
    ** 002 CA  20250319 A method from a super-class can have the same name as current class, so calls to this kind
    **                  of methods must not be resolved as a constructor for the current class.
    */

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

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

    import com.goldencode.p2j.oo.lang.*;
    import com.goldencode.p2j.persist.*;
    import com.goldencode.p2j.uast.*;
    import com.goldencode.p2j.util.logging.*;

    /**
     * Implements the rules to perform fuzzy method lookup, for runtime.
     */
    public class FuzzyMethodRt
    extends FuzzyMethodLookup
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(FuzzyMethodRt.class);

       /** The type where the search will start. */
       private final Class<? extends _BaseObject_> type;

       /** The overloads which will be used to calculate the {@link #candidates}. */
       private final List<InternalEntry> overloads;

       /** The candidates used to perform the lookup. */
       private List<MatchMetrics> candidates;

       /**
        * Initialize this instance.
        *
        * @param    type
        *           The type where the search will start.
        * @param    overloads
        *           The overloads which will be used to calculate the {@link #candidates}.
        */
       public FuzzyMethodRt(Class<? extends _BaseObject_> type, List<InternalEntry> overloads)
       {
          this.type = type;
          this.overloads = overloads;
       }

       /**
        * Perform a runtime fuzzy lookup of the given method, in the {@link #overloads} list.
        *
        * @param    thisType
        *           The type where the call is being performed (external program or legacy class).
        * @param    methodName
        *           The target method name.
        * @param    isStatic
        *           If <code>true</code>, only static methods will be looked up.
        *           Otherwise any method can be returned.
        * @param    modes
        *           The argument modes.
        * @param    args
        *           The arguments.
        *
        * @return   The resolved method (as its {@link InternalEntry}) or <code>null</code> if none found.
        */
       public InternalEntry lookup(Class<?> thisType,
                                   String   methodName,
                                   boolean  isStatic,
                                   String   modes,
                                   Object[] args)
       {
          boolean internal = thisType == type;
          int accessMode = calcAccessMode(thisType);
          ParameterKey[] sig = resolveSignature(args, modes);
          candidates = new ArrayList<>();
          for (InternalEntry entry : overloads)
          {
             candidates.add(new MatchMetrics(new InternalMethodData(entry)));
          }

          MethodSearchResult result = fuzzyMethodLookup(methodName, sig, accessMode, isStatic, internal, null,
                                                        false);

          InternalEntry entry = result == null || result.getData() == null
                                   ? null
                                   : ((InternalMethodData) result.getData()).getEntry();
          return entry;
       }

       /**
        * Get the class name where this method lookup starts from.
        *
        * @return   See above.
        */
       @Override
       protected String getClassName()
       {
          return ObjectOps.getLegacyName(type);
       }

       /**
        * Detect if the callee class is an instance of an implemented interface of the caller class.
        *
        * @param    caller
        *           The caller type.
        * @param    callee
        *           The callee type.
        *
        * @return   {@code true} if the given class is an implemented interface in this class.
        */
       @Override
       protected boolean isImplemented(String caller, String callee)
       {
          Class<?> src = parseObjectSpec(caller);
          Class<?> tgt = parseObjectSpec(callee);

          return isImplemented(src, tgt);
       }

       /**
        * Detect if the callee class is an instance of a parent class or an implemented interface of a parent
        * class, of the caller.
        *
        * @param    caller
        *           The caller type.
        * @param    callee
        *           The callee type to check.
        * @param    incrementer
        *           Code to be executed to increment the inheritance level.
        *
        * @return   {@code true} if the given class is a parent class or an implemented interface of a parent
        *           class.
        */
       @Override
       protected boolean isInheritedFrom(String caller, String callee, Runnable incrementer)
       {
          Class<?> src = parseObjectSpec(caller);
          Class<?> tgt = parseObjectSpec(callee);

          return isInheritedFrom(src, tgt, true, incrementer);
       }

       /**
        * Reports if the class name is Progress.Lang.ParameterList and the method name is {@code setParameter()}.
        *
        * @param    method
        *           The method name.
        *
        * @return   {@code true} if this is a setParameter call.
        */
       @Override
       protected boolean isSetParam(String method)
       {
          return ParameterList.class == type && "setParameter".equalsIgnoreCase(method);
       }

       /**
        * Get the list of all possible methods that could match in this class AND its parent classes. The
        * actual types are not checked but the method name, access mode, static/instance and number of
        * parameters must all match.
        *
        * @param    name
        *           Method name.
        * @param    num
        *           Number of parameters.
        * @param    access
        *           Access mode (<code>KW_PUBLIC</code>, <code>KW_PROTECTD</code> or <code>KW_PRIVATE</code>).
        * @param    isStatic
        *           If <code>true</code>, only static methods will be looked up. Otherwise any method can be
        *           returned.
        * @param    internal
        *           {@code true} if the lookup is internal to the current class definition.
        * @param    first
        *           {@code true} if the first match should be immediately returned as the result.
        * @param    exist
        *           The set of existing methods already represented in the list.
        * @param    constructor
        *           Flag indicating this must resolve a constructor.
        *
        * @return   List of possible methods that match.  This may be an empty list if no match exists.
        */
       @Override
       protected List<MatchMetrics> candidates(String            name,
                                               int               num,
                                               int               access,
                                               boolean           isStatic,
                                               boolean           internal,
                                               boolean           first,
                                               Set<SignatureKey> exist,
                                               boolean           constructor)
       {
          return candidates;
       }

       /**
        * Give the class for the context where this method is being invoked (i.e. THIS-PROCEDURE or THIS-OBJECT),
        * determine what access mode is allowed for the candidate methods.
        *
        * @param    thisType
        *           The type where this method call is being performed.
        *
        * @return   See above.
        */
       private int calcAccessMode(Class<?> thisType)
       {
          int access = ProgressParserTokenTypes.KW_PUBLIC;

          if (thisType != null)
          {
             if (thisType == type)
             {
                // inside a class def and explicitly referencing the current class, this is a private search
                access = ProgressParserTokenTypes.KW_PRIVATE;
             }
             else
             {
                if (thisType.isAssignableFrom(type))
                {
                   // this is explicit access to a parent class from within a subclass definition, this is a
                   // protected search
                   access = ProgressParserTokenTypes.KW_PROTECTD;
                }
             }
          }

          return access;

       }

       /**
        * Check if the source class implements the specified interface.
        *
        * @param    src
        *           The source type.
        * @param    tgt
        *           The lookup type.
        *
        * @return   See above.
        */
       private boolean isImplemented(Class<?> src, Class<?> tgt)
       {
          Set<Class<?>> all = new LinkedHashSet<>();
          Utils.collectInterfaces(src, all);

          return all.contains(tgt);
       }

       /**
        * Check if the source type implements or extends the target type, and calculate the distance from this
        * parent.
        *
        * @param    src
        *           The source type.
        * @param    tgt
        *           The target type.
        * @param    ifaces
        *           {@code true} if implemented interfaces should be checked.
        * @param    incrementer
        *           Code to be executed to increment the inheritance level.
        *
        * @return   See above.
        */
       private boolean isInheritedFrom(Class<?> src, Class<?> tgt, boolean ifaces, Runnable incrementer)
       {
          Class<?>[] parents = getParents(src);

          if (parents != null)
          {
             for (Class<?> parent : parents)
             {
                // stay within legacy code
                if (!_BaseObject_.class.isAssignableFrom(parent))
                {
                   continue;
                }

                if (tgt == parent || (ifaces && isImplemented(parent, tgt)))
                {
                   // track the current parent level
                   incrementer.run();

                   return true;
                }

                if (isInheritedFrom(parent, tgt, ifaces, incrementer))
                {
                   // track our level's contribution to the parent level
                   incrementer.run();

                   return true;
                }
             }
          }

          if (ifaces)
          {
             Class<?>[] interfaces = getInterfaces(src);

             if (interfaces != null)
             {
                for (Class<?> parent : interfaces)
                {
                   // stay within legacy code
                   if (!_BaseObject_.class.isAssignableFrom(parent))
                   {
                      continue;
                   }

                   if (tgt == parent || isImplemented(parent, tgt))
                   {
                      // track the current parent level
                      incrementer.run();

                      return true;
                   }
                }
             }
          }

          return false;
       }

       /**
        * Get the interfaces implemented by this type.  If the type is an interface, in 4GL terms the interfaces
        * are inherited, and not implemented, so an empty array is returned.
        *
        * @param    src
        *           The type.
        *
        * @return   The interfaces.
        */
       private Class<?>[] getInterfaces(Class<?> src)
       {
          if (src.isInterface())
          {
             return new Class<?>[0];
          }
          else
          {
             return src.getInterfaces();
          }
       }

       /**
        * Get the parents of the given type.  In OpenEdge terms, the parents of an interface are all inherited
        * interfaces, and for class, its direct super-class.
        *
        * @param    src
        *           The type.
        *
        * @return   The parent list.
        */
       private Class<?>[] getParents(Class<?> src)
       {
          if (src.isInterface())
          {
             return src.getInterfaces();
          }
          else
          {
             return new Class[] { src.getSuperclass() };
          }
       }

       /**
        * Parse the given object spec (in the form "object<? extends _CLS_>" or "jobject<? extends _CLS_>") and
        * return the type associated with _CLS_.
        *
        * @param    spec
        *           The specification to parse.
        *
        * @return   The type or {@code null} if the spec was malformed or if there is no such definition.
        */
       private Class<?> parseObjectSpec(String spec)
       {
          String sig = "object<? extends ";
          int    idx = spec.indexOf(sig);
          Class<?> cls = null;

          if (idx != -1)
          {
             String cname = spec.substring(idx + sig.length(), spec.length() - 1);

             cls = ObjectOps.resolveClass(cname);

             if (cls == null)
             {
                LOG.log(Level.SEVERE, String.format("No class found for %s (object spec %s)!", cname, spec));
             }
          }
          else
          {
             LOG.log(Level.SEVERE, String.format("Malformed object spec %s!", spec));
          }

          return cls;
       }

       /**
        * Given a list of arguments and their modes, resolve the signature in conversion-compatible mode.
        *
        * @param    args
        *           The arguments.
        * @param    modes
        *           The modes.
        *
        * @return   See above.
        */
       private ParameterKey[] resolveSignature(Object[] args, String modes)
       {
          ParameterKey[] sig = new ParameterKey[args.length];

          for (int i = 0; i < args.length; i++)
          {
             Object arg = args[i];
             Integer mode = null;
             String type = null;

             if (modes != null)
             {
                switch (modes.charAt(i))
                {
                   case 'I':
                      mode = ProgressParserTokenTypes.KW_INPUT;
                      break;
                   case 'O':
                      mode = ProgressParserTokenTypes.KW_OUTPUT;
                      break;
                   case 'U':
                      mode = ProgressParserTokenTypes.KW_IN_OUT;
                      break;
                }
             }

             Class<?> argType = ControlFlowOps.calculateType(arg);

             if (argType == null)
             {
                // TODO: what?
             }
             // TODO: OO types like InputOutputTableParameter, etc
             else if (arg instanceof TableParameter)
             {
                type = "TEMP-TABLE"; // TODO: signature
             }
             else if (arg instanceof DataSetParameter)
             {
                type = "DATASET"; // TODO: signature
             }
             else if (arg instanceof DataModelObject)
             {
                type = "BUFFER"; // TODO: signature
             }
             else if (BaseDataType.class.isAssignableFrom(argType))
             {
                if (mode == null)
                {
                   mode = ProgressParserTokenTypes.KW_INPUT;
                }

                type = arg.getClass().getSimpleName();
                boolean isArray = arg.getClass().isArray();
                int sz = isArray ? Array.getLength(arg) : 0;

                if (argType == object.class || argType == jobject.class)
                {
                   String qname = "progress.lang.object";
                   if (isArray)
                   {
                      if (sz != 0)
                      {
                         Object v = Array.get(arg, 0);
                         if (v instanceof object)
                         {
                            Class<? extends _BaseObject_> vtype = ((object) v).type();
                            if (vtype != null)
                            {
                               qname = ObjectOps.getLegacyName(vtype);
                            }
                         }
                         else if (v instanceof jobject)
                         {
                            Class<?> vtype = ((jobject) v).type();
                            if (vtype != null)
                            {
                               qname = vtype.toString();
                            }
                         }
                      }
                   }
                   else
                   {
                      if (arg instanceof object)
                      {
                         Class<? extends _BaseObject_> vtype = ((object) arg).type();
                         if (vtype != null)
                         {
                            qname = ObjectOps.getLegacyName(vtype);
                         }
                      }
                      else if (arg instanceof jobject)
                      {
                         Class<?> vtype = ((jobject) arg).type();
                         if (vtype != null)
                         {
                            qname = vtype.toString();
                         }
                      }
                   }

                   type = type + "<? extends " + qname + ">";
                }

                if (isArray)
                {
                   type = type + "[" + (sz == 0 ? "" : sz) + "]";
                }
             }

             sig[i] = new ParameterKey(mode, type);
          }

          return sig;
       }

       /**
        * The state used by {@link MatchMetrics} to calculate the match.
        */
       private static class InternalMethodData
       implements MethodData
       {
          /** The entry candidate. */
          private final InternalEntry entry;

          /** The conversion-compatible calculated signature. */
          private final ParameterKey[] signature;

          /**
           * Create a new method data for the given entry.
           *
           * @param    entry
           *           The method entry.
           */
          public InternalMethodData(InternalEntry entry)
          {
             this.entry = entry;

             Method method = entry.getMethod();
             this.signature = new ParameterKey[method.getParameterCount()];

             LegacySignature ls = method.getAnnotation(LegacySignature.class);
             for (int i = 0; i < signature.length; i++)
             {
                LegacyParameter lp = ls.parameters()[i];
                this.signature[i] = new ParameterKey(lp);
             }
          }

          /**
           * Get the {@link #entry method}.
           *
           * @return   See above.
           */
          public InternalEntry getEntry()
          {
             return entry;
          }

          /**
           * Get the signature of this method.
           *
           * @return   See above.
           */
          @Override
          public ParameterKey[] getSignature()
          {
             return signature;
          }

          /**
           * Get the signature for the parameter at the specified 0-based index.
           *
           * @param    idx
           *           The 0-based index for the parameter.
           *
           * @return   See above.
           */
          @Override
          public ParameterKey getSignature(int idx)
          {
             return signature[idx];
          }
       }
    }