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>.
   }

}