# HandleResource.java

    /*
    ** Module   : HandleResource.java
    ** Abstract : Implementation of common attributes and APIs for all resources.
    **
    ** Copyright (c) 2013-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
    ** 001 CA  20130221 Created initial version.
    ** 002 OM  20130304 Refactored isValid and isUnknown of WrappedResource to valid and unknown.
    ** 003 EVK 20130903 Added method type().
    ** 004 CA  20130927 All resources are known by default; special resources which need to be unknown
    **                  will override the unknown() API. The resource type is determined based on the
    **                  LegacyResource annotation: an annotation at the class has precedence over an
    **                  annotation at the interface.
    ** 005 CA  20131113 Added INSTANTIATING-PROCEDURE support.
    **                  Changes related to properly instantiate dynamic resources.
    ** 006 CA  20140326 Fixed implicitDeletion in case is called from an Agent, outside of an external
    **                  procedure (when the remote persistent procedure is deleted by the Agent).
    ** 007 CA  20150502 The resource ID needs to be kept at the resource, so it can be accessed after
    **                  the resource is deleted.
    **                  Track the resources created by each procedure, as dynamic resources don't get
    **                  deleted when the creator proc gets deleted, but their INSTANTIATING-PROCEDURE
    **                  attr needs to be unknown.
    ** 008 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
    ** 009 CA  20160327 getResourceType() invokes type(), so sub-classes can override it.
    ** 010 CA  20171202 Allow the LegacyResource annotation to be determined from the super-class, too.
    ** 011 CA  20181029 Cache the legacy resource names, for each FWD class implementing a resource.
    ** 012 HC  20181128 Moved the relevant portion of HandleChain.delete() in this class.
    ** 013 CA  20190122 OBJECT pseudo-resources don't require procedure-level processing.
    ** 014 OM  20190417 Added unableToAssignUnknown() method.
    ** 015 HC  20200119 Added extension points to allow to externalize widget implementation.
    ** 016 CA  20200110 Implicit static resource deletion must be executed when the instantiating
    **                  procedure gets deleted.
    ** 017 CA  20201011 Track the number of triggers on which this resource is listening on.  On delete, it
    **                  allows to skip unnecessary work if there is no trigger for this resource.
    **     OM  20201030 Invalid attribute API support for getters/setters.
    **     OM  20201203 Fixed handling of READ/ONLY attributes.
    **     CA  20220120 Small performance improvement, use setUnknown instead of set(null), to avoid undoable
    **                  overhead.
    **     VVT 20220306 Uniform toString() support added.
    **     VVT 20220307 AbstractPrintable class eliminated. See #6013-153.
    **     VVT 20221003 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
    **                  with namesakes in DMO. See #6694. Also, a few missing annotations added.
    **     CA  20221006 Cache the hash-code values.  Resources can't override equals() or hashCode().
    **     CA  20221031 Javadoc fixes.
    **     CA  20220918 Use a weak reference for the INSTANTIATING-PROCEDURE, to allow the garbage collector to
    **                  remove the references once this was deleted.
    **     VVT 20220913 CommonHandle.getResourceType() method renamed to resourceType() to prevent conflicts
    **                  with namesakes in DMO. See #6694. Also, a few missing annotations added.
    **     CA  20230110 Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
    **                  context-local lookup.
    **     CA  20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
    ** 018 CA  20230215 Improved other cases to reduce BDT usage from within FWD runtime.
    ** 019 CA  20230724 Further reduce context-local usage.
    ** 020 CA  20231129 Directly set the INSTANTIATING-PROCEDURE referent, from the parent BUFFER's, instead of
    **                  passing it as a handle.
    ** 021 CA  20240918 Removed default methods from interfaces and moved them as concrete implementations, as
    **                  default methods cause the Java metaspace to spike one order of magnitude.
    ** 022 ICP 20250131 Used character.of to leverage cached instances.
    */

    /*
    ** 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.ref.*;
    import java.util.*;
    import java.util.concurrent.*;

    import com.goldencode.p2j.ui.*;
    import com.goldencode.proxy.*;
    import com.goldencode.util.*;

    /**
     * This acts as a superclass which implements:
     * <table summary="">
     * <tr>
     *    <th>attribute or base method</th>
     *    <th>API</th>
     * </tr>
     * <tr>
     *    <td>TYPE</td>
     *    <td>{@link #resourceType()} and {@link #type()}</td>
     * </tr>
     * <tr>
     *    <td>INSTANTIATING-PROCEDURE</td>
     *    <td>{@link #instantiatingProcedure()}</td>
     * </tr>
     * <tr>
     *    <td>Unknown status</td>
     *    <td>{@link #unknown()}</td>
     * </tr>
     * <tr>
     *    <td>Read only field error</td>
     *    <td>{@link #readOnlyError(String, Object)}</td>
     * </tr>
     * <tr>
     *    <td>Invalid attribute access</td>
     *    <td>{@link #invalidAttribute(String, boolean)}</td>
     * </tr>
     * </table>
     * <p>
     * The abstract class also provides setters for the implemented attributes
     * which are not defined inside the interfaces to be used for initializing of
     * those attributes
     */
    public abstract class HandleResource
    implements Printable,
               CommonHandle,
               InstantiatingProcedure,
               Deletable
    {
       /** A mapping of resource names for each FWD class implementing a resource. */
       private static final Map<Class<?>, String> CLS_2_RES = new ConcurrentHashMap<>();

       /** Weak reference to the instantiating procedure. */
       protected WeakReference<Object> instProc;

       /** Variable for holding the type attribute */
       private String type = null;

       /**
        * The resource's ID.  Remains <code>null</code> until the ID is requested via
        * {@link handle#resourceId}
        */
       private Long id = null;

       /** The number of trigger registration (ever).  It will not reflect trigger deregistration. */
       private int triggerRegistration;

       /** Cached hash code. */
       private int hashCode;

       /** Helper to use the ProcedureManager without any context local lookups. */
       protected final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();

       /**
        * Default c'tor.
        */
       public HandleResource()
       {
          this(false);
       }

       /**
        * Initialize this resource by determining its type.
        * <p>
        * The resource's type is determined from a {@link LegacyResource} annotation, which is
        * searched in these locations, in this order:
        * <ol>
        * <li>The class implementing the resource in P2J is checked for an annotation. If found, this
        *     is used to determine the resource type.</li>
        * <li>The entire interface hierarchy is checked. There should be only one interface with a
        *     {@link LegacyResource} annotation; if there is more than annotated interface, then n
        *     {@link IllegalStateException} is thrown.</li>
        * </ol>
        *
        * @param    dynamic
        *           Flag indicating if this is a static or dynamic resource.
        *
        * @throws   IllegalStateException
        *           If the resource type could not be determined from a {@link LegacyResource}
        *           annotation, after checking the interface hierarchy, or more than one interface
        *           is annotated.
        */
       protected HandleResource(boolean dynamic)
       {
          this.hashCode = super.hashCode();

          // check the class first
          Class<?> cls = getClass();

          type = CLS_2_RES.get(cls);

          if (type == null)
          {
             LegacyResource lr = cls.getAnnotation(LegacyResource.class);
             if (lr != null)
             {
                type = lr.resource();
             }
             else
             {
                // else, determine it from the interface. the annotated interface in most cases is a
                // directly implemented interface, but ensure we reach all interfaces, to cover all
                // bases

                Deque<Class<?>> ifcs = new ArrayDeque<Class<?>>();
                // add all the interfaces from the extended classes first
                Class<?> superCls = cls;
                while (superCls != null)
                {
                   ifcs.addAll(Arrays.asList(superCls.getInterfaces()));

                   lr = superCls.getAnnotation(LegacyResource.class);

                   if (lr != null)
                   {
                      if (type != null)
                      {
                         // if more than one interface is annotated, then we are ambiguous
                         final String msg =
                            "More than one LegacyResource annotation in the extended class %s " +
                            "for class %s.";
                         throw new IllegalStateException(
                            String.format(msg, superCls.getName(), cls.getName()));
                      }

                      type = lr.resource();
                   }

                   superCls = superCls.getSuperclass();
                }

                Set<Class<?>> checked = new HashSet<Class<?>>();

                while (!ifcs.isEmpty())
                {
                   Class<?> ifc = ifcs.remove();

                   if (checked.contains(ifc))
                      continue;

                   // keep track of which interface we've visited, as the same interface may appear more
                   // than once in the hierarchy
                   checked.add(ifc);

                   lr = ifc.getAnnotation(LegacyResource.class);
                   if (lr != null)
                   {
                      if (type != null)
                      {
                         // if more than one interface is annotated, then we are ambiguous
                         final String msg =
                            "More than one LegacyResource annotation in the implemented interface %s " +
                            "for class %s.";
                         throw new IllegalStateException(String.format(msg, ifc.getName(), cls.getName()));
                      }

                      type = lr.resource();
                   }

                   ifcs.addAll(Arrays.asList(ifc.getInterfaces()));
                }
             }

             if (type == null)
             {
                final String msg = "Could not determine the legacy resource type for class %s.";
                throw new IllegalStateException(String.format(msg, cls.getName()));
             }

             CLS_2_RES.put(cls, type);
          }

          // this will ensure the INSTANTIATING-PROCEDURE attribute is setup accordingly and also that
          // static resources will be registered with the correct top-level block
          if (processResource() && !ProxyFactory.isProxyClass(this.getClass()))
          {
             // for proxy cases, explicit registration is needed
             pm.processResource(this, dynamic);
          }
       }

       /**
        * Registers a new resource in the internal map of resource class to resource name.
        *
        * @param   clazz
        *          Resource class.
        * @param   name
        *          Resource name.
        */
       public static void registerResource(Class clazz, String name)
       {
          CLS_2_RES.put(clazz, name);
       }

       /**
        * Determine whether this object is equivalent to the specified object. Forces the identity equality.
        *
        * @param   obj
        *          Object to test for equality.
        *
        * @return  {@code true} if equivalent, else {@code false}.
        */
       @Override
       public final boolean equals(Object obj)
       {
          return this == obj;
       }

       /**
        * Return the hash code for this object. Overrides the parent's behavior to return a value which is cached
        * at construction and does not change. Implemented as a performance enhancement.
        *
        * @return  Hash code.
        */
       @Override
       public final int hashCode()
       {
          return hashCode;
       }

       /**
        * Get this resource's ID, if is already set.
        *
        * @return   The resource's ID or <code>null</code> if not set.
        */
       @Override
       public Long id()
       {
          return id;
       }

       /**
        * Set this resource's ID.
        *
        * @param    id
        *           The resource's ID.
        */
       @Override
       public void id(long id)
       {
          this.id = id;
       }

       /**
        * Get the type of this handle.
        *
        * @return   See above.
        */
       @Override
       public character resourceType()
       {
          return character.of(type());
       }

       /**
        * A newly-created resource is always not-unknown. If implementations need to return something
        * different, then override this method.
        *
        * @return   always <code>false</code>
        */
       @Override
       public boolean unknown()
       {
          return false;
       }

       /**
        * Show a specific error, as this read-only {@code attribute} was used on the right-side of an assignment.
        *
        * @param    attr
        *           The read-only attribute.
        *
        * @deprecated Use the overloaded method with multiple arguments.<p>
        *             This API is deprecated because of two reasons: first, it looses the possible side effect of
        *             the assigned expression (consider the case when it is a function or method, but not
        *             exclusively). Secondly, some (if not all) are unknown value sensitive, meaning that, prior
        *             testing whether this is a writable attribute, P4GL checks first if the expression is
        *             unknown and if this value is supported. In this case a different error condition is raised.
        */
       @Override
       @Deprecated
       public void readOnlyError(String attr)
       {
          handle.readOnlyError(new handle(this), attr, handle.UNKNOWN_ARGUMENT);
       }

       /**
        * Shows a specific error, as this read-only {@code attribute} was used on the right-side of an assignment.
        * If the {@code expr} is evaluated to {@code ?} (unknown value) and this attribute does not support it,
        * a specific error is raised.
        *
        * @param    attribute
        *           The read-only attribute.
        * @param    expr
        *           The expression whose value was attempted to be assigned to READ-ONLY attribute.
        */
       @Override
       public void readOnlyError(String attribute, Object expr)
       {
          handle.readOnlyError(new handle(this), attribute, expr);
       }

       /**
        * Get the type of this handle as type of String.
        *
        * @return   See above.
        */
       public String type()
       {
          return type;
       }

       /**
        * Getter for the INSTANTIATING-PROCEDURE attribute.
        * <p>
        * The returned handle will refer a valid external procedure (persistent or not) or will be
        * unknown, in case its instantiating procedure was persistent and has been deleted.
        *
        * @return   See above.
        */
       @Override
       public handle instantiatingProcedure()
       {
          return instProc == null ? new handle() : pm.resolveInstantiatingProcedure(instProc.get());
       }

       /**
        * Getter for the INSTANTIATING-PROCEDURE attribute, as direct resource.
        * <p>
        * The returned reference will refer the exact external procedure (persistent or not) or will be
        * null, if {@link #instProc} was not set.
        *
        * @return   See above.
        */
       public Object _instantiatingProcedure()
       {
          return instProc == null ? null : instProc.get();
       }

       /**
        * Check if the resource can veto the delete or not, before attempting the {@link #delete()} call.
        *
        * @param    ref
        *           The exact handle holding the reference, on which the delete is attempted.
        * @param    explicit
        *           Flag indicating this is called via <code>DELETE OBJECT</code> from the application.
        *
        * @return   <code>false</code> if the delete is vetoed.
        */
       @Override
       public boolean allowDelete(handle ref, boolean explicit)
       {
          return true;
       }

       /**
        * Perform actual delete of an resource. At the time of this call, it is assumed the resource
        * is valid for deletion (the handle and the resource are both valid).
        * <p>
        * The method first calls {@link #resourceDelete()}, if the method returns {@code false}, the deletion
        * is aborted.
        */
       @Override
       public void delete()
       {
          // call resource-specific delete
          if (!resourceDelete())
          {
             // if the resource-specific delete was not possible, exit
             return;
          }

          doDelete();
       }

       /**
        * Increment the {@link #triggerRegistration} counter.
        */
       public void incrementTrigger()
       {
          triggerRegistration++;
       }

       /**
        * Returns a string representation of the object. In general, the
        * {@code toString} method returns a string that
        * "textually represents" this object. The result should
        * be a concise but informative representation that is easy for a
        * person to read.
        * It is recommended that all subclasses override this method.
        * <p>
        * The {@code toString} method for class {@code Object}
        * returns a string consisting of the name of the class of which the
        * object is an instance, the at-sign character `{@code @}', and
        * the unsigned hexadecimal representation of the hash code of the
        * object. In other words, this method returns a string equal to the
        * value of:
        * <blockquote>
        * <pre>
        * getClass().getName() + '@' + Integer.toHexString(hashCode())
        * </pre></blockquote>
        *
        * @return  a string representation of the object.
        */
       @Override
       public String toString()
       {
          return asString();
       }

       /**
        * Add this resource to the {@link WidgetPool}.
        *
        * @param    pool
        *           The pool name, set to <code>null</code> if the unnamed pool is to be used.
        */
       public void addToPool(character pool)
       {
          pm.wp.addResource(this, pool);
       }

       /**
        * Perform actual delete of an resource. At the time of this call, it is assumed the resource
        * is valid for deletion (the handle and the resource are both valid).
        */
       protected void doDelete()
       {
          // remove it from the procedure's resource list
          Object instProc = this.instProc == null ? null : this.instProc.get();
          if (pm.isValid(instProc))
          {
             pm.deregisterResource(instProc, this);
          }

          if (triggerRegistration != 0)
          {
             Long resourceId = this.id();
             int widgetId = (this instanceof CommonWidget
                               ? ((CommonWidget) this).getId()
                               : WidgetId.INVALID_WIDGET_ID);

             if ((resourceId != null && resourceId != ResourceIdHelper.INVALID_RESOURCE) ||
                 widgetId != WidgetId.INVALID_WIDGET_ID)
             {
                LogicalTerminal.cleanupTriggers(resourceId, widgetId);
             }
          }

          // remove the instantiating procedure reference, to help out the garbage collector
          this.instProc = null;

          // the resource was deleted, clean up after it
          pm.h.removeResource(this);
          // remove from the widget pools too
          pm.wp.removeResource(this);
       }

       /**
        * Worker to be implemented by each resource.  Called by {@link #delete()}.
        *
        * @return   <code>true</code> if the resource was deleted.
        */
       @SuppressWarnings("static-method")
       protected boolean resourceDelete()
       {
          return true;
       }

       /**
        * To be used only in cases a getter for a not-supported attribute is forced to be implemented
        * by the resource. It displays appropriate error message.
        *
        * @param   attr
        *          The not-supported attribute attribute.
        * @param   setter
        *          {@code true} for write access.
        */
       protected void invalidAttribute(String attr, boolean setter)
       {
          handle.invalidAttribute(attr, setter, type());
       }

       /**
        * Check if the resource may be implicitly deleted.
        *
        * @return   <code>true</code> if the resource's instantiating procedure is currently being
        *           deleted, and the resource is currently being processed.
        */
       protected boolean implicitDeletion()
       {
          return pm.inNotifications() &&
                 pm.pendingResourceDelete(this) &&
                 (this.instProc != null && this.instProc.get() == pm.getProcessedProcedure());
       }

       /**
        * Set the instantiating procedure for this resource.
        *
        * @param    hproc
        *           The procedure where the resource was instantiated.
        */
       protected void setInstantiatingProcedure(handle hproc)
       {
          if (hproc != null && hproc._isValid())
          {
             this.instProc = new WeakReference<>(hproc.get());

             // register the resource with the procedure, too
             pm.registerResource(hproc.get(), this);
          }
          else
          {
             this.instProc = null;
          }
       }

       /**
        * Check if this resource must be processed via {@link ProcedureManager#processResource}, once
        * it has been instantiated.
        *
        * @return   Always <code>true</code>.
        */
       @SuppressWarnings("static-method")
       protected boolean processResource()
       {
          return true;
       }

       /**
        * Show an error complaining that the UNKNOWN value cannot be assigned to a specified
        * attribute.
        *
        * @param   attribute
        *          The targeted attribute.
        * @param   widgetType
        *          The widget type.
        */
       protected static void unableToAssignUnknown(String attribute, String widgetType)
       {
          ErrorManager.recordOrShowError(4083, attribute, widgetType);
          // **Unable to assign UNKNOWN value to attribute <attribute> on <widget id>.
       }

    }