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);
         }
      }
       
   }
}