# InternalEntry.java

    /*
    ** Module   : InternalEntry.java
    ** Abstract : stores metadata relating to an internal entry
    **
    ** Copyright (c) 2013-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 GES 20131124 Moved this code out of SourceNameMapper into its own class. This enables
    **                  cleaner separation and modularity. Added synchronization and made changes
    **                  for RETURN support.
    ** 002 ECF 20140918 Reduced memory footprint.
    ** 003 ECF 20150424 Minor memory tweak.
    ** 004 ECF 20150601 Minor performance fix.
    ** 005 OM  20150717 Fixed range check for parameters.
    ** 006 ECF 20150802 Removed synchronization, which was a performance bottleneck. Made constructors
    **                  protected to ensure instances are not created elsewhere, other than by
    **                  subclasses which presumably will be written with an understanding of thread
    **                  safety. See class description notes for explanation of why unsynchronized
    **                  methods are safe.
    ** 007 CA  20160418 Added getter for "parameters" field.
    ** 008 ECF 20180818 Avoid use of expensive String.format.
    ** 009 CA  20181029 Exposed setParameterList() and initialize() to sub-classes.
    ** 010 CA  20181216 Fixed loading OO methods, constructors and destructors.
    ** 011 CA  20190219 OO members (methods, constructors, class events) are annotated via the
    **                  LegacySignature and statically linked with the converted Java method.
    **     CA  20190324 Cache the libname and mapTo values.
    ** 012 CA  20190520 Added some more OO method types, for internal FWD usage.
    ** 013 CA  20190604 Fixed a typo for LENGTH-OF name.
    ** 014 ME  20200518 Added VARIABLE and PROPERTY.
    ** 015 CA  20200605 The Java method instance is resolved on first access of getMethod().
    ** 016 ME  20200810 Added qualified name support in signature processing.
    **     CA  20210218 Fixed signature processing for qualified return type.
    **     CA  20220909 Fixed TABLE-HANDLE, DATASET, DATASET-HANDLE parameters reported by GET-SIGNATURE.
    ** 017 HC  20230125 Added caching of resolved procedure internal entries.
    ** 018 CA  20230928 Small optimization in 'initialize', user plain Java for instead of 'for each'.
    ** 019 CA  20231213 The synthetic 'execute' methods emitted for legacy classes can be dropped if there is
    **                  nothing emitted in them.  These Java methods are also marked with Type.EXECUTE
    **                  LegacySignature annotation, and their BlockManager API removed - the FWD runtime will run
    **                  this only once.
    */

    /*
    ** 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 com.goldencode.p2j.util.SourceNameMapper.ExternalProgram;

    /**
     * Container for legacy info belonging to an internal entry (a user-defined internal procedure,
     * function or external API call).
     * <p>
     * The methods of this class are unsynchronized because the methods of this class are invoked
     * very frequently and testing and profiling indicates that a synchronized architecture creates
     * a bottleneck. Instances of this class are only (and MUST only) be constructed and initialized
     * from within synchronized code within {@link SourceNameMapper}. Although there are public
     * methods in this class which access internal state, they are only invoked after all internal
     * state has been set. Subclasses must be aware of this restriction to ensure they are thread
     * safe.
     */
    public class InternalEntry
    {
       /** Type of internal entry */
       public static enum Type
       {
          /** User defined function */
          FUNCTION("FUNCTION"),

          /** Internal procedure */
          PROCEDURE("PROCEDURE"),

          /** Object method */
          METHOD("METHOD"),

          /** Class event */
          EVENT("EVENT"),

          /** Event's subscribe method definition */
          SUBSCRIBE("SUBSCRIBE"),

          /** Event's unsubscribe method definition */
          UNSUBSCRIBE("UNSUBSCRIBE"),

          /** Event's publish method definition */
          PUBLISH("PUBLISH"),

          /** Property's getter method. */
          GETTER("GET"),

          /** Property's setter method. */
          SETTER("SET"),

          /** Extent property's resize method. */
          RESIZE("RESIZE"),

          /** Extent property's length-of method. */
          LENGTH("LENGTH-OF"),

          /** Class constructor */
          CONSTRUCTOR("CONSTRUCTOR"),

          /** Class destructor */
          DESTRUCTOR("DESTRUCTOR"),

          /** Native procedure */
          DLL_ENTRY("DLL-ENTRY"),

          /** User defined function defined in another procedure */
          EXTERN("EXTERN"),

          /** Main procedure */
          MAIN("MAIN"),

          /** Synthetic 'execute' methods emitted for legacy classes. */
          EXECUTE("EXECUTE"),

          /** Object variable */
          VARIABLE("VARIABLE"),

          /** Object property */
          PROPERTY("PROPERTY"),
          ;

          /** Map of Progress entry names to enum values */
          private static Map<String, Type> map = new HashMap<>(8);

          static
          {
             for (Type t : Type.values())
             {
                map.put(t.name, t);
             }
          }

          /** Progress entry name */
          private final String name;

          /**
           * Constructor.
           *
           * @param   name
           *          Progress entry name.
           */
          Type(String name)
          {
             this.name = name.intern();
          }

          /**
           * Given a Progress entry name, retrieve the appropriate enum value.
           *
           * @param   name
           *          Progress entry name.
           *
           * @return  Associated enum value.
           *
           * @throws  IllegalArgumentException
           *          if the given Progress entry name is unrecognized.
           */
          public static Type fromString(String name)
          {
             Type t = map.get(name);
             if (t != null)
             {
                return t;
             }

             throw new IllegalArgumentException("Unrecognized InternalEntry type: " + name);
          }

          /**
           * Return the Progress entry name.
           *
           * @return  Progress entry name.
           */
          public String toString()
          {
             return name;
          }
       };

       public static enum SerializeMode {
          DEFAULT,
          VISIBLE,
          HIDDEN
       }

       /** Identify parameters in INPUT mode. */
       public static final char INPUT_MODE = 'I';

       /** Identify parameters in INPUT-OUTPUT mode. */
       public static final char INPUT_OUTPUT_MODE = 'U';

       /** Identify parameters in OUTPUT mode. */
       public static final char OUTPUT_MODE = 'O';

       /** Identify buffer parameters. */
       public static final char BUFFER_MODE = 'B';

       /** Legacy 4GL name for this internal-entry. */
       final String pname;

       /** Converted Java name for this internal-entry. */
       final String jname;

       /** The type of this internal entry. */
       private final Type type;

       /**
        * The signature of this internal entry, as returned by the
        * <code>get-signature</code> 4GL API.
        */
       private String signature = null;

       /** The string representation of the parameter modes for this internal entry. */
       private String parameterModes = null;

       /** The parameter list of this internal entry. */
       private List<Parameter> parameters = Collections.emptyList();

       /** A map containing all the internal entry's attributes. */
       private Map<String, String> attributes = new HashMap<>(4);

       /**
        * Flag indicating if this is a IN SUPER internal entry (when set to <code>true</code>).
        * Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
        */
       private Boolean isSuper = null;

       /**
        * Flag indicating if this is a IN handle internal entry (when set to <code>true</code>).
        * Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
        */
       private Boolean inHandle = null;

       /**
        * Flag indicating if this is a PRIVATE internal entry (when set to <code>true</code>).
        * Gets its permanent value when internal entry is initialized by {@link SourceNameMapper}.
        */
       private Boolean isPrivate = null;

       /** The return type's extent. */
       private int extent = SourceNameMapper.NO_EXTENT;

       /** The qualified OO name. */
       private String qualified = null;

       /** The converted Java method from where this internal entry was built, using the
        * {@link LegacySignature} annotation.
        */
       private Method method = null;

       /** The OS library name, if this is a native method. */
       private String libname = null;

       /** The MAP-TO option, if it applies. */
       private String mapTo = null;

       /**
        * The external program containing this entry.  It is non-null only for external programs, until the
        * {@link #method} is resolved.
        */
       private ExternalProgram extprog;

       /** Represents null InternalEntry value */
       public static final InternalEntry NULL = new InternalEntry();

       /**
        * Private constructor for the internal implementation.
        */
       private InternalEntry()
       {
          pname = null;
          jname = null;
          type = null;
       }

       /**
        * Basic c'tor.
        *
        * @param    pname
        *           Legacy 4GL name for this internal-entry.
        * @param    jname
        *           Converted Java name for this internal-entry.
        * @param    type
        *           The type of this internal entry.
        */
       protected InternalEntry(String pname, String jname, String type)
       {
          this(pname, jname, Type.fromString(type));
       }

       /**
        * Basic c'tor.
        *
        * @param    pname
        *           Legacy 4GL name for this internal-entry.
        * @param    jname
        *           Converted Java name for this internal-entry.
        * @param    type
        *           The type of this internal entry.
        */
       protected InternalEntry(String pname, String jname, Type type)
       {
          this.pname = pname.intern();
          this.jname = jname.intern();
          this.type = type;
       }

       /**
        * Check if the given mode is an OUTPUT mode (i.e. {@link #OUTPUT_MODE} or
        * {@link #INPUT_OUTPUT_MODE}.
        *
        * @return   <code>true</code> if the given character represents one of the output
        *           modes.
        */
       public static boolean isOutputMode(char mode)
       {
          return mode == OUTPUT_MODE || mode == INPUT_OUTPUT_MODE;
       }

       /**
        * Get the OS library name, if it applies.
        *
        * @return   The {@link #libname}.
        */
       public String getLibname()
       {
          return libname;
       }

       /**
        * Get the MAP-TO option, if it applies.
        *
        * @return   The {@link #mapTo} option.
        */
       public String getMapTo()
       {
          return mapTo;
       }

       /**
        * Return <code>true</code> if this internal-entry has its <code>in-super</code> attribute set
        * to <code>"true"</code>.
        *
        * @return   See above.
        */
       public boolean isSuper()
       {
          return isSuper;
       }

       /**
        * Return <code>true</code> if this internal-entry has its
        * <code>private</code> attribute set to <code>"true"</code>.
        *
        * @return   See above.
        */
       public boolean isPrivate()
       {
          return isPrivate;
       }

       /**
        * Return <code>true</code> if internal-entry is a function (its {@link #type} is set to
        * {@link Type#FUNCTION}).
        *
        * @return   See above.
        */
       public boolean isFunction()
       {
          return type == Type.FUNCTION;
       }

       /**
        * Return <code>true</code> if this internal-entry has its
        * <code>in-handle</code> attribute set to <code>"true"</code>.
        *
        * @return   See above.
        */
       public boolean isInHandle()
       {
          return inHandle;
       }

       /**
        * Obtain the original name of the procedure being executed as specified in the 4GL
        * code.
        *
        * @return   The original (unconverted) name.
        */
       public String getLegacyName()
       {
          return pname;
       }

       /**
        * Get the qualified OO name (if it applies).
        *
        * @return   See above.
        */
       public String getQualified()
       {
          return qualified;
       }

       /**
        * Get the return's extent (if it applies).
        *
        * @return   See above.
        */
       public int getExtent()
       {
          return extent;
       }

       /**
        * Get the Java {@link #method}.
        *
        * @return   See above.
        */
       public Method getMethod()
       {
          if (method == null && extprog != null && extprog.ooname == null)
          {
             SourceNameMapper.buildLegacyProgram(extprog);

             // in case the Java method is not needed for this entry (is a in-handle or in-super), avoid multiple
             // lookups.
             extprog = null;
          }

          return method;
       }

       /**
        * Get the value of the given attribute.  If the attribute is null or
        * is not set, return <code>null</code>.
        *
        * @param    attr
        *           The attribute's name.
        *
        * @return   See above.
        */
       public String getAttribute(String attr)
       {
          if (attr == null)
          {
             return null;
          }

          attr = attr.toLowerCase();

          return attributes.get(attr);
       }

       /**
        * Set the value of the given attribute.
        *
        * @param    name
        *           The attribute's name.
        * @param    value
        *           The attribute's value.
        */
       public void putAttribute(String name, String value)
       {
          attributes.put(name.toLowerCase().intern(), value.intern());
       }

       /**
        * Gets the signature of this internal-entry, as returned by the <code>get-signature</code>
        * API of an persistent procedure. If this is the first access, it initializes the
        * {@link #signature} field.
        *
        * @return   The 4GL-compatible signature string.
        */
       public String getSignature()
       {
          return signature;
       }

       /**
        * Gets the details of the given parameter.
        *
        * @param    idx
        *           0-based parameter index.
        *
        * @return   The parameter instance or <code>null</code> if no such parameter exists.
        */
       public Parameter getParameter(int idx)
       {
          if (idx < 0 || idx >= parameters.size())
          {
             return null;
          }

          return parameters.get(idx);
       }

       /**
        * Returns the number of parameters for this internal entry.
        *
        * @return   The size of the parameter list.
        */
       public int getParameterListSize()
       {
          return parameters.size();
       }

       /**
        * Returns the list of parameters for this internal entry.
        *
        * @return   The parameter list.
        */
       public List<Parameter> getParameterList()
       {
          // this is safe because the list cannot be modified
          return parameters;
       }

       /**
        * Gets the string representation of the parameter modes for this internal-entry.
        *
        * @return   See above.
        */
       public String getParameterModes()
       {
          return parameterModes;
       }

       /**
        * Gets the parameters for this internal entry.
        *
        * @return   See above.
        */
       public List<Parameter> getParameters()
       {
          return new ArrayList<>(parameters);
       }

       /**
        * Set the target Java method for this internal entry to the specified one.
        *
        * @param    mthd
        *           The target method.
        */
       void setMethod(Method mthd)
       {
          this.method = mthd;
       }

       /**
        * Set the external program for this entry.
        *
        * @param    extprog
        *           The external program for this entry.
        */
       void setExternalProgram(ExternalProgram extprog)
       {
          this.extprog = extprog;
       }

       /**
        * Sets the list of parameters for this internal entry.  Changes to the input list will
        * not be reflected in the saved list.
        *
        * @param    parameters
        *           The parameter list.
        */
       protected void setParameterList(List<Parameter> parameters)
       {
          this.parameters = Collections.unmodifiableList(parameters);
       }

       /**
        * Initialize this object's internal state. This method must be called after all attributes and
        * parameters have been set, but before any public access to {@link #isSuper()} is possible.
        * This is done within synchronized code in {@link SourceNameMapper}, such that the creation
        * of this object and initialization of this state is atomic from a concurrency perspective.
        */
       protected void initialize()
       {
          isSuper = "true".equalsIgnoreCase(getAttribute("in-super"));
          isPrivate = "true".equalsIgnoreCase(getAttribute("private"));
          inHandle = "true".equalsIgnoreCase(getAttribute("in-handle"));

          String sextent = getAttribute("extent");
          if (sextent != null)
          {
             extent = Integer.parseInt(sextent);
          }
          qualified = getAttribute("qualified");

          libname = (type == InternalEntry.Type.DLL_ENTRY) ? getAttribute("libname") : null;

          mapTo = (type == Type.FUNCTION) ? getAttribute("map-to") : null;

          // must come after attributes
          signature = buildSignature().intern();

          if (!parameters.isEmpty())
          {
             StringBuilder sb = new StringBuilder();

             for (int i = 0; i < parameters.size(); i++)
             {
                Parameter p = parameters.get(i);

                String pmode = p.getMode();
                String type = p.getType();
                char mode = 0;

                if ("BUFFER".equalsIgnoreCase(type))
                {
                   mode = BUFFER_MODE;
                }
                else if ("INPUT".equalsIgnoreCase(pmode))
                {
                   mode = INPUT_MODE;
                }
                else if ("INPUT-OUTPUT".equalsIgnoreCase(pmode))
                {
                   mode = INPUT_OUTPUT_MODE;
                }
                else if ("OUTPUT".equalsIgnoreCase(pmode) || "RETURN".equalsIgnoreCase(pmode))
                {
                   // the resulting string is used for validation purposes, so we must convert
                   // RETURN parms to OUTPUT (although since we defer validation to
                   // NativeInvoker.invoke(), I'm not sure this is actually needed)
                   mode = OUTPUT_MODE;
                }
                else
                {
                   String err = "Malformed parameter mode " + pmode + " for parameter " + pname;
                   throw new IllegalStateException(err);
                }

                sb.append(mode);
             }

             parameterModes = sb.toString().intern();
          }
       }

       /**
        * Build the 4GL-style signature of this instance.
        *
        * @return   The internal-entry's signature.
        */
       private String buildSignature()
       {
          // The format is: TYPE,return type,param1,param2,param3 where:
          // - TYPE is either PROCEDURE, FUNCTION, EXTERN or DLL-ENTRY
          // - return type is the 4GL name of the return data type
          // - param# is one of:
          //     INPUT varname TYPE
          //     OUTPUT varname TYPE or INPUT-OUTPUT varname TYPE.
          //   Varname is either the original 4GL variable name or "arg#", if this
          //   is a header definition (i.e. an IN SUPER or IN handle) which had
          //   its parameter set as:
          //   "function f1 returns int (input int) in super."

          String type    = this.type.toString();
          String returns = getAttribute("returns");
          String qualified = getAttribute("qualified");

          if (qualified != null)
          {
             returns = qualified;
          }

          if (returns == null)
          {
             returns = "";
          }

          if (isSuper() || isInHandle())
          {
             returns = returns.toUpperCase();
          }
          else
          {
             returns = returns.toLowerCase();
          }

          StringBuilder sb = new StringBuilder();

          if (isInHandle())
          {
             type = Type.EXTERN.toString();
          }

          sb.append(type).append(",");
          sb.append(returns);

          if (parameters.size() == 0)
          {
             sb.append(",");
          }
          else
          {
             int idx = 0;

             for (Parameter p : parameters)
             {
                idx++;

                sb.append(",");

                // we can't just use the raw mode string, we must use the version that is meant
                // for the signature string since RETURN is reported by the 4gl as OUTPUT
                String pmode = p.getSignatureMode();

                if (pmode.length() > 0)
                {
                   sb.append(pmode).append(" ");
                }

                if ("BUFFER".equalsIgnoreCase(p.getType()))
                {
                   String buffer = p.getAttribute("buffer");
                   String forTable = p.getAttribute("for");

                   sb.append("BUFFER").append(" ");
                   sb.append(buffer).append(" ");
                   sb.append(forTable);
                }
                else if ("TABLE".equalsIgnoreCase(p.getType()))
                {
                   String table = p.getAttribute("table");
                   String append = p.getAttribute("append");

                   sb.append("TABLE").append(" ");
                   sb.append(table);

                   if ("true".equalsIgnoreCase(append))
                   {
                      sb.append(" ").append("APPEND");
                   }
                }
                else if ("HANDLE".equalsIgnoreCase(p.getType()) && p.getAttribute("TABLE") != null)
                {
                   String name = p.getAttribute("pname");

                   sb.append("TABLE-HANDLE").append(" ");
                   sb.append(name);
                }
                else if ("DATASET".equalsIgnoreCase(p.getType()))
                {
                   String dataset = p.getAttribute("dataset");

                   sb.append("DATASET").append(" ");
                   sb.append(dataset);
                }
                else if ("HANDLE".equalsIgnoreCase(p.getType()) && p.getAttribute("DATASET") != null)
                {
                   String name = p.getAttribute("pname");

                   sb.append("DATASET-HANDLE").append(" ");
                   sb.append(name);
                }
                else
                {
                   String pname = p.getLegacyName();

                   if (pname == null || pname.length() == 0)
                   {
                      pname = "arg" + idx;
                   }

                   sb.append(pname).append(" ");

                   if ("OBJECT".equalsIgnoreCase(p.getType()) || "JOBJECT".equals(p.getType()))
                   {
                      sb.append(p.getQualified().toUpperCase());
                   }
                   else
                   {
                      sb.append(p.getType());
                   }

                   // emit extent info
                   if (p.isExtent())
                   {
                      sb.append(" EXTENT");
                      if (p.isFixedExtent())
                      {
                         sb.append(" ").append(p.getExtent());
                      }
                   }
                }
             }
          }

          return sb.toString();
       }
    }