# BaseDataType.java

    /*
    ** Module   : BaseDataType.java
    ** Abstract : Progress 4GL compatible operator logic
    **
    ** Copyright (c) 2005-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
    ** 001 GES 20050518   @21222 Created initial version with full support for all Progress logical
    **                           operators and common functions.
    ** 002 GES 20050606   @21423 Minor additions for function support.
    ** 003 ECF 20050629   @21601 Implement Serializable. Will need this to push wrapper objects
    **                           over the wire.
    ** 004 SVG 20050720   @21762 Cloning support added.
    ** 005 GES 20050815   @22117 Added Undoable interface support.
    ** 006 GES 20050825   @22252 Added getTypeName().
    ** 007 ECF 20051005   @22966 Introduced an abstract hashCode() method. This forces subclasses
    **                           to provide an implementation which is consistent with this class'
    **                           implementation of the equals() method.
    ** 008 GES 20060208   @24385 Added defaultFormatString().
    ** 009 GES 20060301   @24840 Added generateUnknown() static factory method.
    ** 010 NVS 20060407   @25407 Added isUnknownValue() and notUnknownValue() methods.
    ** 011 GES 20060417   @25551 Added default value generation.
    ** 012 GES 20060724   @28156 Added a forced assign() abstract method.
    ** 013 GES 20060807   @28469 Moved to externalizable interface to optimize network performance.
    ** 014 GES 20061109   @31049 Added formatLength().
    ** 015 ECF 20070720   @34626 Added safety check to equals(). Check type of object before casting
    **                           it to BaseDataType to prevent ClassCastException.
    ** 016 SIY 20100812          Removed unnecessary cast.
    ** 017 CA  20130119          Added castLogical and castBoolean, which cast the current value
    **                           using DYNAMIC-FUNCTION's rules.
    ** 018 GES 20130128          Clarified some javadoc.
    ** 019 GES 20130312          Removed castLogical() and castBoolean(). This same functionality is
    **                           implemented in the logical(BDT) c'tor.
    ** 020 EVK 20131130          Added elementsOfType static method.
    ** 021 MAG 20140602          Added incompatibleTypesOnConversion() method.
    ** 022 OM  20150723          Updated javadoc.
    ** 023 CA  20151214          Added APIs to implicitly initialize an extent var and to check if two
    **                           objects are of the same type.
    ** 024 GES 20160617          Added assign(BDT) for converting polymorphic return values.
    ** 025 CA  20160627          Reworked undoable support - the undoables register themselves with
    **                           all blocks up the stack, until either 1. the tx block which created
    **                           it is reached or 2. the last tx block where it was saved is reached.
    ** 026 GES 20171207          Removed explicit bypass on pending error (new silent error mode).
    ** 027 CA  20181029          Some refactoring required by the CALL's dynamic invoke support.
    ** 028 HC  20190127          Added method isAllKnown.
    **     OM  20190206          Added declaration of field size() needed by record-length.
    ** 029 CA  20190516          Added assign(Object) for values returned by POLY functions.
    ** 030 CA  20190710          Added OBJECT to fromTypeName.
    ** 031 CA  20190927          Added support for direct Java access from 4GL code.
    ** 032 CA  20200514          Added BLOB and CLOB to fromTypeName.  Also, added the DATETIMETZ alias for
    **                           DATETIME-TZ.
    ** 033 GES 20200628          Added invalidInitializer() helper to raise error 12003.
    ** 034 CA  20201003          Added isAllKnown(BaseDataType), to avoid array creation and iteration for single
    **                           element calls.
    **     IAS 20201007          Added Type enum
    **     ME  20201009          Added new data type for table/dataset handle, mapped to handle.
    **     ME  20200322          Add info of which data type does not support a certain value assign.
    **     ME  20210504          Added method isIncompatibleTypesOnConversion to check if POLY data type conversion
    **                           is to be attempted for given data type.
    ** 035 CA  20210512          Fixed 'getTypeName' when anon classes are used (for 'directAssign' usage).
    **     CA  20210609          Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
    **                           initialized at the method's execution, and not at the caller's arguments.
    ** 036 AL2 20220328          Created proxy for BDT.
    **     CA  20221006          Performance improvement for 'getTypeName', use'getType' instead of the BDT actual
    **                           class name.
    **     TJD 20220504          Java 11 compatibility related minor changes
    **     CA  20230116          Avoid fromTypeName when the ref type is valid and known.
    **     HC  20230118          Eliminated some of the uses of String.toUpperCase and/or
    **                           String.toLowerCase for performance.
    **     CA  20230206          'instantiateDefault' must be aware of anon BDT classes.
    ** 037 CA  20230215          'instantiateDefault' now is implemented by each sub-class.  Added unknown.UNKNOWN
    **                           singleton (used by FWD runtime at this time).
    ** 038 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    ** 039 RAA 20230525          Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
    ** 040 RAA 20231009          Added check for dynamic extents in sameType.
    ** 041 CA  20231129          Allow 'null' doNotProxy arrays in ProxyFactory.getProxy.
    ** 042 PBB 20250509          Added validation for assignment of extent values to BDT properties.
    ** 043 ICP 20250414          Added implementation of the HASH-CODE builtin function.
    ** 044 AL2 20250530          Added getIndependentFromContext.
    */

    /*
    ** 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.io.*;
    import java.lang.reflect.*;
    import java.math.*;
    import java.util.*;
    import java.util.function.*;

    import com.goldencode.p2j.persist.FieldReference;
    import com.goldencode.p2j.util.logging.*;
    import com.goldencode.proxy.ProxyFactory;
    import com.goldencode.util.*;

    /**
     * An abstract class that implements a Progress 4GL compatible data type
     * which has common logical operator processing.  Meant to be the parent
     * class for specific Progress compatible data type wrappers.
     * <p>
     * Each subclass should implement <code>Comparable</code> but is only
     * required to implement <code>compareTo()</code> as the <code>equals()</code>
     * method is implemented in this class.
     * <p>
     * <strong>Note:</strong> As result, classes from this hierarchy cannot be used as keys for normal
     * java associative collections because of collisions caused by objects (Ex: an unknown
     * {@link integer} and an unknown {@link logical} are considered equals in P4GL so only one
     * unknown can be the key in such collection). If such collection is required, use a special one
     * like {@link java.util.IdentityHashMap}.
     * <p>
     * All processing relies upon the implementation of the following methods
     * in each concrete subclass:
     * <p>
     * <pre>
     * boolean      isUnknown()
     * void         setUnknown(boolean)
     * int          compareTo(Object) - this comes from the Comparable interface
     * BaseDataType instantiateUnknown()
     * BaseDataType duplicate()
     * void         assign(Undoable) - this comes from the Undoable interface
     * void         assign(BaseDataType)
     * String       toString()
     * String       toStringMessage()
     * String       toStringExport()
     * String       defaultFormatString()
     * </pre>
     */
    @SuppressWarnings("rawtypes")
    public abstract class BaseDataType
    extends LazyUndoable
    implements Comparable,
               Externalizable
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(BaseDataType.class.getName());

       /**
        * Default constructor (only used for de-serialization).
        */
       public BaseDataType()
       {
       }

       /**
        * Mark this BDT instance as a 4GL-declared variable - it will be wrapped in a
        * {@link BaseDataTypeVariable} instance.
        *
        * @param    var
        *           The reference to be marked as a variable.
        *
        * @return   The wrapped variable.
        */
       public static BaseDataTypeVariable variable(BaseDataType var)
       {
          return new BaseDataTypeVariable(var);
       }

       /**
        * Resolve the BDT sub-class from the specified 4GL type name.
        * <p>
        * This type name must be un-abbreviated and standardized (if multiple aliases exists).
        *
        * @param    typeName
        *           The type name.
        *
        * @return   The BDT sub-class or <code>null</code> if the type is unknown.
        */
       public static Class<? extends BaseDataType> fromTypeName(String typeName)
       {
          Type type = Type.of(typeName);
          return type == null ? null : type.getCls();
       }

       /**
        * Creates a new instance of the given type that represents the
        * <code>unknown value</code>.
        *
        * @param    cls
        *           instance class
        * @return   An instance that represents the <code>unknown value</code>.
        */
       public static BaseDataType generateUnknown(Class<?> cls)
       {
          BaseDataType bdt = null;

          try
          {
             // use the default constructor
             bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
          }

          catch (Exception expt)
          {
             // what else to do here (the calling code will probably fail with
             // a class cast exception)?
             return unknown.UNKNOWN;
          }

          return bdt.instantiateUnknown();
       }

       /**
        * Creates a new instance of the given type that represents the
        * default initialized value for that type.
        *
        * @param    cls
        *           instance class
        * @return   An instance that represents the default value.
        */
       public static BaseDataType generateDefault(Class<?> cls)
       {
          BaseDataType bdt = null;

          try
          {
             // use the default constructor
             bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
          }

          catch (Exception expt)
          {
             throw new RuntimeException(expt);
          }

          return bdt.instantiateDefault();
       }

       /**
        * Calculate the length of a formatted value (the text form of the value)
        * using this format string.
        *
        * @param    fmt
        *           The format string.
        * @param    cls
        *           instance class
        * @return   The length in characters of the any resulting formatted
        *           value.
        */
       public static int calcFormatLength(String fmt, Class<?> cls)
       {
          BaseDataType bdt = null;

          try
          {
             // use the default constructor
             bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
          }

          catch (Exception expt)
          {
             throw new RuntimeException(expt);
          }

          return bdt.formatLength(fmt);
       }

       /**
        * Check list of BaseType of arguments and return {@code true} if all BaseTypes are specific
        * type.
        *
        * @param    cls
        *           Specific BaseDataType class witch will be checked to the arguments.
        * @param    args
        *           list of BaseType instances.
        *
        * @return   The {@code true} if all BaseTypes are character.
        */
       public static boolean elementsOfType(Class<? extends BaseDataType> cls, BaseDataType... args)
       {
          if (args.length == 0)
          {
             return true;
          }

          for (BaseDataType arg : args)
          {
             if (!cls.isInstance(arg))
             {
                return false;
             }
          }
          return true;
       }

       /**
        * Check if the types of the specified objects match.
        * <p>
        * In case of extent vars, the size of each var must be the same, and both must be extent.
        *
        * @param    source
        *           The reference variable.
        * @param    target
        *           The candidate variable, which must match.
        *
        * @return   <code>true</code> if the type of these two variables matches.
        */
       protected static boolean sameType(Object source, Object target)
       {
          // extent vars
          if (target.getClass().isArray())
          {
             if (!source.getClass().isArray())
             {
                // shared is not an array
                return false;
             }

             Class<?> tcls = target.getClass().getComponentType();
             Class<?> scls = source.getClass().getComponentType();

             // check component type
             if (!tcls.equals(scls))
             {
                return false;
             }
             else
             {
                int tlen = Array.getLength(target);
                if (ArrayAssigner.isDynamicArray((BaseDataType[]) source) && tlen == 0)
                {
                   // if both arrays have dynamic extent, no further length check is needed
                   return true;
                }

                // check extent
                int slen = Array.getLength(source);
                if (slen != tlen)
                {
                   return false;
                }
             }

             return true;
          }
          else
          {
             // check type directly (this will also fail if source is array and target
             // is not array)
             return source.getClass().equals(target.getClass());
          }
       }

       /**
        * Initialize the given array with the default value, based on the following table
        * (the rules are from variable_definition.rules):
        *
        * <pre>
        *  integer                new integer(0)
        *  int64                  new int64(0)
        *  decimal                new decimal(0)
        *  character              new character(&quot;&quot;)
        *  logical                new logical(false)
        *  longchar               new longchar(&quot;&quot;)
        *  date                   new date()
        *  datetime               new datetime()
        *  datetime-tz            new datetimetz()
        *  raw                    new raw()
        *  memptr                 new memptr()
        *  recid                  new recid()
        * </pre>
        *
        * @param    var
        *           The extent variable which needs to be initialized.
        */
       protected static void initializeDefaultExtent(BaseDataType[] var)
       {
          if (var == null || var.length == 0)
          {
             return;
          }

          Class<? extends BaseDataType> vcls =
             (Class<? extends BaseDataType>) var.getClass().getComponentType();

          try
          {
             BaseDataType bdinit = BaseDataTypeFactory.instantiate(vcls).instantiateDefaultExtent();
             ArrayAssigner.assignMulti(var, bdinit);
          }
          catch (SecurityException               |
                 ReflectiveOperationException    |
                 IllegalArgumentException e)
          {
             throw new RuntimeException(e);
          }
       }

       /**
        * The method returns {@code true} if all the supplied values are "known", i.e. none of the
        * values is {@code null} or unknown ({@link #isUnknown}).
        *
        * @param   values
        *          The values to check.
        *
        * @return  see above.
        */
       public static boolean isAllKnown(BaseDataType... values)
       {
          for (BaseDataType val : values)
          {
             if (val == null || val.isUnknown())
             {
                return false;
             }
          }

          return true;
       }

       /**
        * The method returns {@code true} if all the supplied values are "known", i.e. none of the
        * values is {@code null} or unknown ({@link #isUnknown}).
        *
        * @param   value
        *          The values to check.
        *
        * @return  see above.
        */
       public static boolean isAllKnown(BaseDataType value)
       {
          return value != null && !value.isUnknown();
       }

       /**
        * Create base data type proxy of the same data type as the provided value.
        *
        * @param   value
        *          The value which should be wrapped with the proxy.
        *
        * @return  A proxy which targets the provided value.
        */
       public static BaseDataType createProxy(BaseDataType value)
       {
          return createProxy(value, value.getType().getCls());
       }

       /**
        * Create base data type proxy of a specified data type.
        *
        * @param   value
        *          The value which should be wrapped with the proxy.
        * @param   clazz
        *          The data type of the proxy.
        *
        * @return  A proxy which targets the provided value and has a specified data type.
        */
       public static BaseDataType createProxy(BaseDataType value, Class<? extends BaseDataType> clazz)
       {
          return ProxyFactory.getProxy(clazz,
                   true, // intercept any call to parent methods
                   new Class<?>[] { }, // no extra interface to consider
                   false,
                   null,
                   null,
                   new WrapperHandler(value, clazz));
       }

       /**
        * Check if a BDT is a proxy or a real value.
        *
        * @param   value
        *          The BDT to be checked
        *
        * @return  {@code true} if the provided value is a proxy
        */
       public static boolean isProxy(BaseDataType value)
       {
          return value != null && value.val() != value;
       }

       /**
        * Convenient method to initialize a base data type. If the provided value is null or
        * unknown, this value is also unknown. Otherwise, the assign method is used.
        *
        * @param   value
        *          The value to assign to this type.
        */
       public void initialize(BaseDataType value)
       {
          if (value == null || value.isUnknown())
          {
             setUnknown();
          }
          else
          {
             assign(value);
          }
       }

       /**
        * Calculate the length of a formatted value (the text form of the value)
        * using this format string.
        *
        * @param    fmt
        *           The format string.
        *
        * @return   The length in characters of the any resulting formatted
        *           value.
        */
       public int formatLength(String fmt)
       {
          if (fmt == null)
          {
             fmt = defaultFormatString();
          }

          return fmt.length();
       }

       /**
        * Determines if this instance and the given instance are equivalent. Only an object of type
        * {@code BaseDataType} is compared with this instance; objects of other types cause this test
        * to return {@code false}.
        * <p>
        * <strong>Note:</strong> Unknown values form different classes from this hierarchy are
        * considered equals in P4GL, even if they have different types (Ex: an unknown {@link integer}
        * and an unknown {@link logical}.
        * <strong>Note:</strong> Classes from this hierarchy cannot be used as keys for normal
        * java associative collections because of collisions caused by objects (Ex: only one
        * unknown can be the key in such collection). If such collection is required, use a special
        * one like {@link java.util.IdentityHashMap}.
        *
        * @param   obj
        *          The instance to compare against.
        *
        * @return  {@code true} if the objects compare equivalently; {@code false} if they do not,
        *          or if the parameter is not a {@code BaseDataType} instance.
        */
       public boolean equals(Object obj)
       {
          if (!(obj instanceof BaseDataType))
          {
             return false;
          }

          return CompareOps.equals(this, (BaseDataType) obj);
       }

       /**
        * The default implementation of this method provided by
        * <code>Object</code> is overridden here with an abstract method to force
        * subclasses to provide a concrete implementation which is consistent with
        * this class' implementation of {@link #equals}.
        *
        * @return  The hash code for this object instance.
        */
       public abstract int hashCode();

       /**
        * Computes a hash code for the given arguments, implementation of the <code>HASH-CODE</code> built-in
        * function.
        * <p>
        * This function is deterministic and order-sensitive.
        * <ul>
        *   <li>If a single argument is provided, the result is simply the individual hash of that argument.</li>
        *   <li>If multiple arguments are provided, the result is computed using a nested expression:</li>
        * </ul>
        * <pre>
        *     result = 1;
        *     for each argument arg:
        *         result = 31 * result + getIndividualHash(arg);
        * </pre>
        * <p>
        * If an argument is of type {@code decimal} and is numerically equal to an {@code integer}, it will be
        * treated as an integer to ensure compatibility: <code>HASH-CODE(5.00) == HASH-CODE(5)</code>
        * <p>
        * Unknown values consistently hash to the same constant value.
        *
        * @param   args
        *          The arguments to hash. May include any mix of base data types.
        *
        * @return  An integer hash code representing the input values.
        */
        public static integer hashCode(BaseDataType... args)
        {
           if (args == null || args.length == 0)
           {
              return new integer(0);
           }

           if (args.length == 1)
           {
              return new integer(getIndividualHash(args[0]));
           }

           int result = 31 + getIndividualHash(args[0]);

           for (int i = 1; i < args.length; i++)
           {
              result = 31 * result + getIndividualHash(args[i]);
           }

           return new integer(result);
        }

       /**
        * Computes the individual hash code for a single {@link BaseDataType} value.
        * <p>
        * - Unknown values return 0.
        * - {@code decimal} values that are numerically equal to an {@code integer} use the integer hash.
        * - All other values use their {@code hashCode()} directly.
        *
        * @param   arg
        *          the value to hash.
        *
        * @return  the individual hash code.
        */
        private static int getIndividualHash(BaseDataType arg)
        {
           if (arg == null || arg.isUnknown())
           {
              return 0;
           }

           if (arg instanceof decimal)
           {
              decimal d = (decimal) arg;
              try
              {
                 integer i = new integer(d);
                 BigDecimal dec = d.toBigDecimal();
                 if (dec != null && dec.compareTo(BigDecimal.valueOf(i.getValue())) == 0)
                 {
                    return i.hashCode();
                 }
              }
              catch (Throwable ignored)
              {
                 // Fall through
              }
              return d.hashCode();
           }

           return arg.hashCode();
        }

       /**
        * Returns the largest instance.
        *
        * @param    b
        *           The instance to compare against.
        *
        * @return   The largest instance.
        */
       public BaseDataType maximum(BaseDataType b)
       {
          if (isUnknown() || b.isUnknown())
             return instantiateUnknown();

          return (CompareOps.greaterThan(this, b)) ? this : b;
       }

       /**
        * Returns the smallest instance.
        *
        * @param    b
        *           The instance to compare against.
        *
        * @return   The smallest instance.
        */
       public BaseDataType minimum(BaseDataType b)
       {
          if (isUnknown() || b.isUnknown())
             return instantiateUnknown();

          return (CompareOps.lessThan(this, b)) ? this : b;
       }

       /**
        * This is a form of a copy constructor that makes a deep copy of the
        * current instance and returns this copy as a new instance of the same
        * class.
        *
        * @return   An instance of the same class whose data is identical to
        *           that of this instance.
        */
       public Undoable deepCopy()
       {
          return (Undoable) duplicate();
       }

       /**
        * Returns the legacy type name of the sub-class as the type was known in
        * the 4GL.  Generally, this is equivalent to an uppercased version of the
        * base name of the class file (all package information is removed). Child
        * classes can override this default behavior.
        *
        * @return   The legacy type name.
        */
       public String getTypeName()
       {
          return getType().typeName;
       }

       /**
        * Get the type
        * @return type
        */
       public abstract Type getType();

       /**
        * Reports if this instance represents the <code>unknown value</code>.
        *
        * @return   <code>true</code> if this instance is set to the
        *           <code>unknown value</code>.
        */
       public logical isUnknownValue()
       {
          return new logical(isUnknown());
       }

       /**
        * Reports if this instance represents some known value.
        *
        * @return   <code>true</code> if this instance is not set to the
        *           <code>unknown value</code>.
        */
       public logical notUnknownValue()
       {
          return new logical(!isUnknown());
       }

       /**
        * Sets the state (data and unknown value) of this instance based on the state of the passed
        * instance.
        * <p>
        * If the value is not of the same type, some form of automatic type conversion may occur, or
        * an error will be raised.
        * <p>
        * This variant is meant to handle the cases of built-in functions and methods in the 4GL
        * which have polymorphic return types (e.g. DYNAMIC-FUNCTION()). This should NOT be used for
        * non-polymorphic assignments.
        *
        * @param    value
        *           The instance from which to copy state.
        */
       public abstract void assign(BaseDataType value);

       /**
        * Convert the specified Java value to a legacy BDT instance, and assign it to this instance.
        *
        * @param    value
        *           The instance holding a Java type, compatible with BDT native types.
        */
       public void assign(jobject<?> value)
       {
          assign((BaseDataType) jobject.fromJava(value));
       }

       /**
        * Assign the given value to the current instance.
        *
        * @param    value
        *           The current value.  This is considered POLY, as it may be passed by DYNAMIC-INVOKE
        *           or other POLY functions for which their returned data type is unknown at conversion
        *           time.
        */
       public void assign(Object value)
       {
          if (value == null)
          {
             setUnknown();
             return;
          }

          if (value instanceof jobject)
          {
             assign((jobject<?>) value);
          }
          else if (value instanceof BaseDataType)
          {
             assign(value == null ? null : ((BaseDataType) value).val());
          }
          else if (value instanceof String)
          {
             assign(new character((String) value));
          }
          else if (value instanceof Number)
          {
             assign(new decimal((Number) value));
          }
          else if (value instanceof Boolean)
          {
             assign(new logical((Boolean) value));
          }
          else if (value instanceof FieldReference)
          {
             assign(((FieldReference) value).get());
          }
          else if (value instanceof PropertyReference)
          {
             assign(((PropertyReference) value).get());
          }
          else if (value.getClass().isArray())
          {
             String errMsg = "Whole-array assignment target and source must have the same extent " +
                             "unless the target is indeterminate";
             ErrorManager.recordOrThrowError(14905, errMsg, false);
          }
          else
          {
             UnimplementedFeature.missing(String.format("%s.assign(%s)", getClass(), value.getClass()));
          }
       }

       /**
        * Reports if this instance represents the <code>unknown value</code>.
        *
        * @return   <code>true</code> if this instance is set to the
        *           <code>unknown value</code>.
        */
       public abstract boolean isUnknown();

       /**
        * Sets the state of this instance's <code>unknown value</code> flag or
        * state to <code>true</code>.
        * <p>
        * <b>Warning: the data stored in this instance may be invalid after
        * calling this method.</b>
        */
       public abstract void setUnknown();

       /**
        * Does the same as standard <code>clone()</code> method but returns an
        * instance of <code>BaseDataType</code> and doesn't throw the
        * <code>CloneNotSupportedException</code>.
        *
        * @return   A clone of this instance.
        */
       public abstract BaseDataType duplicate();

       /**
        * Creates a new instance of the same type that represents the
        * <code>unknown value</code>.
        *
        * @return   An instance that represents the <code>unknown value</code>.
        */
       public abstract BaseDataType instantiateUnknown();

       /**
        * Creates a new instance of the same type that represents the default initialized value.
        *
        * @return   An instance that represents the default value.
        */
       public abstract BaseDataType instantiateDefault();

       /**
        * Creates a string representation of the instance data using the given
        * format string.  If the instance represents the
        *  <code>unknown value</code>, a '?' will be returned.
        *
        * @param    fmt
        *           The format string to use.
        *
        * @return   The formatted string.
        */
       public abstract String toString(String fmt);

       /**
        * Creates a string representation of the instance data in a form that is
        * compatible with the <code>MESSAGE</code> language statement.  If the
        * instance represents the <code>unknown value</code>, a '?' will be
        * returned.
        *
        * @return   The 'message' formatted string.
        */
       public abstract String toStringMessage();

       /**
        * Creates a string representation of the instance data using the 'export'
        * format.  If the instance represents the <code>unknown value</code>, a
        * '?' will be returned.
        *
        * @return   The 'export' formatted string.
        */
       public abstract String toStringExport();

       /**
        * Return the default display format string for this type.
        *
        * @return   The default format string.
        */
       public abstract String defaultFormatString();

       /**
        * Obtain the length (in bytes) of this BDT will use when serialized.
        *
        * @return  the length of this BDT will use when serialized.
        */
       public int getSize()
       {
          return 1;
       }

       /**
        * Retrieve the real value. This is used to ignore any proxy which is done over a value.
        *
        * @return  this value or the target value if this is a proxy.
        */
       public BaseDataType val()
       {
          return this;
       }

       /**
        * Retrieve the real value in a proxy data type. This is used to honor any proxy
        * which is done over a value, and convert the value to the needed data type.
        *
        * @return  this value or a converted value if this is a proxy.
        */
       public BaseDataType fallback()
       {
          return this;
       }

       /**
        * Identify if this data is dependent upon the context local. This identifies
        * if the data is safe to be cached in cross-session places or the data should
        * not leak from the context. If is is not safe to be shared cross-session, then
        * this should return {@code null}. If other representation is suitable for caching,
        * then this can return another object that wraps internal values that are not
        * dependent upon the context (e.g. date, date-time, date-time-tz).
        *
        * @return   Usually {@code this}. The extensions should be eager to
        *           state if they are dependent upon the context. If they are, then either
        *           {@code null} should be returned or another object that is more suitable
        *           for caching.
        */
       public Object getIndependentFromContext()
       {
          return this;
       }

       /**
        * Check if this instance can be assigned directly, without boundary checks.
        * <p>
        * This API should be overridden by specific BDT sub-classes, and is currently used in CALL's
        * OUTPUT/INPUT-OUTPUT parameter case, where data needs to be copied directly.
        *
        * @return    Always <code>false</code>.
        */
       boolean isAssignDirect()
       {
          return false;
       }

       /**
        * Get the assigner supplier which will be used to register this instance as a parameter.
        *
        * @return    See above.
        */
       Supplier<AbstractParameter> getAssigner()
       {
          return null;
       }

       /**
        * Get the default initialization for an extent variable of this type.
        *
        * @return   See above.
        */
       protected BaseDataType instantiateDefaultExtent()
       {
          return instantiateDefault();
       }

       /**
        * Throws error message in case of incompatible types for a POLY conversion.
        * (e.g. DYNAMIC-FUNCTION()).
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       protected void incompatibleTypesOnConversion()
       {
          String err = "Incompatible datatypes found during runtime conversion";
          ErrorManager.recordOrThrowError(5729, err);
       }

       /**
        * Returns true if dynamic data type conversion should not be attempted,
        * it can also throws in case of incompatible types for a POLY conversion.
        * (e.g. DYNAMIC-FUNCTION()).
        *
        * @throws   ErrorConditionException
        *           If dynamic conversion of input value is not supported.
        */
       protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
       {
          return false;
       }

       /**
        * Display error message in case of an invalid initializer for the given type.
        *
        * @param    text
        *           The string initializer for display in the error.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       protected void invalidInitializer(String text)
       {
          String err = "'%s' is not an appropriate initializer for items of type %s";
          ErrorManager.recordOrThrowError(12003, String.format(err, text, getTypeName()));
       }

       /**
        * Enum class describing all 4GL wrapper types.  This makes it easy to map the BDT wrapper subclass
        * to the 4GL type with less string manipulation/fewer map lookups.
        */
       @SuppressWarnings("javadoc")
       public static enum Type
       {
          BLOB("BLOB", blob.class),
          CLOB("CLOB", clob.class),
          CHARACTER("CHARACTER", character.class),
          COMHANDLE("COMHANDLE", comhandle.class),
          COM_HANDLE("COM-HANDLE", comhandle.class),
          DATASET_HANDLE("DATASET-HANDLE", handle.class),
          DATE("DATE", date.class),
          DATETIME("DATETIME", datetime.class),
          DATETIMETZ("DATETIMETZ", datetimetz.class),
          DATETIME_TZ("DATETIME-TZ", datetimetz.class),
          DECIMAL("DECIMAL", decimal.class),
          HANDLE("HANDLE", handle.class),
          INTEGER("INTEGER", integer.class),
          INT64("INT64", int64.class),
          JOBJECT("JOBJECT", jobject.class),
          LOGICAL("LOGICAL", logical.class),
          LONGCHAR("LONGCHAR", longchar.class),
          MEMPTR("MEMPTR", memptr.class),
          OBJECT("OBJECT", object.class),
          RAW("RAW", raw.class),
          RECID("RECID", recid.class),
          ROWID("ROWID", rowid.class),
          TABLE_HANDLE("TABLE-HANDLE", handle.class),
          UNKNOWN("UNKNOWN", unknown.class);

          /** The 4GL-compatible type name. */
          private final String typeName;

          /** The BDT sub-class that implements the type. */
          private final Class<? extends BaseDataType> cls;

          /** Types by type name map. */
          private static final Map<String, Type> VALS =
          Collections.unmodifiableMap(new CaseInsensitiveHashMap<String, Type>()
          {
             { Arrays.stream(Type.values()).forEach(t -> put(t.getTypeName(), t)); }
          });

          /**
           * Constructor.
           *
           * @param    typeName
           *           type name
           * @param    cls
           *           class
           */
          private Type(String typeName, Class<? extends BaseDataType> cls)
          {
             this.typeName = typeName;
             this.cls = cls;
          }

          /**
           * Get the class instance by type name.
           *
           * @param     typeName
           *            type name
           *
           * @return    Type instance or <code>null</code> if unknown
           */
          public static Type of(String typeName)
          {
             return typeName == null ? null : VALS.get(typeName);
          }

          /**
           * Get type name.
           *
           * @return   The type name.
           */
          public String getTypeName()
          {
             return typeName;
          }

          /**
           * Get the class.
           *
           * @return   The class which implements this type.
           */
          public Class<? extends BaseDataType> getCls()
          {
             return cls;
          }
       }

       /**
        * An invocation handler dedicated for BDT proxy. This will wrap a provided value which
        * should pass data type checks. This means that such proxy can pretend to be a character,
        * but under the hood to refer a decimal. This can help to implement 4GL relaxed constraints
        * which in Java are naturally hard constraints. For instance, the 4GL return data type check
        * is ignored if the returning value is provided by a dynamic-function. In Java, we can
        * emulate the bypass of a return data type check by encapsulating the value into such proxy.
        */
       static class WrapperHandler
       implements InvocationHandler
       {
          /** The data type of the proxy */
          private Class<? extends BaseDataType> proxyDataType;

          /** The underlying value which is proxied */
          private BaseDataType target;

          /** A fallback value of proxy's data type which is assigned from target */
          private BaseDataType fallbackValue;

          /** The target has the same data type as the proxy */
          private boolean identity;

          /**
           * Basic constructor.
           * @param   target
           *          The underlying value which is proxied
           * @param   proxyDataType
           *          The data type of the proxy
           */
          WrapperHandler(BaseDataType target, Class<? extends BaseDataType> proxyDataType)
          {
             this.target = target;
             this.proxyDataType = proxyDataType;
             this.identity = proxyDataType.isAssignableFrom(target.getClass());
          }

          /**
           * The invoke method of the invocation handler. This should allow only {@code val} to identify
           * the underlying value and {@code fallback} to trigger an assign of the underlying value
           * to proxy's data type. Any other method is delegated to the fallback value to keep availability.
           * The fallback plan should be avoided, usually, by a proxy check in the calling context.
           */
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
          {
             if (method.getName().equals("val"))
             {
                return target.val();
             }

             try
             {
                if (method.getName().equals("fallback"))
                {
                   initFallback();
                   return fallbackValue;
                }

                // unexpected situation - try to make it work
                if (!identity)
                {
                   LOG.severe("Using unsafe fallback plan for BDT value proxy. This means that there was no proxy check!");
                }
                initFallback();
                return method.invoke(fallbackValue, args);
             }
             catch (Exception ex)
             {
                Throwable ex2 = ex;
                while (ex2 != null && !(ex2 instanceof ConditionException))
                {
                   ex2 = ex2.getCause();
                }

                if (ex2 == null)
                {
                   // wrap in a RunnableException and rethrow
                   throw new RuntimeException(ex);
                }
                else
                {
                   // ex2 is instance of ConditionException because of a 4GL validation/conversion
                   // error has occurred: propagate, will be handled elsewhere
                   throw (ConditionException) ex2;
                }
             }
          }

          /**
           * Helper method to build a fallback value. This process can result in errors, so the fallback
           * value is best to have a lazy initialization.
           * @throws ReflectiveOperationException
           */
          private void initFallback()
          throws ReflectiveOperationException
          {
             if (fallbackValue == null)
             {
                fallbackValue = BaseDataTypeFactory.instantiate(proxyDataType);
                fallbackValue.assign(target);
             }
          }

       }
    }