InputParameter.java

/*
** Module   : InputParameter.java
** Abstract : Defines APIs which are emitted in special cases, to validate the argument before invoking the
**            top-level block.
**
** Copyright (c) 2021-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------Description---------------------------------
** 001 CA  20210216 Created initial version. 
**     TJD 20220504 Java 11 compatibility minor changes
** 002 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 003 RAA 20230525 Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
** 004 ES  20240719 Casting the value to the type defined of the Resolvable.
*/
/*
** 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.util.logging.*;

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

/**
 * Defines APIs emitted to preserve the conversion-time type of an expression, and allow the runtime to check
 * if the evaluated value of this expression matches the conversion-time type or not.
 * <p>
 * For OO method calls, this ensures that the proper overloaded method is resolved, as this requires the
 * conversion-time type, for both direct and dynamic calls.
 */
public class InputParameter
{
   /** Logger */
   private static final CentralLogger LOG = CentralLogger.get(InputParameter.class);

   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * <p>
    * This API is emitted when the target top-level block is a direct Java call for a legacy OO method.
    *  
    * @param    type
    *           The conversion-time type of the expression.
    * @param    value
    *           The runtime value of the argument.
    *           
    * @return   The value converted to the conversion-time type.
    */
   public static Resolvable arg(Class<? extends BaseDataType> type, BaseDataType value)
   {
      return new Resolvable()
      {
         @Override
         public Class<? extends BaseDataType> getType()
         {
            return type;
         }
         
         @Override
         public BaseDataType resolve()
         {
            if (value.isUnknown())
            {
               return BaseDataType.generateUnknown(type);
            }
            else
            {
               try
               {
                  return BaseDataTypeFactory.instantiate(type, value);
               }
               catch (ReflectiveOperationException e)
               {
                  return value;
               }
            }
         }
      };
   }
   
   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * <p>
    * This API is emitted when the target top-level block is a function.
    *  
    * @param    type
    *           The conversion-time type of the expression.
    * @param    value
    *           The runtime value of the argument.
    *           
    * @return   The value converted to the conversion-time type.
    */
   public static <T extends BaseDataType> T function(Class<T> type, BaseDataType value)
   {
      return cast(type, 
                  false, 
                  value, 
                  "User-defined Function could not build input runtime parameters from the stack. (6348)", 
                  6348);
   }
   
   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * <p>
    * This API is emitted when the target top-level block is from a RUN statement.
    *  
    * @param    type
    *           The conversion-time type of the expression.
    * @param    value
    *           The runtime value of the argument.
    *           
    * @return   The value converted to the conversion-time type.
    */
   public static <T extends BaseDataType> T procedure(Class<T> type, BaseDataType value)
   {
      return cast(type, 
                  true, 
                  value, 
                  "Procedure could not build input runtime parameters from the stack. (988)", 
                  988);
   }

   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * <p>
    * This API is emitted when the target top-level block is a property setter.
    *  
    * @param    type
    *           The conversion-time type of the expression.
    * @param    value
    *           The runtime value of the argument.
    *           
    * @return   The value converted to the conversion-time type.
    */
   public static <T extends BaseDataType> T property(Class<T> type, BaseDataType value)
   {
      return cast(type, true, value, "** Unable to update  Field. (142)", 142);
   }

   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * 
    * @param    type
    *           The conversion-time type of the expression.
    * @param    value
    *           The runtime value of the argument.
    *           
    * @return   The value converted to the conversion-time type.
    */
   public static <T extends BaseDataType> T cast(Class<T> type, BaseDataType value)
   {
      return cast(type, 
                  true, 
                  value, 
                  "User-defined Function could not build input runtime parameters from the stack. (6348)", 
                  6348);
   }
   
   /**
    * Cast the runtime value to the specified type.   If the assignment fails, then the next top-level block
    * will not be executed, and instead argument failure errors are shown/thrown.
    * 
    * @param    type
    *           The conversion-time type of the expression.
    * @param    error
    *           Flag indicating if an ERROR condition needs to be raised.
    * @param    value
    *           The runtime value of the argument.
    * @param    errMsg
    *           The error message to add in case of failure.
    * @param    errNum
    *           The error number to add in case of failure.
    *           
    * @return   The value converted to the conversion-time type.
    */
   private static <T extends BaseDataType> T cast(Class<T>     type, 
                                                  boolean      error, 
                                                  BaseDataType value, 
                                                  String       errMsg, 
                                                  int          errNum)
   {
      if (type == value.getClass())
      {
         return (T) value;
      }
      
      try
      {
         T newVal = BaseDataTypeFactory.instantiate(type);
         List<String> errors = new ArrayList<>();
         List<Integer> nums = new ArrayList<>();
         
         BiConsumer<String, Integer> processor = (msg, num) ->
         {
            errors.add(msg);
            nums.add(num);
         };
         
         if (ErrorManager.nestedSilent(() -> newVal.assign(value), processor))
         {
            errors.add(errMsg);
            nums.add(errNum);
            
            // get the stacktrace which initiated this ignore, to make it easier to find errors in case the
            // 'abend' code gets called for another top-level block
            Exception e = new Exception();
            e.fillInStackTrace();
            StackTraceElement[] ste = e.getStackTrace();
            StackTraceElement last = ste[2];
            
            LOG.log(Level.INFO, "Invalid INPUT parameter - ignoring the call at: " + last.toString());
            
            BlockManager.ignoreNextCall(() -> 
            {
               if (error)
               {
                  ErrorManager.recordOrThrowError(Utils.integerCollectionToPrimitive(nums), 
                                                  errors.toArray(new String[0]), 
                                                  false, 
                                                  true);
               }
               else
               {
                  ErrorManager.recordOrShowError(Utils.integerCollectionToPrimitive(nums), 
                                                 errors.toArray(new String[0]), 
                                                 false, 
                                                 false,
                                                 false,
                                                 true,
                                                 false);
               }
            });
         }
         
         return newVal;
      }
      catch (ReflectiveOperationException e)
      {
         // should never happen, but throw anyway
         throw new RuntimeException(e);
      }
   }
}