# HandleOps.java

    /*
    +** Module   : HandleOps.java
    ** Abstract : Defines APIs to implement handle-specific operations.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description-----------------------------------------
    ** 001 CA  20130112 Created initial version.
    ** 002 CA  20130215 Added support for DELETE WIDGET. All deletes are done at the resource, as they
    **                  need to implement Deletable.
    ** 003 CA  20130221 getType was renamed to getResourceType.
    ** 004 GES 20130302 Added CAN-QUERY and CAN-SET builtin function support.
    ** 005 CA  20130529 Removed explicit handling of procedure deletion, now all is done in a generic
    **                  approach.
    ** 006 CA  20130918 Resource cleanup must be done only if the resource deleted.
    ** 007 EVL 20130911 Adding implementation for CAN-QUERY/CAN-SET functions.
    ** 008 EVL 20131009 Fix for getting the attribute getters and setters.
    ** 009 EVL 20131014 Add scan the static methods to CAN-QUERY/CAN-SET functions for possible
    **                  ignored attributes.
    ** 010 CA  20140218 Error messages use the program name as it was used at the RUN call.
    ** 011 CA  20140223 Fixed deletion of persistent procedures via DELETE OBJECT <system-proc-handle>.
    ** 012 CA  20140526 handle unwrap APIs were switched to instance method calls.
    ** 013 CA  20150122 Added isSystemHandle API.
    ** 014 CA  20151222 Added logging to invalidDelete(handle).
    **     OM  20160128 Added 4140 error condition for deleting DEFAULT-WINDOW.
    ** 015 SBI 20160608 Added CLIPBOARD handle to isSystemHandle.
    ** 016 CA  20180510 Added PROFILER system handle support.
    ** 017 CA  20181029 Added LIST-SET-ATTRS() and LIST-QUERY-ATTRS() builtin functions.
    **                  Changes related to CALL's GET-ATTR-CALL-TYPE and SET-ATTR-CALL-TYPE modes.
    **                  Fixed CAN-QUERY, which can check for 4GL methods, too.
    ** 018 CA  20181204 Added DEBUGGER handle support.
    ** 019 IAS 20190501 Added SECURITY-POLICY support.
    **         20190506 Added AUDIT-POLICY, AUDIT CONTROL support.
    ** 020 CA  20190720 If a handle is registered as a OUTPUT argument, and the resource is a DATASET
    **                  or TABLE, postpone the attempted delete until the copy is performed.  This
    **                  tries to emulate the BY-REFERENCE behavior in 4GL, and this solution is not
    **                  necessarily correct.
    ** 021 CA  20200110 Fixed delete of BY-REFERENCE DATASET parameters.
    **     HC  20200301 Added LegacyAttribute.canQuery to allow to override the result of CAN-QUERY
    **                  built in legacy function.
    ** 022 AIL 20200623 Added support for resources which can be deleted multiple times.
    **     CA  20200918 Optimized the performance for CAN-SET() and CAN-QUERY() legacy functions.
    **     VVT 20210115 Bugs fixed for canQuery and canSet. See the #5064 for discussion.
    **     CA  20210507 Fixed a bug in executing a CALL for a static proxy resource (like SESSION).
    **     CA  20210527 Added RCODE-INFO as a system handle.
    **     RFB 20210713 Add the stub methods for LIST-EVENTS, listEvents. Ref #5536.
    **     CA  20210917 Javadoc fixes.
    **     CA  20210928 Only dynamic by-reference dataset parameters fail delete with error 12327.
    **     CA  20211025 When checking if a handle supports the attribute or method passed to CAN-QUERY or CAN-SET,
    **                  we need to walk the class hierarchy and keep the state of the 'ignore' flag from the
    **                  closest definition which has the annotation for this attribute/method.
    **     CA  20220707 Any PSEUDO-WIDGET resource must return true in 'isSystemHandle' - this is how 4GL behaves
    **                  and solves a performance problem with 'isSystemHandle'.
    **     VVT 20221003 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
    **                  with namesakes in DMO. See #6694.
    **     CA  20220613 Do not allow a DELETE of a BUFFER attached to a DATASET or TEMP-TABLE handle which is an
    **                  OUTPUT parameter.
    **     VVT 20220913 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
    **                  with namesakes in DMO. See #6694.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 023 CA  20230302 When deleting a temp-table, the delete can be vetoed if any buffer for the temp-table is
    **                  associated with an OUTPUT dataset.  Refactored to move the resource-specific code in a
    **                  'allowDelete' method.
    ** 024 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
    ** 025 CA  20230817 HandleOps.delete executing for a DELETE WIDGET-POOL is not considered an explicit DELETE
    **                  OBJECT (which would prevent deleting a temp-table/buffer part of a dataset).
    ** 026 AI  20231114 Updated delete to remove the childs of the widget.
    **         20231416 Change if condition to use instanceof instead of isAssignableFrom.
    ** 027 CA  20231129 Added 'PseudoWidget' marker to system handle resources.
    ** 028 EVL 20241004 Adding SCREEN-VALUE attribute to check for possible invalid for chained widget with call
    **                  invalidChainAttribute().  For widgets: WINDOW, FRAME and FIELD-GROUP we can not get this
    **                  attribute with CAN-QUERY converted call.
    ** 029 ICP 20250129 Used logical constants to leverage caches instances.
    ** 030 EVL 20250318 Fix for abbreviated attribute names resolution to proper getter or setter in canQuery()
    **                  method.  We have to use full name while getting annotation.
    */
    /*
    ** 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 com.goldencode.p2j.uast.*;
    import com.goldencode.p2j.ui.*;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.proxy.*;

    import java.util.*;
    import java.util.concurrent.*;
    import java.util.function.*;
    import java.util.logging.*;

    import java.lang.annotation.*;
    import java.lang.reflect.*;

    /**
     * Provides implementation for these handle-specific operations:
     * <ul>
     *    <li>DELETE OBJECT</li>
     *    <li>DELETE PROCEDURE</li>
     *    <li>CAN-QUERY() built-in function</li>
     *    <li>CAN-SET() built-in function</li>
     * </ul>
     *
     * Current implementation provides support only for SESSION, FILE-INFO,
     * LAST-EVENT, ERROR-STATUS and procedure handles.
     */
    public class HandleOps
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(HandleOps.class.getName());

       /**
        * A map holding the string-representation of all queryable attributes and methods, per each
        * resource class.
        */
       private static final Map<Class<?>, String> QUERY_ATTRIBUTES = new ConcurrentHashMap<>();

       /**
        * A map holding the string-representation of all settable attributes, per each resource class.
        */
       private static final Map<Class<?>, String> SET_ATTRIBUTES = new ConcurrentHashMap<>();

       /** Mapping of the 4GL attributes to their Java setter method names. */
       private static final Map<String, String> SETTERS_TO_JAVA = new ConcurrentHashMap<>();

       /** Mapping of the 4GL attributes to their Java getter method names. */
       private static final Map<String, String> GETTERS_TO_JAVA = new ConcurrentHashMap<>();

       /** Mapping of the 4GL method to their Java method names. */
       private static final Map<String, String> METHODS_TO_JAVA = new ConcurrentHashMap<>();

       /**
        * A map holding the set of all gettable attribute names, per each resource class.
        */
       private static final Map<String, Set<String>> LEGACY_GETTERS = new HashMap<>();

       /**
        * A map holding the set of all settable attribute names, per each resource class.
        */
       private static final Map<String, Set<String>> LEGACY_SETTERS = new HashMap<>();

       /**
        * A map holding the set of all method names, per each resource class.
        */
       private static final Map<String, Set<String>> LEGACY_METHODS = new HashMap<>();

       /**
        * Check if the given resource represents a system handle.  The procedure system handles
        * (THIS-PROCEDURE, SOURCE-PROCEDURE, TARGET-PROCEDURE) and the window system handles
        * (DEFAULT-WINDOW, ACTIVE-WINDOW and CURRENT-wINDOW) are not considered system handles by
        * this API.
        *
        * @param     res
        *            The resource to check.
        *
        * @return    <code>true</code> if the resource represents a system handle as defined above.
        */
       public static boolean isSystemHandle(WrappedResource res)
       {
          return res.valid() && isSystemHandle(new handle(res));
       }

       /**
        * Get the list of valid events for the specified resource.
        * <p>
        * The order in which these are returned by 4GL has no meaning; in FWD, these are returned
        * sorted alphabetically.
        *
        * @param    h
        *           The handle referencing the resource.
        *
        * @return   See above.
        */
       public static character listEvents(handle h)
       {
          UnimplementedFeature.missing("LIST-EVENTS");
          return new character();
       }

       /**
        * Get the list of valid events for the specified resource.
        * <p>
        * The order in which these are returned by 4GL has no meaning; in FWD, these are returned
        * sorted alphabetically.
        *
        * @param    h
        *           The handle referencing the resource.
        *
        * @param    platform
        *           The display type (GUI or TTY are valid).
        *
        * @return   See above.
        */
       public static character listEvents(handle h, String platform)
       {
          UnimplementedFeature.missing("LIST-EVENTS");
          return new character();
       }

       /**
        * Get the list of valid events for the specified resource.
        * <p>
        * The order in which these are returned by 4GL has no meaning; in FWD, these are returned
        * sorted alphabetically.
        *
        * @param    h
        *           The handle referencing the resource.
        *
        * @param    platform
        *           The display type (GUI or TTY are valid).
        *
        * @return   See above.
        */
       public static character listEvents(handle h, character platform)
       {
          UnimplementedFeature.missing("LIST-EVENTS");
          return new character();
       }

       /**
        * Get the list of queryable attributes and methods for the specified resource.
        * <p>
        * The order in which these are returned by 4GL has no meaning; in FWD, these are returned
        * sorted alphabetically.
        *
        * @param    h
        *           The handle referencing the resource.
        *
        * @return   See above.
        */
       public static character listQueryAttributes(handle h)
       {
          if (!h._isValid())
          {
             return new character();
          }

          WrappedResource res = h.getResource();
          String qryAttrs = QUERY_ATTRIBUTES.get(res.getClass());

          if (qryAttrs != null)
          {
             return new character(qryAttrs);
          }

          Function<Method, String> legacy = (m ->
          {
             // legacy attribute setter methods are not excluded here, as this API is used as a
             // helper to populate the cache (attribute to java method) method/getter/setter maps.
             // note that the attribute name will not be reported twice.

             final LegacyAttribute la = m.getAnnotation(LegacyAttribute.class);
             if (la != null && !la.ignore())
             {
                return la.name();
             }

             final LegacyMethod lm = m.getAnnotation(LegacyMethod.class);

             return lm != null && !lm.ignore() ? lm.name() : null;
          });

          qryAttrs = listLegacyResource(res, legacy, true);

          QUERY_ATTRIBUTES.put(res.getClass(), qryAttrs);

          return new character(qryAttrs);
       }

       /**
        * Get the list of settable attributes for the specified resource.
        * <p>
        * The order in which these are returned by 4GL has no meaning; in FWD, these are returned
        * sorted alphabetically.
        *
        * @param    h
        *           The handle referencing the resource.
        *
        * @return   See above.
        */
       public static character listSetAttributes(handle h)
       {
          if (!h._isValid())
          {
             return new character();
          }

          WrappedResource res = h.getResource();
          String setAttrs = SET_ATTRIBUTES.get(res.getClass());

          if (setAttrs != null)
          {
             return new character(setAttrs);
          }

          Function<Method, String> legacy = (m ->
          {
             LegacyAttribute la = m.getAnnotation(LegacyAttribute.class);

             return (la != null && la.setter() && !la.ignore() ? la.name() : null);
          });

          setAttrs = listLegacyResource(res, legacy, false);

          SET_ATTRIBUTES.put(res.getClass(), setAttrs);

          return new character(setAttrs);
       }

       /**
        * Check if the given resource represents a system handle.  The procedure system handles
        * (THIS-PROCEDURE, SOURCE-PROCEDURE, TARGET-PROCEDURE) and the window system handles
        * (DEFAULT-WINDOW, ACTIVE-WINDOW and CURRENT-wINDOW) are not considered system handles by
        * this API.
        *
        * @param     h
        *            The handle to check.
        *
        * @return    <code>true</code> if the handle references a system handle.
        */
       public static boolean isSystemHandle(handle h)
       {
          return h._isValid() && h.get() instanceof PseudoWidget;
       }

       /**
        * Deletes the given handles. If multiple handles refer the same resource, then the resource is
        * deleted only once (the first time is encountered).
        * <p>
        * In cases when the handle is a system handle, an {@link #invalidDelete()}
        * is called (to raise an error).
        *
        * @param    handles
        *           The list of handles to be deleted.
        */
       public static void delete(handle... handles)
       {
          Set<Object> unique = new HashSet<>();
          for (handle h : handles)
          {
             if (h == null)
             {
                invalidDelete();
                break;
             }

             Object referent = h.get();
             if (unique.contains(referent))
                continue;

             unique.add(referent);

             if (!h._isValid())
             {
                // this is shown for all invalid handles.
                String msg = "Invalid handle.  Not initialized or points to a deleted object";
                ErrorManager.displayError(3135, msg);
                continue;
             }

             // DELETE WIDGET can't be protected with no error, so if something fails here, the error
             // will propagate
             delete(h);
          }
       }

       /**
        * Deletes the given handle. The current implementation allows only procedure handles to be
        * deleted.
        * <p>
        * In cases when the handle is a system handle, an {@link #invalidDelete()} is called (to raise
        * an error).
        *
        * @param   h
        *          The handle to be deleted.
        */
       public static void delete(handle h)
       {
          delete(h, true);
       }

       /**
        * Deletes the given handle. The current implementation allows only procedure handles to be
        * deleted.
        * <p>
        * In cases when the handle is a system handle, an {@link #invalidDelete()} is called (to raise
        * an error).
        *
        * @param   h
        *          The handle to be deleted.
        */
       public static void delete(handle h, boolean explicit)
       {
          if (h != null && h._isValid())
          {
             Object referent = h.get();
             if (referent instanceof CommonHandleTree)
             {
                handle child = h.unwrapTree().firstChild();
                while (child._isValid())
                {
                   BaseConfig config = (BaseConfig) ((GenericWidget) child.get()).config();
                   handle next = null;
                   if (child.get() instanceof CommonHandleTree)
                   {
                      next = child.unwrap().getNextSibling();
                   }

                   if (config.dynamic)
                   {
                      delete(child);
                   }

                   if (next != null)
                   {
                      child = next;
                   }
                   else
                   {
                      break;
                   }
                }
             }

             if (isSystemHandle(h) ||
                 (ProcedureManager.systemHandle(referent)) &&
                  !ProcedureManager._isPersistent(referent))
             {
                // persistent system handle procedure can be deleted!
                invalidDelete(h);
                return;
             }

             WrappedResource resource = h.getResource();
             if (resource instanceof WindowWidget &&
                 resource == LogicalTerminal.defaultWindow().getResource())
             {
                // the default window is automatically created at startup and it cannot be destroyed.
                // Only in GUI mode, other WINDOWS can created and destroyed.
                ErrorManager.recordOrShowError(4140, "Cannot DESTROY default window", false, false);
                return;
             }

             if (resource instanceof Deletable)
             {
                if (((Deletable) resource).allowDelete(h, explicit))
                {
                   ((Deletable) resource).delete();
                }

                // do not perform any cleanup here. delete() doesn't ensure the resource was deleted
                // (an error might be raised, with NO-ERROR clause present), so cleanup must be done
                // either in HandleChain.delete (if the resource extends HandleChain) or in resource's
                // delete implementation, if the resource can be deleted
                return;
             }

             throw new RuntimeException(
                "Delete implementation not available for handle " +
                h.unwrapType().resourceType().toStringMessage());
          }

          if (h != null && !h._isValid())
          {
             WrappedResource resource = h.getResource();
             if (resource != null && resource instanceof MultiDeletable)
             {
                if (((MultiDeletable) resource).handleMultipleDeletes())
                   return;
             }
          }
          invalidDelete(h);
       }

       /**
        * Reports if the given attribute or method can be read from the given handle. The 4GL
        * documentation suggests this can only work on handles that are widgets, but this can be
        * called on any handle.  This corresponds to the <code>CAN-QUERY()</code> built-in function.
        *
        * @param    h
        *           The handle to be tested.
        * @param    attr
        *           The name of the attribute to be checked.
        *
        * @return   <code>true</code> if the resource in this handle supports reading the named
        *           attribute or method, <code>false</code> if the resource is valid but doesn't
        *           support the named attribute and <code>unknown</code> if either input is
        *           <code>unknown</code>.
        */
       public static logical canQuery(handle h, character attr)
       {
          if (attr == null || attr.isUnknown())
          {
             return new logical(false);
          }

          return canQuery(h, attr.toStringMessage());
       }

       /**
        * Reports if the given attribute or method can be read from the given handle. The 4GL
        * documentation suggests this can only work on handles that are widgets, but this can be
        * called on any handle.  This corresponds to the <code>CAN-QUERY()</code> built-in function.
        *
        * @param    h
        *           The handle to be tested.
        * @param    attributeOrMethodName
        *           The name of the attribute or method to be checked.
        *
        * @return   <code>true</code> if the resource in this handle supports reading the named
        *           attribute or method, <code>false</code> if the resource is valid but doesn't
        *           support the named attribute and <code>unknown</code> if either input is
        *           <code>unknown</code>.
        */
       public static logical canQuery(final handle h, final String attributeOrMethodName)
       {
          if (h == null || h.isUnknown())
          {
             return logical.UNKNOWN;
          }

          final Object res = h.get();
          if (res == null)
          {
             return logical.FALSE;
          }

          final String upcased = ProgressAst._keyword(attributeOrMethodName).toUpperCase();

          if (isHandleAttribute(upcased))
          {
             return logical.TRUE;
          }

          if (res instanceof HandleChain && invalidChainAttribute((HandleChain) res, upcased, true))
          {
             return logical.FALSE;
          }

          // check getters first
          boolean canq = isLegacyResourceOperationAllowed(res, upcased, LEGACY_GETTERS, m ->
          {
             LegacyAttribute annotation = m.getAnnotation(LegacyAttribute.class);
             return annotation != null && annotation.setter() ? null : annotation;
          });

          if (!canq)
          {
             // getter not found, check methods
             canq = isLegacyResourceOperationAllowed(res,
                                                     upcased,
                                                     LEGACY_METHODS,
                                                     m -> m.getAnnotation(LegacyMethod.class));
          }

          return logical.of(canq);
       }

       /**
        * Reports if the given attribute can be written to the given handle. The 4GL documentation
        * suggests this can only work on handles that are widgets, but this can be called on any
        * handle.  This corresponds to the <code>CAN-SET()</code> built-in function.
        *
        * @param    h
        *           The handle to be tested.
        * @param    attr
        *           The name of the attribute to be checked.
        *
        * @return   <code>true</code> if the resource in this handle supports writing the named
        *           attribute, <code>false</code> if the resource is valid but doesn't support
        *           the named attribute and <code>unknown</code> if either input is
        *           <code>unknown</code>.
        */
       public static logical canSet(handle h, character attr)
       {
          if (attr == null || attr.isUnknown())
          {
             return new logical(false);
          }

          return canSet(h, attr.toStringMessage());
       }

       /**
        * Reports if the given attribute can be written to the given handle. The 4GL documentation
        * suggests this can only work on handles that are widgets, but this can be called on any
        * handle.  This corresponds to the <code>CAN-SET()</code> built-in function.
        *
        * @param    h
        *           The handle to be tested.
        * @param    attributeName
        *           The name of the attribute to be checked.
        *
        * @return   <code>true</code> if the resource in this handle supports writing the named
        *           attribute, <code>false</code> if the resource is valid but doesn't support
        *           the named attribute and <code>unknown</code> if either input is
        *           <code>unknown</code>.
        */
       public static logical canSet(final handle h, final String attributeName)
       {
          if (h == null || h.isUnknown())
          {
             return new logical();
          }

          final String attributeUpcased = attributeName.toUpperCase();
          if (isHandleAttribute(attributeUpcased))
          {
             return new logical(false);
          }

          final Object res = h.get();
          if (res instanceof HandleChain
                   && invalidChainAttribute((HandleChain) res, attributeUpcased, false))
          {
             return new logical(false);
          }

          return new logical(isLegacyResourceOperationAllowed(res, attributeUpcased, LEGACY_SETTERS, m -> {
             final LegacyAttribute annotation = m.getAnnotation(LegacyAttribute.class);
             return annotation != null && annotation.setter() ? annotation : null;
          }));
       }

       /**
        * Find if the legacy attribute or method can be used with the resource referenced by the
        * given handle.
        * <p>
        * This will use the global {@link #SETTERS_TO_JAVA}, {@link #GETTERS_TO_JAVA} and
        * {@link #METHODS_TO_JAVA} maps - if the cached values do not exist for the given resource
        * type, this will be initialized via a {@link #listQueryAttributes} call.
        * @param    h
        *           The resource.
        * @param    attrOrMethod
        *           The attribute or method name.
        * @param    attr
        *           Flag indicating if looking for attributes only.
        * @param    setter
        *           Flag indicating if looking for attribute setters only.
        */
       static String findLegacy(handle h, String attrOrMethod, boolean attr, boolean setter)
       {
          String upcased = attrOrMethod.toUpperCase();
          Map<String, String> jnames = attr ? (setter ? SETTERS_TO_JAVA : GETTERS_TO_JAVA)
                                            : METHODS_TO_JAVA;
          String mthd = jnames.get(upcased);

          if (mthd == null)
          {
             // one more try, in case this resource wasn't initialized yet

             WrappedResource res = h.getResource();
             if (!QUERY_ATTRIBUTES.containsKey(res.getClass()))
             {
                listQueryAttributes(h);
             }

             mthd = jnames.get(upcased);

             // if is null here, it can't be found
          }

          return mthd;
       }

       /**
        * Checks if the given attribute name is HANDLE.  This is used to bypass reflection based code
        * to find out if the attribute can be set or queried.  We know this is read-only and always
        * exists for valid handle we use in CAN-SET and CAN-QUERY calls.
        *
        * @param    attrName
        *           The name of the attribute to be checked.
        *
        * @return   <code>true</code> if the attribute name is HANDLE, <code>false</code> otherwise.
        */
       private static boolean isHandleAttribute(String attrName)
       {
          return attrName.equalsIgnoreCase("HANDLE");
       }

       /**
        * Invoked when the handle is invalid or a system (non-procedure) handle.
        * Shows appropriate error messages and raises an error.
        * Calls {@link #invalidDelete(handle) invalidDelete(null)}.
        */
       private static void invalidDelete()
       {
          invalidDelete(null);
       }

       /**
        * Invoked when the handle is invalid or a system (non-procedure) handle.
        * Shows appropriate error messages and raises an error.
        *
        * @param    h
        *           The handle for which the delete failed. May be
        *           <code>null</code> if it was decide this is a non-procedure
        *           handle.
        */
       private static void invalidDelete(handle h)
       {
          boolean procedure = (h != null && h._isValid() && ProcedureManager.isProcedure(h));

          final String msg1 = "Invalid or inappropriate handle value given to " +
                              "DELETE OBJECT or DELETE PROCEDURE statement";
          final int id1 = 5425;

          final String msg2 = "Invalid or inappropriate handle value given to " +
                              "DELETE PROCEDURE statement. Procedure '%s':%d";
          final int id2 = 1428;
          // this can't be computed, is the legacy line num from the 4GL file.
          final int linenum = 0;

          int id = -1;
          String msg = null;

          if (procedure)
          {
             String pname = ProcedureManager.getRelativeName(h.get());
             msg = String.format(msg2, pname, linenum);
             id = id2;
          }
          else
          {
             msg = msg1;
             id = id1;
          }

          if (LOG.isLoggable(Level.FINE))
          {
             String emsg = (h == null ? "with null handle"
                                      : (h.get() == null ? "with null resource"
                                                         : "with resource " + h.get().getClass()));
             String noError = ErrorManager.isSilentError() ? "(with NO-ERROR) " : "";
             emsg = msg + ". (" + id + ") - " + noError + emsg;

             LOG.log(Level.FINE, emsg, new ErrorConditionException(emsg));
          }

          ErrorManager.recordOrThrowError(id, msg, false);
       }

       /**
        * Get a listing of the specified resource, of all its attributes/methods (in 'query' mode)
        * or all its setable attributes (in non-query mode).
        *
        * @param    res
        *           The resource for which to resolve the attributes/methods.
        * @param    resolver
        *           Function which checks if a method can be used by this call.
        * @param    query
        *           Flag indicating if we are looking for queryable attributes/methods (when true) or
        *           only settable attributes (when false).
        *
        * @return   The alphabetically ordered list of attributes and methods.
        */
       private static String listLegacyResource(Object                   res,
                                                Function<Method, String> resolver,
                                                boolean                  query)
       {
          Class<?> cls = res.getClass();
          List<Method> methods = listAllMethods(cls);
          methods.addAll(StaticProxy.getStaticMethods(res));

          boolean isHandleChain = (res instanceof HandleChain);
          HandleChain chain = (isHandleChain ? (HandleChain) res : null);
          Set<String> names = new TreeSet<>();
          for (Method mthd : methods)
          {
             String name = resolver.apply(mthd);
             if (name != null)
             {
                Map<String, String> jnames;

                if (mthd.isAnnotationPresent(LegacyAttribute.class))
                {
                   LegacyAttribute lattr = mthd.getAnnotation(LegacyAttribute.class);

                   if (isHandleChain && invalidChainAttribute(chain, name, query))
                   {
                      continue;
                   }

                   jnames = lattr.setter() ? SETTERS_TO_JAVA : GETTERS_TO_JAVA;
                }
                else
                {
                   jnames = METHODS_TO_JAVA;
                }

                String jname = jnames.get(name);
                if (jname != null && !jname.equals(mthd.getName()))
                {
                   LOG.warning("An attribute/method is defined with more than one Java method name: " +
                               jname + " " + mthd);
                }

                jnames.put(name, mthd.getName());

                names.add(name);
             }
          }

          if (query)
          {
             names.add("HANDLE");
          }

          String q = "";
          for (String name : names)
          {
             q = q + (q.isEmpty() ? "" : ",") + name;
          }

          return q;
       }

       /**
        * List all methods declared in this class and all its super-classes and interfaces.
        *
        * @param    cls
        *           The class which requires its methods listed.
        *
        * @return   See above.
        */
       private static List<Method> listAllMethods(Class<?> cls)
       {
          List<Method> allMethods = new ArrayList<>();

          Class<?> current_base = cls;
          while (current_base != null)
          {
             allMethods.addAll(Arrays.asList(current_base.getDeclaredMethods()));

             Class<?>[] ifaces = current_base.getInterfaces();
             if (ifaces != null)
             {
                for (Class<?> iface : ifaces)
                {
                   allMethods.addAll(listAllMethods(iface));
                }
             }

             current_base = current_base.getSuperclass();
          }

          return allMethods;
       }

       /**
        * Check if the specified chain attribute is invalid with the given resource, in query or
        * settable mode.
        *
        * @param    chain
        *           The chainable resource.
        * @param    name
        *           The attribute name.
        * @param    query
        *           Check for reading (when <code>true</code>) or writing (when <code>false</code>).
        *
        * @return   <code>true</code> if the attribute is invalid in this resource.
        */
       private static boolean invalidChainAttribute(HandleChain chain, String name, boolean query)
       {
          switch (name)
          {
             case "PREV-SIBLING":
                return !chain.hasPrevSibling();
             case "NEXT-SIBLING":
                return !chain.hasNextSibling();
             case "PRIVATE-DATA":
                return !chain.hasPrivateData();
             case "NAME":
                return !chain.hasName() || (!query && chain.hasNameReadOnly());
             case "SCREEN-VALUE":
                return chain instanceof WindowWidget ||
                       chain instanceof FrameWidget  ||
                       chain instanceof FieldGroup;
             default:
                return false;
          }
       }

       /**
        * Get the set of operations of given type allowed for a class instances.
        *
        * Note: since this method is only called from a synchronized section
        * already, there is no point bother of synchronization in this method.
        *
        * @param fromClass
        *        the class or interface in question
        * @param table
        *
        * @param resolver
        *        a function which takes a method as the only arguments and
        *        returns a method annotation, matching some condiotions,
        *        or {@code null} if no method annotation matches.
        *
        * @return the set of operation names for the given method arguments
        */
       private static final Set<String> legacyResourceAllowedOperations(Class<?>                     fromClass,
                                                                        Map<String, Set<String>>     table,
                                                                        Function<Method, Annotation> resolver)
       {
          final String className = fromClass.getName();

          Set<String> annotations = table.get(className);
          if (annotations != null)
          {
             return annotations;
          }

          annotations = new HashSet<>();

          /**
           * Collect data from all interfaces and the superclass,
           * we are not expecting the name intersections.
           */
          for (Class<?> iface : fromClass.getInterfaces())
          {
             annotations.addAll(legacyResourceAllowedOperations(iface, table, resolver));
          }

          Class<?> superClass = fromClass.getSuperclass();
          if (superClass != null && superClass != Object.class)
          {
             annotations.addAll(legacyResourceAllowedOperations(superClass, table, resolver));
          }

          Set<String> annos = annotations;
          Utils.dfsClassHierarchy(fromClass, (cls) ->
          {
             // do a DFS walk, and add/remove the annotations based on if there is a method defined for it, and
             // the state of the 'ignore' flag at the annotation.  a granpa class can set it to 'ignore', and
             // another parent class can set it back to 'allowed'.
             Method[] mthds = cls.getDeclaredMethods();
             legacyResourceAllowedUpdateFromMethods(Arrays.asList(mthds), annos, resolver);
          });

          if (annotations.isEmpty())
          {
             annotations = Collections.emptySet();
          }

          table.put(className, annotations);

          return annotations;
       }

       /**
        * Test if a legacy operation is allowed for a given resource based on method annotations.
        * <p>
        * Obtain the legacy attribute annotation instance for the given handle object and given
        * legacy attribute name.
        * <p>
        * The search algorithm for this method uses reflection to read annotations from various
        * sources in the following precedence order:
        * <ol>
        *   <li> The proxy static methods of the resource itself if exist.
        *   <li> The class of the resource itself.
        *   <li> The superclass of the resource or.
        *   <li> All interfaces of the resource.
        * </ol>
        * <p>
        * The search is repeated recursively for the superclass and interfaces.
        * <p>
        * Where possible, we put annotations on the interface, not the class for the resource.  But
        * there are some attributes where the configuration is not universal for all resources
        * implementing the same attribute.  For example, the following attributes are readable for
        * certain resources even though for most other resources the same attribute is writable:
        * <ul>
        *   <li> LABEL for LAST-EVENT
        *   <li> MODIFIED for browse columns
        *   <li> COLUMN for browse cells, field groups, and LAST-EVENT
        *   <li> NO-VALIDATE for both static and dynamic browses (it is writeable only for dynamic
        *        browses)
        * </ul>
        * <p>
        * Keeping the above precedence order is critical to allow attribute configurations of
        * more-specific resources to properly override the more general configuration in the parent
        * hierarchy or the interface hierarchy.
        *
        * @param resource
        *        the resource jbject to test
        * @param key
        *        the operation name, must be in upper case
        * @param table
        *        the map holding sets of allowed operations for an operation type, this map
        *        is updated lazily in this call
        * @param resolver
        *        a function which takes a method as the only arguments and
        *        returns a method annotation, matching some condiotions,
        *        or {@code null} if no method annotation matches.
        *
        * @return   See above.
        */
       private static boolean isLegacyResourceOperationAllowed(Object                       resource,
                                                               String                       key,
                                                               Map<String, Set<String>>     table,
                                                               Function<Method, Annotation> resolver)
       {
          boolean resourceIsProxy = StaticProxy.isStaticProxy(resource);
          Class<?> klass = resource.getClass();
          if (resourceIsProxy)
          {
             klass = klass.getInterfaces()[0];
          }
          String tableKey = klass.getName();

          Set<String> allowed = table.get(tableKey);
          if (allowed == null)
          {
             synchronized (table)
             {
                if (allowed == null)
                {
                   allowed = legacyResourceAllowedOperations(klass, table, resolver);
                }
             }
          }

          return allowed.contains(key);
       }

       /**
        * Update the set of allowed names based on methods annotations.
        *
        * This method scans all methods provided for method annotations of
        * the given class, and, if found, adds or removes entries in the
        * provided set.
        *
        * @param methods
        *        the methods to scan for annotations.
        * @param allowSet
        *        the set of allowed names to update
        * @param resolver
        *        a function which takes a method as the only arguments and
        *        returns a method annotation, matching some condiotions,
        *        or {@code null} if no method annotation matches.
        */
       private static void legacyResourceAllowedUpdateFromMethods(
                final Iterable<Method>             methods,
                final Set<String>                  allowSet,
                final Function<Method, Annotation> resolver)
       {
          for (Method method : methods)
          {
             final Annotation anno = resolver.apply(method);
             if (anno == null)
             {
                continue;
             }

             /**
              * Extract name and allow info from the annotation
              */
             String name;
             boolean allow;
             if(anno instanceof LegacyAttribute)
             {
                LegacyAttribute attAnno = (LegacyAttribute) anno;
                name = attAnno.name();
                allow = !attAnno.ignore();
             }
             else
             {
                LegacyMethod methAnno = (LegacyMethod) anno;
                name = methAnno.name();
                allow = !methAnno.ignore();
             }

             /**
              * Update the set of allowed operations.
              */
             if (allow)
             {
                allowSet.add(name);
             }
             else
             {
                allowSet.remove(name);
             }
          }
       }
    }