# DataSetManager.java

    /*
    ** Module   : DataSetManager.java
    ** Abstract : Manages all the DataSets, taking care for matching shared ones.
    **
    ** Copyright (c) 2019-2024, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- ----------------------------------------Description---------------------------------------
    ** 001 OM  20190403 Created initial version.
    ** 002 OM  20190624 Added runtime implementation.
    ** 003 CA  20190722 Allow lookup for handles registered as OUTPUT arguments.
    **     OM  20190731 Added REFERENCE-ONLY support.
    ** 004 CA  20200110 More fixes for DATASET parameters and resource delete.
    ** 005 CA  20201003 Allow dictionaries with an identity map.
    ** 006 CA  20220524 The scope ends when the procedure returns, not on 'scopeFinished', to allow parameter
    **                  processing.
    **     CA  20220613 The static DATASET must be registered as 'pending' to be properly registered with the next
    **                  external program call.
    **     CA  20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration
    **                  is now specific to each type of scopeable.  For each case, the block will be registered
    **                  for scope support (for that particular scopeable) only when the scopeable is 'active'
    **                  (i.e. unnamed streams or accumulators are used).  This allows a lazy registration of
    **                  scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
    **                  and every block.
    ** 007 AL2 20230613 Use procedure helper where possible. Allow faster gateways to the transaction manager.
    ** 008 CA  20231031 Added 'BlockDefinition' parameter to 'scopeStart'.
    ** 009 CA  20240404 Static temp-table or dataset resolution by name must be done using the currently executing
    **                  type and the static temp-table/dataset's defining type.
    */

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

    import com.goldencode.p2j.security.*;
    import com.goldencode.p2j.util.*;

    import java.util.*;

    /**
     * A manager for {@code DataSet}-s:
     * <ul>
     *    <li>registers new {@code DataSet}-s and matches shared ones;
     *    <li>keeps the head and tail of the list of {@code DataSet}-s.
     * </ul>
     */
    public class DataSetManager
    implements Scopeable
    {
       /**
        * The context-local manager.
        */
       private final static ContextLocal<DataSetManager> instance = new ContextLocal<DataSetManager>()
       {
          /**
           * This method will be invoked to retrieve the initial value of this context local variable,
           * the first time {@link #get} is invoked (but only if {@link #set} was not previously
           * invoked. The returned value will be stored as the variable's initial value.
           * <p>
           *
           * @return A new instance of {@code DataSetManager}.
           */
          @Override
          protected DataSetManager initialValue()
          {
             return new DataSetManager();
          }
       };

       /** The pending datasets defined in the next external program being invoked. */
       private Set<StaticDataSet> pendingRegistry = new HashSet<>();

       /**
        * Scoped dictionary which contains the maps of output copiers for a procedure/function to
        * their 0-based indexes in the set of all DATASET-HANDLE parameters for the procedure/function
        * (excluding other types of parameters).
        */
       private final ScopedDictionary<OutputDataSetHandleCopier, Integer> dshOutputParams =
             new ScopedDictionary<>();

       /** A dictionary of BY-REFERENCE dataset parameters. */
       private final ScopedDictionary<DataSet, Integer> byReferenceParameters = new ScopedDictionary<>();

       /** The transaction helper. */
       private final TransactionManager.TransactionHelper tm;

       /** The procedure helper. */
       private final ProcedureManager.ProcedureHelper pm;

       /** The buffer helper. */
       private final BufferManager bm;

       /**
        * Default c'tor.  Marks dictionaries which can use identity maps.
        */
       private DataSetManager()
       {
          tm = TransactionManager.getTransactionHelper();
          pm = ProcedureManager.getProcedureHelper();
          bm = BufferManager.get();
          dshOutputParams.setIdentityKeys(true);
          byReferenceParameters.setIdentityKeys(true);
       }

       /**
        * Factory method. Corresponds to {@code DEFINE DATASET} statement. Creates a new builder for a
        * new {@link DataSet} and returns it for further setting up of options.
        *
        * @param   legacyName
        *          The legacy name of the {@code DataSet}. Used for lookup.
        * @param   isShared
        *          This is a shared {@code DataSet}.
        * @param   isNew
        *          This is a new {@code DataSet}. Only in combination with {@code isShared}.
        */
       public static DataSet.Builder define(String legacyName, boolean isShared, boolean isNew)
       {
          return new DataSet.Builder(legacyName, isShared, isNew);
       }

       /**
        * Obtain the instance of {@code DataSetManager} (associated with this work area ?).
        *
        * @return  the instance of {@code DataSetManager}.
        */
       public static DataSetManager instance()
       {
          return instance.get();
       }

       /**
        * Register the {@link DataSetManager} for scope notifications at the current block.
        */
       public static void registerScopeable()
       {
          instance()._registerScopeable();
       }

       /**
        * Registers a ((new) shared) dataset.
        *
        * @param   dataSet
        *          The new {@code DataSet} to be registered.
        * @param   legacyName
        *          The legacy name of the {@code DataSet}.
        * @param   isShared
        *          {@code true} if this is a SHARED DATASET.
        * @param   isNew
        *          {@code true} if this is a NEW SHARED DATASET.
        */
       public void register(DataSet dataSet, String legacyName, boolean isShared, boolean isNew)
       {
          tm.registerPendingScopeable(this);

          if (!dataSet._dynamic())
          {
             pendingRegistry.add((StaticDataSet) dataSet);
          }
       }

       /**
        * Register the {@link DataSetManager} for scope notifications at the current block.
        * This is a convenient method when the manager is already retrieved, so that there is
        * no need for a further context look-up.
        */
       public void _registerScopeable()
       {
          tm.registerBlockScopeable(this);

          // if there is dataset usage, then buffers are needed, too
          BufferManager.registerScopeable(bm);
       }

       /**
        * Convenient gateway to the transaction manager. This is used to avoid an additional
        * context look-up if the caller already has access to this manager. This is not a
        * good pattern to follow (add a method in data set manager to gateway each method
        * in transaction manager). However, this is the only case we want to have such gateway.
        *
        * @param   dsHandle
        *          the target to be registered with the transaction manager
        */
       public void registerCurrent(handle dsHandle)
       {
          tm.registerCurrent(dsHandle);
       }

       /**
        * Looks up for a {@code DataSet} by its name. Only STATIC dataset are managed.
        *
        * @param   dsName
        *          The name to search.
        *
        * @return  The {@code DataSet} statically named {@code sdName} or {@code null} is such
        *          {@code DataSet} is not accessible.
        */
       public DataSet byName(String dsName)
       {
          Object ref = pm._thisProcedure();
          Class<?> type = pm.getExecuting();

          return bm.getStaticDataSet(type, dsName, ref);
       }

       /**
        * Provides a notification that a new scope is about to be entered.
        *
        * @param    block
        *           The explicit block definition which required this notification.
        */
       @Override
       public void scopeStart(BlockDefinition block)
       {
          if (block.topLevel)
          {
             dshOutputParams.addScope(null);
             byReferenceParameters.addScope(null);

             if (!pendingRegistry.isEmpty())
             {
                for (StaticDataSet dataset : pendingRegistry)
                {
                   String name = dataset.getDefinitionName();
                   Object ref = pm._thisProcedure();
                   Class<?> type = dataset.getDefiningType();
                   bm.registerDataSet(type, name, ref, dataset);
                }
                pendingRegistry.clear();
             }

             pm.executeOnReturn(() ->
             {
                dshOutputParams.deleteScope();
                byReferenceParameters.deleteScope();
             }, WeightFactor.LAST);
          }
       }

       /**
        * Provides a notification that a scope is about to be exited.
        */
       @Override
       public void scopeFinished()
       {
       }

       /**
        * Provides a notification that an external scope is about to be deleted.
        */
       @Override
       public void scopeDeleted()
       {
          // TODO: implement~me
       }

       /**
        * Get the {@link ScopeId} for the instance.
        *
        * @return   {@link ScopeId#DATA_SET_MANAGER}.
        */
       @Override
       public ScopeId getScopeId()
       {
          return ScopeId.DATA_SET_MANAGER;
       }

       /**
        * Check if the given dataset is passed as a BY-REFERENCE parameter.
        *
        * @param    ds
        *           The dataset to check.
        *
        * @return   <code>true</code> if the dataset exists in the {@link #byReferenceParameters}
        *           dictionary.
        */
       public boolean isByReferenceParameter(DataSet ds)
       {
          for (int i = 0; i < byReferenceParameters.size(); i++)
          {
             Map<DataSet, Integer> map = byReferenceParameters.getDictionaryAtScope(i, false);
             if (map == null)
             {
                continue;
             }

             if (map.containsKey(ds))
             {
                return true;
             }
          }

          return false;
       }

       /**
        * Register the given dataset as BY-REFERENCE, in the {@link #byReferenceParameters} dictionary.
        *
        * @param    ds
        *           The dataset.
        */
       public void addByReferenceParameter(DataSet ds)
       {
          Map<DataSet, Integer> map = byReferenceParameters.getDictionaryAtScope(0, true);
          byReferenceParameters.addEntry(false, ds, map.size());
       }

       /**
        * Deregister the given dataset as BY-REFERENCE, from the {@link #byReferenceParameters}
        * dictionary.
        *
        * @param    ds
        *           The dataset.
        */
       public void removeByReferenceParameter(DataSet ds)
       {
          byReferenceParameters.removeEntryAtScope(ds, 0);
       }

       /**
        * Check if the specified dataset instance is registered as an OUTPUT argument.
        *
        * @param    ds
        *           The dataset instance.
        *
        * @return   <code>true</code> if the handle originates from the caller as an OUTPUT argument.
        */
       public boolean isOutputDataSetHandle(DataSet ds)
       {
          for (int i = 0; i < dshOutputParams.size(); i++)
          {
             Map<OutputDataSetHandleCopier, Integer> map = dshOutputParams.getDictionaryAtScope(i, false);
             if (map == null)
             {
                continue;
             }

             for (OutputDataSetHandleCopier out : map.keySet())
             {
                if (out.getCalled().get() == ds)
                {
                   return true;
                }
             }
          }

          return false;
       }

       /**
        * Create and register the output table copier.
        *
        * @param   dsParam
        *          The source dataset parameter.
        * @param   dsHandle
        *          The target dataset handle..
        */
       public void registerOutputDataSetHandleCopier(DataSetParameter dsParam,
                                                     handle           dsHandle)
       {
          OutputDataSetHandleCopier copier = new OutputDataSetHandleCopier(dsParam, dsHandle);
          pm.executeOnReturn(copier::finished, copier.weight());

          Map<OutputDataSetHandleCopier, Integer> map = dshOutputParams.getDictionaryAtScope(0, true);
          dshOutputParams.addEntry(false, copier, map.size());
          copier.setSiblingsMap(map);
       }

       /**
        * Unregister the output dataset copier <b>in the current scope</b>.
        *
        * @param   copier
        *          Copier to unregister.
        */
       public void unregisterOutputDataSetHandleCopier(OutputDataSetHandleCopier copier)
       {
          dshOutputParams.removeEntryAtScope(copier, 0);
       }
    }