# ObjectResource.java

    /*
    ** Module   : ObjectResource.java
    ** Abstract : synthetic resource which helps with legacy object management.
    **
    ** Copyright (c) 2019-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
    ** 001 CA  20190122 First version.
    ** 002 CA  20200304 A 4GL object instance must delay its delete if is still on stack.
    ** 003 CA  20200324 For H002, use 'nestedSilent', to preserve any already set silent processing.
    ** 004 CA  20200827 A destructor may invalidate an in-process-of-being-deleted instance.
    **     CA  20210221 Allow an initializing instance to survive decrement even if it reaches 0.  Its reference
    **                  can be passed to arguments/definitions without affecting its lifecycle, unless is
    **                  explicitly deleted.
    **     CA  20210305 The object fields have a Long representation at the SQL table field.
    **     CA  20220120 The destructors must be called from delete(), as resourceDelete() can be called multiple
    **                  times. Fixed decrement when there is a pending assignment.
    **     CA  20220203 Fixed javadoc for resourceDelete().
    **     CA  20221006 Improved context-local lookup. Refs #6824
    **     CA  20220524 Fixed destructor execution.
    **     CA  20220526 If there are subscriptions for a legacy OO instance, do not delete (implicitly).
    **                  Progress.Lang.Class instances can't be deleted.
    **     CA  20220918 Mark an object as pending delete before executing the destructors.
    **     CA  20221123 Progress.Lang.Class instances are not reported in the SESSION:FIRST-OBJECT chain.
    **     CA  20230110 Cache the helper for ProcedureManager, ObjectOps and others (as needed), to reduce
    **                  context-local lookup.
    ** 005 CA  20230731 Do not delete (implicitly) an instance which is being returned by a function or method
    **                  call.
    ** 006 CA  20230724 Further reduce context-local usage.
    ** 007 CA  20240105 The 'inScopeFinished' flag must be reset/restored while executing destructors.
    */

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

    import com.goldencode.p2j.oo.lang.*;

    /**
     * A pseudo-resource to help management for the legacy class instances.
     * <p>
     * This will allow the object chaining to work naturally as with other legacy resources, help
     * with reference management and resource delete.
     */
    @LegacyResource(resource = LegacyResource.OBJECT)
    public class ObjectResource
    extends HandleChain
    {
       /** The contained object instance. <code>null</code> is unknown value. */
       private _BaseObject_ ref;

       /** The reference counter. */
       private long references = 0;

       /** The destructors to be executed on delete. */
       private Deque<InternalEntry> destructors = new ArrayDeque<>(3);

       /** Flag indicating if this resource is being deleted. */
       private boolean pendingDelete = false;

       /**
        * Initialize this object with the given reference.
        *
        * @param    reference
        *           The reference.
        */
       public ObjectResource(_BaseObject_ reference)
       {
          super(false, !(reference instanceof LegacyClass));

          this.ref = reference;

          // resolve the internal resource ID now.
          handle.resourceId(this);
       }

       /**
        * Check if this resource is unknown.
        */
       @Override
       public boolean unknown()
       {
          return ref == null;
       }

       /**
        * Check if this pseudo-resource is valid.
        *
        * @return   <code>true</code> if {@link #ref} is not <code>null</code>.
        */
       @Override
       public boolean valid()
       {
          return ref != null;
       }

       /**
        * Return the wrapped reference.
        *
        * @return   The contained reference.
        */
       public _BaseObject_ ref()
       {
          return ref;
       }

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

       /**
        * Increment the reference counter.
        */
       public void increment()
       {
          if (references == 0)
          {
             // remove it from the pending assignments
             pm.oh.instanceAssigned(ref);
          }

          references = references + 1;
       }

       /**
        * Decrement the reference counter.  When it reaches zero, delete this object.
        */
       public void decrement()
       {
          if (references > 0)
          {
             // there are cases where the reference was never assigned.
             references = references - 1;
          }

          if (references == 0 &&
              pm.oh.isValid(ref) &&
              !pm.oh.isInitializing(ref) &&
              !pm.oh.isPendingAssigned(ref) &&
    	  !pm.oh.instanceReturned(ref) &&
              !pm.hasSubscriptions(ref))
          {
             // if there are subscriptions for this instance, do not delete (implicitly)

             pm.oh.delete(ref);
          }
       }

       /**
        * 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).
        */
       @Override
       public void delete()
       {
          // call resource-specific delete
          if (!resourceDelete())
          {
             // if the resource-specific delete was not possible, exit
             return;
          }

          pendingDelete = true;

          boolean inScopeFinished = pm.oh.inScopeFinished();
          // call the destructor from all loaded super-classes, in reverse order.
          while (!destructors.isEmpty())
          {
             InternalEntry dtor = destructors.pop();

             pm.oh.setInScopeFinished(false);
             ControlFlowOps.executeDestructor(ref, dtor);
          }
          pm.oh.setInScopeFinished(inScopeFinished);

          super.delete();

          if (valid())
          {
             // may have been invalidated by the destructor calls.
             // finally, delete the resource
             ExternalProgramWrapper extProg = new ExternalProgramWrapper(ref);
             extProg.delete();

             ref = null;
          }
       }

       /**
        * Check if the resource can be deleted (is not
        * {@link ProcedureManager$ProcedureHelper#isDelayedDelete(Object) delayed}).
        *
        * @return   Always <code>true</code>
        */
       @Override
       protected boolean resourceDelete()
       {
          // do not delete a progress.lang.class
          if (pm.isDelayedDelete(ref) || ref instanceof LegacyClass)
          {
             return false;
          }

          return true;
       }

       /**
        * Checks whether the object has any destructor pushed on stack ready to be executed when the
        * lifetime of the object ends.
        *
        * @return  {@code true} if when the object is deleted at least one destructor will be
        *          executed.
        */
       boolean hasActiveDestructors()
       {
          return !destructors.isEmpty();
       }

       /**
        * Add a new destructor on the stack.  These will be invoked, in the inverse order, on object
        * deletion.
        *
        * @param    mthd
        *           The destructor method.
        */
       void pushDestructor(InternalEntry mthd)
       {
          if (destructors.isEmpty() || !mthd.equals(destructors.peek()))
          {
             destructors.push(mthd);
          }
       }

       /**
        * Get the current reference counter.
        *
        * @return   The {@link #references}.
        */
       long getReferences()
       {
          return references;
       }

       /**
        * Silently delete this object, by ignoring all conditions.
        */
       void deleteSilent()
       {
          if (pm.hasSubscriptions(ref))
          {
             // if there are subscriptions for this instance, do not delete (implicitly)
             return;
          }

          ErrorManager.nestedSilent(() ->
          {
             try
             {
                // save the reference, before the delete
                _BaseObject_ ref = this.ref;

                delete();

                if (this.ref == null)
                {
                   // if it was actually deleted, remove the reference
                   pm.oh.deregisterObject(ref);
                }
             }
             catch (Throwable t)
             {
                // ignore
             }
          });
       }

       /**
        * Check if this resource is being deleted.
        *
        * @return   The {@link #pendingDelete} state.
        */
       boolean isPendingDelete()
       {
          return pendingDelete;
       }
    }