int64.java

/*
** Module   : int64.java
** Abstract : Progress 4GL compatible 64-bit integer object
**
** Copyright (c) 2012-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 OM  20121217 Created initial version.
** 002 CA  20130123 Added c'tor which build a new instance from a BaseDataType
**                  value, if possible. This should be used only when
**                  constructing an instance using the value returned by a
**                  DYNAMIC-FUNCTION call; 4GL does some special casting in
**                  this case, which is not implemented yet.
** 003 CS  20130128 Added conversion support for memptr related functions for task #1635.
** 004 GES 20130128 Removed getTypeName() which is already implemented in a parent class.
** 005 GES 20130322 Fixed the BDT c'tor and modified the assign(BDT,boolean) to be the
**                  common location for the _POLY morphing. This is safe because all current
**                  usage would have generated a ClassCastException if that method was ever used
**                  with an invalid type. Now, instead of the CCE, the code will morph the value
**                  as is possible. The morphing code still needs to be finished.
** 006 OM  20130328 Added missing runtime implementation. Made this class the base class for 
**                  integer class and sharing common code via inheritance.
** 007 OM  20130527 Fixed toString() method for particular case of Long.MIN_VALUE.
**                  Added error message when datetime/tz was passed to int64 constructor.
**                  Added patch for converting to timezone '+HH:MM' special format.
** 008 OM  20130711 Fixed setValue(String) when in silent mode.
** 009 CA  20131001 Eliminated custom group/dec separators when format is used. In this case, they
**                  must default to comma (,) for group and dot (.) for decimal separator.
** 010 GES 20131114 Minor optimization of assignment code.
** 011 HC  20140213 Added precise conversion to BigDecimal.
** 012 ECF 20150715 Replace StringBuffer with StringBuilder.
** 013 CA  20151222 Added instantiateDefaultExtent.
** 014 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 015 GES 20160617 Added unknown literal processing for the polymorphic assign case.
** 016 ECF 20160614 Implemented faster copy constructors.
** 017 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.
** 018 GES 20171206 Removed explicit bypass on pending error (new silent error mode).
** 019 OM  20180219 Fixed NPE in setValue() for null parameter (SQL access).
** 020 CA  20181029 Assignments incoming from CALL's dynamic invoke are not bound-check.
** 021 OM  20190206 Added implementation of field size needed by record-length.
** 022 CA  20190927 Added support for direct Java access from 4GL code.
** 023 GES 20200520 Added 4GL enum constructor.
**     GES 20200603 Changed the checkBounds() signature to make it more reusable.
**     ME  20200528 Add assign for "legacy" object - resource id.
** 024 IAS 20201007 Added Type enum.
**     OM  20201120 Avoid construction of supplementary, unneeded wrapping objects.
**     CA  20210413 An integer value can be built from a raw's bytes.
** 025 ME  20210504 Added isIncompatibleTypesOnConversion override for invalid POLY conversion.
**     CA  20210816 Fixed toString() for "+HH:MM" format - check must be case-insensitive.
**     CA  20220203 Only assignment from decimal values can be out-of-bounds.
**     AL2 20220319 Added value proxy check in assign.
** 026 CA  20230215 'duplicate()' method returns the real type instead of BDT.
** 027 ICP 20250123 Added int64 constant, static and dynamic cache support - these are immutable instances
**                  which must not 'escape' from FWD runtime, and only used via assignment or as read-only.
*/

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

import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.security.ContextLocal;
import com.goldencode.p2j.util.logging.*;

/**
 * A class that represents a Progress 4GL compatible 64-bit integer object whose data is mutable.
 * This class provides the basic wrapping of the 64-bit integer primitive data, instantiation,
 * type conversion, <code>unknown</code> value representation and the core comparison logic.
 * <p>
 * All Progress language features related to integer/decimal data types are implemented in the
 * {@link MathOps} class (except comparison logic which is in {@link CompareOps}. All Progress
 * integer/decimal features are supported including all functionality that can be accessed
 * via operators and built-in Progress functions.
 *
 * It acts as storage for all internal intermediary results of arithmetic operations.
 * The implementation uses 64-bit signed integer storage.
 * As this is the base class for <code>integer</code> it shares the common code and data.
 *
 * @author    OM
 */
public class int64
extends NumberType
{
   /** Logger */
   private static final CentralLogger log = CentralLogger.get(int64.class);

   /** Number of seconds in a minute. */
   protected static final int SECS_IN_A_MIN = 60;

   /** Number of seconds in an hour. */
   protected static final int SECS_IN_AN_HOUR = 60 * SECS_IN_A_MIN;

   /** Number of seconds in a day (leap minutes aren't considered). */
   protected static final int SECS_IN_A_DAY = 24 * SECS_IN_AN_HOUR;

   /** Smallest big decimal value that can be assigned. */
   private static final BigDecimal LOWER_BOUND = new BigDecimal(Long.MIN_VALUE);
   
   /** Largest big decimal value that can be assigned. */
   private static final BigDecimal UPPER_BOUND = new BigDecimal(Long.MAX_VALUE);

   /** Default maximum number of cached int64Constant instances in the static cache*/
   private static final int DEFAULT_STATIC_CACHE_SIZE = 1024;

   /** Default maximum number of cached int64Constant instances in the dynamic cache */
   private static final int DEFAULT_DYNAMIC_CACHE_SIZE = 512;

   /** Singleton and immutable for the int64 UNKNOWN value. */
   public static final int64Constant UNKNOWN = new int64Constant();;

   /** Stores context-local state variables. */
   private static ContextContainer work = new ContextContainer();

   /** Static cache for the singleton and immutable int64 values. */
   private static int64Constant[] STATIC_CACHE;

   /** Dynamic cache for the singleton and immutable int64 values. */
   private static int64Constant[] DYNAMIC_CACHE;

   /**
    * Stores the value of this instance. The contents of this member are invalid if the 
    * <code>unknown</code> flag is <code>true</code>.
    */
   protected long value = 0;

   /**
    * Stores the state of whether or not this instance represents the <code>unknown value</code>,
    * independent of the state of this integer's value.
    */
   protected boolean unknown = true;

   /**
    * Default constructor which creates an instance representing the <code>unknown value</code>.
    */
   public int64()
   {
   }

   /**
    * This is a special c'tor which should be used only when converting the
    * value returned by a function or method with polymorphic return type into the
    * expected type (i.e. DYNAMIC-FUNCTION()).  In such cases, the 4GL does some
    * automatic type conversion (see {@link #assign(BaseDataType)}).
    * 
    * @param    value
    *           The value to be used for this instance.
    */
   public int64(BaseDataType value)
   {
      initialize(value);
   }

   /**
    * Constructs an instance after casting the parameter into the internal
    * representation of this class.  No loss of precision will occur.
    * <p>
    * It is not possible to yield an <code>unknown value</code> from this
    * constructor without calling the method {@link #setUnknown} separate.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(long value)
   {
      this.unknown = false;
      this.value = value;
   }

   /**
    * Constructs an instance after casting the parameter into the internal
    * representation of this class.  Loss of precision may occur in this
    * conversion (all digits to the right of the decimal point will be
    * truncated).
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(double value)
   {
      setValue(new decimal(value));
   }

   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * When the parameter is <code>true</code> the resulting integer value
    * will be 1. Otherwise the value is 0.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(boolean value)
   {
      setValue(value);
   }
   
   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.  No loss of precision will
    * occur.
    * <p>
    * If the parameter's represents the <code>unknown value</code>, this
    * instance will also represent the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(int64 value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof int64)
         {
            value = (int64) value.val();
         }
         else
         {
            initialize(value.val());
            return;
         }
      }
      this.unknown = value.isUnknown();
      if (!isUnknown())
      {
         this.value = value.value;
      }
   }

   /**
    * Constructs an instance after casting the parameter into the internal representation of this 
    * class. No loss of precision will occur.
    * <p>
    * If the parameter's represents the <code>unknown value</code>, this instance will also 
    * represent the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(integer value)
   {
      this((int64) value);
   }

   /**
    * Constructs an instance after casting the parameter into the internal representation of this 
    * class. Loss of precision may occur in this conversion (all digits to the right of the 
    * decimal point will be truncated).
    * <p>
    * If the parameter's represents the <code>unknown value</code>, this instance will also 
    * represent the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(decimal value)
   {
      assign(value);
   }
   
   /**
    * Constructs an instance wth the given enum's value.
    * <p>
    * If the parameter is {@code unknown value} the resulting instance is the {@code unknown value}.
    *
    * @param    enm
    *           The enum whose value to be used for this instance.
    */
    public int64(object<? extends LegacyEnum> enm)
   {
      if (enm.isUnknown())
      {
         setUnknown();
      }
      else
      {
         setValue(enm.ref()._getValue());
      }
   }
   
   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * If the parameter's represents the <code>unknown value</code>, this
    * instance will also represent the <code>unknown value</code>.
    * <p>
    * When the parameter is <code>true</code> the resulting integer value
    * will be 1. Otherwise the value is 0.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(logical value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof logical)
         {
            value = (logical) value.val();
         }
         else
         {
            initialize(value.val());
         }
      }
      setValue(value);
   }

   /**
    * Constructs an instance after converting the string representation
    * of a number into an integer.  Due to rounding, a loss of precision
    * may occur in this conversion (all digits to the right of the decimal
    * point will be truncated).
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(String value)
   {
      setValue(value);
   }

   /**
    * Constructs an instance after converting the string representation
    * of a number into an integer.  Due to rounding, a loss of precision
    * may occur in this conversion (all digits to the right of the decimal
    * point will be truncated).
    * <p>
    * If the parameter is <code>unknown value</code> the resulting integer is 
    * the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(character value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof character)
         {
            value = (character) value.val();
         }
         else
         {
            initialize(value.val());
            return;
         }
      }
      if (value.isUnknown())
      {
         setUnknown();
      }
      else
      {
         setValue(value.getValue());
      }
   }

   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * If the parameter's represents the <code>unknown value</code>, this
    * instance will also represent the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(date value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         if (value.val() instanceof date)
         {
            value = (date) value.val();
         }
         else
         {
            initialize(value.val());
            return;
         }
      }
      if (value.isUnknown())
      {
         setUnknown();
      }
      else if (value instanceof datetime)
      {
         ErrorManager.recordOrShowError(
               5729, "Incompatible datatypes found during runtime conversion", false);
      }
      else 
      {
         setValue(value.longValue());
      }
   }

   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * If the parameter is <code>null</code>, this instance will represent
    * the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(Number value)
   {
      if (value == null)
      {
         setUnknown();
      }
      else
      {
         setValue(value.longValue());
      }
   }

   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * If the parameter is <code>null</code>, this instance will represent
    * the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(Boolean value)
   {
      if (value == null)
      {
         setUnknown();
      }
      else
      {
         setValue(value.booleanValue());
      }
   }

   /**
    * Constructs an instance after copying the parameter's data into the
    * internal representation of this class.
    * <p>
    * If the parameter is <code>null</code>, this instance will represent
    * the <code>unknown value</code>.
    *
    * @param   value
    *          The value to be used for this instance.
    */
   public int64(Date value)
   {
      this(new date(value));
   }

   /**
    * Initializes the cache using CacheManager. The default value of the cache
    * is used when there is no size available from the configuration.
    */
   public static void initializeCache()
   {
      STATIC_CACHE = CacheManager.createArrayCache(int64.class,
                                            "static-cache",
                                            DEFAULT_STATIC_CACHE_SIZE,
                                            int64Constant.class);
      for (int i = 0; i < STATIC_CACHE.length; i++)
      {
         STATIC_CACHE[i] = new int64Constant((long) i);
      }
      DYNAMIC_CACHE = CacheManager.createArrayCache(int64.class,
                                                   "dynamic-cache",
                                                   DEFAULT_DYNAMIC_CACHE_SIZE,
                                                   int64Constant.class);
   }

   /**
    * Returns an int64Constant instance. Uses cached instances for the first n values, where n is the
    * cache size that can be configured in directory.xml.
    *
    * @param   value
    *          The value of the int64.
    * @return  An int64Constant instance.
    */
   public static int64 of(Long value)
   {
      if (value == null)
      {
         return UNKNOWN;
      }

      if (STATIC_CACHE == null)
      {
         return new int64Constant(value);
      }
      // Check static cache
      if (value >= 0 && value < STATIC_CACHE.length)
      {
         return STATIC_CACHE[Math.toIntExact(value)];
      }

      if (DYNAMIC_CACHE == null)
      {
         return new int64Constant(value);
      }
      // Check dynamic cache
      int index = (int)Math.abs(value % DYNAMIC_CACHE.length);
      synchronized (DYNAMIC_CACHE)
      {
         int64Constant cachedInstance = DYNAMIC_CACHE[index];

         if (cachedInstance != null && cachedInstance.intValue() == value)
         {
            return cachedInstance;
         }

         // Cache miss - create and replace the entry in dynamic cache
         int64Constant newInstance = new int64Constant(value);
         DYNAMIC_CACHE[index] = newInstance;
         return newInstance;
      }
   }

   /**
    * Get the type
    * @return type
    */
   public Type getType()
   {
      return Type.INT64;
   }

   /**
    * Returns the largest instance from the list. This list may not include
    * <code>null</code> values.
    *
    * @param   list
    *          The list of decimals and/or integers to check.
    *
    * @return  The largest instance or <code>unknown value</code> if any entries in the list are 
    *          unknown or <code>null</code> if an empty list is passed.
    */
   public static int64 maximum(NumberType ... list)
   {
      NumberType num = (NumberType) CompareOps.maximum(list);
      if (num instanceof int64)
      {
         // this includes the particular num.isUnknown() case
         return (int64) num.duplicate();
      }
      return num.isUnknown() ? new int64() : new int64(num.longValue());
   }

   /**
    * Returns the smallest instance from the list. This list may not include
    * <code>null</code> values.
    *
    * @param   list
    *          The list of decimals and/or integers to check.
    *
    * @return  The smallest instance or <code>unknown value</code> if any entries in the list are 
    *          unknown or <code>null</code> if an empty list is passed.
    */
   public static int64 minimum(NumberType ... list)
   {
      NumberType num = (NumberType) CompareOps.minimum(list);
      if (num instanceof int64)
      {
         // this includes the particular num.isUnknown() case
         return (int64) num.duplicate();
      }
      return num.isUnknown() ? new int64() : new int64(num.intValue());
   }

   /**
    * Dynamically builds the default format string for this given number type
    * which honors the given group and decimal separators.
    *
    * @return   The default format string for this type of number.
    */
   @Override
   public String buildDefaultFormat()
   {
      StringBuilder sb = new StringBuilder("->");
      sb.append(FORMAT_GROUP_SEP).append(">>>").append(FORMAT_GROUP_SEP).append(">>9");
      
      return sb.toString();
   }

   /**
    * Returns the default format string for this given number type (which
    * honors the given group and decimal separators), building that format
    * and caching it if this is the first call in this context.
    *
    * @return   The default format string for this type of number.
    */
   @Override
   public String obtainDefaultFormat()
   {
      WorkArea wa = work.obtain();
      
      if (wa.defFmt == null)
      {
         wa.defFmt = buildDefaultFormat();
      }
      
      return wa.defFmt;
   }

   /**
    * Returns the export format string for this given number type which
    * honors the given group and decimal separators.
    *
    * @return   Always <code>null</code>.
    */
   @Override
   public String buildExportFormat()
   {
      return null;
   }

   /**
    * An equality test which handles the most common cases with the least
    * amount of overhead.  The superclass implementation is invoked otherwise.
    * <p>
    * This implementation is consistent with {@link #hashCode()}.
    * 
    * @see com.goldencode.p2j.util.BaseDataType#equals(java.lang.Object)
    */
   public boolean equals(Object o)
   {
      if (this == o)
      {
         return true;
      }

      if (o instanceof int64)
      {
         int64 that = (int64) o;

         boolean u1 = this.unknown;
         boolean u2 = that.unknown;

         if (u1 && u2)
         {
            return true;
         }

         if (u1 || u2)
         {
            return false;
         }

         return (this.value == that.value);
      }

      return super.equals(o);
   }

   /**
    * Hash code implementation which is consistent with {@link BaseDataType#equals}.
    *
    * @return  Hash code value for this object instance.
    */
   public int hashCode()
   {
      if (unknown)
      {
         return 19;
      }
      
      return (int)(41 + value);
   }

   /**
    * Returns the value of this instance as a <code>long</code>.  No loss of
    * precision will occur in this conversion.
    * <p>
    * This method will fail if the instance represents the
    * <code>unknown value</code>.
    *
    * @return   The value cast into a <code>long</code>.
    *
    * @throws   NullPointerException
    *           If this instance represents the <code>unknown value</code>.
    */
   @Override
   public long longValue()
   throws NullPointerException
   {
      if (unknown)
      {
         throw new NullPointerException("Unknown value cannot be converted to long.");
      }

      return value;
   }

   /**
    * Returns the value of this instance as an <code>int</code>. Loss of
    * precision may occur in this conversion if number exceeds int limits
    * <p>
    * This method will fail if the instance represents the <code>unknown value</code>.
    *
    * @return   The value as an <code>int</code>.
    *
    * @throws   NullPointerException
    *           If this instance represents the <code>unknown value</code>.
    */
   @Override
   public int intValue()
   throws NullPointerException
   {
      if (unknown)
      {
         throw new NullPointerException("Unknown value cannot be converted to int.");
      }

      return (int) value;
   }

   /**
    * Returns the value of this instance as a <code>double</code>. Some loss
    * of precision may occur in this conversion. Use {@linkplain #toBigDecimal()}
    * if keeping the precision is the concern.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * returned <code>double</code> will be set to <code>NaN</code>.
    *
    * @return   The value cast into a <code>double</code>.
    */
   @Override
   public double doubleValue()
   {
      if (unknown)
      {
         return (new decimal()).doubleValue();
      }

      return (double) value;
   }

   /**
    * Returns the value of this instance as a <code>BigDecimal</code>. No loss
    * of precision will occur in this conversion.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * return value will be <code>null</code>.
    *
    * @return   The <code>BigDecimal</code> instance, see above for details.
    */
   public BigDecimal toBigDecimal()
   {
      return isUnknown() ? null : new BigDecimal(value);
   }
   
   /**
    * Increments the value of this instance by 1.
    * <p>
    * If this instance represents the <code>unknown value</code>, nothing is done.
    */
   @Override
   public void increment()
   {
      if (unknown)
      {
         return;
      }

      setValue(value + 1);
   }

   /**
    * Decrements the value of this instance by 1.
    * <p>
    * If this instance represents the <code>unknown value</code>, nothing is done.
    */
   @Override
   public void decrement()
   {
      if (unknown)
      {
         return;
      }

      setValue(value - 1);
   }

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

   /**
    * Sets the state of this instance's <code>unknown value</code> state to <code>true</code>.
    */
   @Override
   public void setUnknown()
   {
      if (!unknown)
      {
         checkUndoable(true);
      }

      unknown = true;
   }

   /**
    * 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>.
    */
   @Override
   public BaseDataType instantiateUnknown()
   {
      return new int64();
   }

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

   /**
    * 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.
    */
   @Override
   public int64 duplicate()
   {
      return new int64(this);
   }

   /**
    * Returns the value of this instance as an <code>long</code>.  No loss
    * of precision will occur in this conversion.  This is the same as the {@link #longValue}.
    * <p>
    * If this instance represents the <code>unknown value</code>, the
    * returned <code>long</code> is <b>not valid</b>!
    *
    * @return   The value as an <code>long</code>.
    */
   public long getValue()
   {
      return isUnknown() ? 0L : longValue();
   }

   @Override
   protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
   {
      // invalid conversion is being thrown even if unknown
      if (value instanceof longchar || value instanceof clob || value instanceof blob) 
      {
         incompatibleTypesOnConversion();
      }
      
      return super.isIncompatibleTypesOnConversion(value);
   }
   
   /**
    * 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 type <code>NumberType</code>, the following automatic type
    * conversion will occur:
    * <p>
    * <ul>
    * <li><code>unknown</code> values (or literal) will set this object to <code>unknown</code>
    * <li><code>NumberType</code> values will be assigned using usual conversion
    * <li><code>handle</code> will use the <code>hashCode()</code> method to obtain an integer. 
    *          <b>The reverse operation is not valid</b> and this should only be used for viewing
    *          handle values.
    * <li><code>date</code> will convert to the number of dates passed from 4GL day 0.
    * <li><code>logical</code>. <code>true</code> values will convert to 1 while 
    *          <code>false</code> are converted to 0.
    * <li><code>character</code> and <code>longchar</code>. The conversion implies parsing the 
    *          string assuming the number is written in base 10.
    * <li>any other data type will raise exception 
    * </ul>
    * <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 void assign(BaseDataType value)
   {
      if (value == null)
      {
         setUnknown();
         return;
      }
      
      // the value may be a proxy; make sure to unwrap and work with the real value
      value = value.val();
      if (value.isUnknown())
      {
         setUnknown();
         return;
      }
      
      if (value instanceof NumberType)
      {
         // first check all numerical cases (integer, int64, decimal)
         setValue((NumberType) value);
      }
      else if (value instanceof handle)
      {
         /* a little workaround here in order to benefit from fact that when computing 
            handle.toString() it will return a string representation that is automatically 
            save off the reference in a weak hash map so that later, it can be converted back 
            via the widgetHandle() method
          */
         setValue(value.toString());
      }
      else if (value instanceof memptr)
      {
         // TODO this type of data is strange enough in 4GL: 
         // * dynamically calling int64 with memptr will fail at runtime if no-error isn't specified
         // * statically calling with memptr parameter gives some strange results despite the 
         //       fact that documentation explicitly mention that RAW / MEMPTR and LONGCHAR 
         //       are not supported for conversion
      }
      else if (value instanceof logical)
      {
         // boolean case ?
         setValue(((logical) value).booleanValue());
      }
      else if (value instanceof date && !(value instanceof datetime))
      {
         // date, but not datetime/datetime-tz ?
         setValue(((date) value).longValue());
      }
      else if (value instanceof character || value instanceof longchar)
      {
         // try to convert to an integer using long parsing
         String javaVal = (value instanceof character) ?
                          ((character) value).toStringMessage() : ((longchar) value).getValue();
         setValue(javaVal);
         // despite the fact that documentation explicitly mention that RAW / MEMPTR and LONGCHAR
         // are not supported for conversion, the implementation handles LONGCHAR as normal 
         // CHARACTER, giving no warnings whatsoever.
      }
      else if (value instanceof object)
      {
         assign((object<?>) value);
      }
      else if (value instanceof jobject)
      {
         assign((jobject<?>) value);
      }
      else if (value instanceof BinaryData)
      {
         byte[] bytes = ((BinaryData) value).getByteArray();
         long val = 0;
         // only the first 8 bytes are used
         for (int i = 0; i < Math.min(8, bytes.length); i++)
         {
            val += ((long) bytes[i] & 0xffL) << (8 * i);
         }
         assign(val);
      }
      else
      {
         ErrorManager.recordOrThrowError(5729,
               "Incompatible datatypes found during runtime conversion");
      }
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the
    * state of the passed instance.
    *
    * @param    value
    *           The instance from which to copy state.
    */
   public void assign(NumberType value)
   {
      // the value may be a proxy; make sure to unwrap and work with the real value
      if (BaseDataType.isProxy(value))
      {
         assign(value.val());
         return;
      }
      setValue(value);
   }

   /**
    * Sets the state (data) of this instance based on the given data.
    *
    * @param    value
    *           The data from which to copy state.
    */
   public void assign(long value)
   {
      assign(new int64(value));
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the
    * state of the passed data.
    *
    * @param    value
    *           The data from which to copy state. <code>NaN</code> will
    *           force this instance to unknown value.
    */
   public void assign(double value)
   {
      assign(new decimal(value));
   }

   /**
    * Sets the state (data and unknown value) of this instance based on the
    * state of the passed instance.
    *
    * @param    value
    *           The instance from which to copy state.
    */
   public void assign(Undoable value)
   {
      if (value instanceof BaseDataType)
      {
         assign((BaseDataType)value);
      }
      else
      {
         throw new RuntimeException("Assignment of " + value.getClass() +
               " to int64 not implemented.");
      }
   }

   /**
    * Compares this instance with the specified instance and returns -1
    * if this instance is less than the specified, 0 if the two instances
    * are equal and 1 if this instance is greater than the specified
    * instance.  This is the implementation of the <code>Comparable</code>
    * interface.
    * <p>
    * The algorithm will fail to give meaningful results in the case where
    * one tries to sort against other objects that do not represent compatible
    * values.
    *
    * @param    obj
    *           The instance to compare against.
    *
    * @return   &le;-1, 0, or &ge;1 depending on if this instance is less
    *           than, equal to or greater (respectively) than the specified
    *           instance <code>obj</code>.
    */
   @Override
   public int compareTo(Object obj)
   {
      //check for unknown first:
      if (obj instanceof BaseDataType)
      {
         BaseDataType bdt = (BaseDataType)obj;
         if (this.isUnknown() && bdt.isUnknown())
         {
            // both are unknowns
            return 0;
         }
         if (this.isUnknown() || bdt.isUnknown())
         {
            // only one is unknown
            return -1;
         }
      }

      // check for integer type arguments:
      if (obj instanceof int64)
      {
         long otherVal = ((int64) obj).value;
         if (otherVal > value)
         {
            return -1;
         }
         return value == otherVal ? 0 : 1;
      }
      
      // check remaining cases:
      double d;
      if (obj instanceof decimal)
      {
         d = ((decimal) obj).doubleValue();
      }
      else if (obj instanceof Long)
      {
         d = ((Long) obj).doubleValue();
      }
      else if (obj instanceof Integer)
      {
         d = ((Integer) obj).doubleValue();
      }
      else if (obj instanceof Double)
      {
         d = ((Double) obj).doubleValue();
      }
      else
      {
         // different from anything else
         return -1;
      }

      int result = -1;
      // no epsilon tests are needed since a decimal class should always have a rounded value 
      // (whose precision is far less than the Java epsilon value)!
      if (value > d)
      {
         result = 1;
      }
      else if (value == d)
      {
         result = 0;
      }

      return result;
   }

   /**
    * Returns the larger of the current or passed-in instance.
    *
    * @param    i
    *           The instance to compare against.
    *
    * @return   The largest instance.
    */
   public int64 maximum(int64 i)
   {
      return (int64) super.maximum(i);
   }

   /**
    * Returns the smaller of the current or passed-in instance.
    *
    * @param    i
    *           The instance to compare against.
    *
    * @return   The smallest instance.
    */
   public int64 minimum(int64 i)
   {
      return (int64) super.minimum(i);
   }

   /**
    * Creates a string representation of the instance data using the default
    * Progress 4GL format of '-&gt;,&gt;&gt;&gt;,&gt;&gt;9'.
    *
    * @return   The formatted string.
    */
   public String toString()
   {
      return toString(null);
   }

   /**
    * 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 String toStringMessage()
   {
      return toStringExport();
   }

   /**
    * Creates a string representation of the instance data using the 'export' format.
    *
    * @return   The 'export' formatted string.
    */
   public String toStringExport()
   {
      if (isUnknown())
      {
         return "?";
      }
      
      return Long.toString(longValue());
   }

   /**
    * Creates a string representation of the instance data using the user
    * specified format string (Progress 4GL compatible) or if no such
    * string is provided, the default format of '-&gt;,&gt;&gt;&gt;,&gt;&gt;9'.  All Progress
    * formatting is supported including the 'HH:MM' or 'HH:MM:SS' time
    * format for integers.
    *
    * @param   fmt
    *          The Progress 4GL format string or <code>null</code> if the default format is 
    *          to be used.
    *
    * @return   The formatted string.
    */
   public String toString(String fmt)
   {
      if (isUnknown())
      {
         return "?";
      }

      String result = null;

      if (fmt == null)
      {
         fmt = getDefaultFormat(this);
      }
      else
      {
         int len = fmt.length();

         // test for the special case of a "timezone" value
         if ("+hh:mm".equalsIgnoreCase(fmt))
         {
            StringBuilder sb = new StringBuilder("+");
            long hours = this.value / 60;
            long minutes = this.value % 60;
            
            sb.append(String.format("%02d", hours));
            sb.append(':');
            sb.append(String.format("%02d", minutes));
            
            result = sb.toString();
         }
         
         // test for the special case of a "time" value
         if (len > 4 && fmt.substring(0, 5).equalsIgnoreCase("hh:mm"))
         {
            // it IS a time value!
            
            // strip off excess days to get a "time of day"
            long tod = longValue() % SECS_IN_A_DAY;

            // this is now the number of seconds since midnight, if it is a
            // non-negative number
            long time = tod;

            // negative numbers count backwards
            if (time < 0)
            {
               time = SECS_IN_A_DAY + time;
            }
            // at this point time is a positive number

            boolean secs = false;
            boolean ampm = (fmt.indexOf('a') > 0 || fmt.indexOf('A') > 0);
            int     last = 5;

            if (len > 7 && fmt.substring(5, 8).equalsIgnoreCase(":ss"))
            {
               secs = true;
               last = 8;
            }

            // calculate the number of hours
            long numHrs = time / SECS_IN_AN_HOUR;

            // strip off hours to leave only minutes and seconds
            time %= SECS_IN_AN_HOUR;

            // calculate the number of minutes
            long numMin = time / SECS_IN_A_MIN;

            // strip off minutes to leave only seconds
            long numSec = time % SECS_IN_A_MIN;
            
            StringBuilder sb = new StringBuilder();
            
            // output the hours
            if (ampm)
            {
               long hr = numHrs > 12 ? numHrs - 12 : numHrs;

               if (hr == 0)
               {
                  // midnight is 12 am
                  hr = 12;
               }

               if (hr < 10)
               {
                  // hours are space padded instead of zero padded
                  sb.append(String.format(" %d", hr));
               }
               else
               {
                  sb.append(String.format("%02d", hr));
               }
            }
            else
            {
               sb.append(String.format("%02d", numHrs));
            }

            // output the separator and the minutes (zero padded)
            sb.append(String.format(":%02d", numMin));

            if (secs)
            {
               // output the separator and the seconds (zero padded)
               sb.append(String.format(":%02d", numSec));
            }

            // add any stuff at the end of the string
            if (len > last)
            {
               sb.append(fmt.substring(last));
            }

            // fixup the AM/PM result like Progress would (even if it is just
            // in the middle of random "extra" text)
            if (ampm && numHrs > 11)
            {
               int replace = sb.indexOf("a");
               if (replace >= 0) 
               {
                  sb.setCharAt(replace, 'p'); 
               }
               else
               {
                  sb.setCharAt(sb.indexOf("A"), 'P');
               }
            }
            
            result = sb.toString();
         }
      }

      if (result == null) 
      {
         if (longValue() != Long.MIN_VALUE)
         {
            result = toString(Long.toString(Math.abs(longValue())), "", longValue() < 0, fmt);
         }
         else 
         {
            // special case, Math.abs(Long.MIN_VALUE) == Long.MIN_VALUE ! 
            result = toString("9223372036854775808", "", true, fmt);
         }
      }

      return result;
   }
   
   /**
    * Obtain the length (in bytes) of this BDT will use when serialized. 
    *
    * @return  the length of this BDT will use when serialized.
    */
   @Override
   public int getSize()
   {
      if (isUnknown() || value == 0)
      {
         return 1;
      }
      else if (-127L <= value && value <= 127L) // 0x7F note: 7F, the other are 7E...
      {
         return 2;
      } 
      else if (-32_512L <= value && value <= 32_511L) // 0x7EFFL
      {
         return 3;
      }
      else if (-8_323_072L <= value && value <= 8_323_071L) //  7E_FFFF
      {
         return 4;
      }
      else if (-2_147_483_648L <= value && value <= 2_147_483_647L) //  7FFF_FFFF note: 7F.., the other are 7E...
      {
         return 5;
      }
      else if (-545_460_846_592L <= value && value <= 545_460_846_591L) //  7E_FFFF_FFFF
      {
         return 6;
      }
      else if (-139_637_976_727_552L <= value && value <= 139_637_976_727_551L) //  7EFF_FFFF_FFFF
      {
         return 7;
      }
      else if (-35_747_322_042_253_312L <= value && value <= 35_747_322_042_253_311L) // 7E_FFFF_FFFF_FFFF
      {
         return 8;
      }
      else 
      {
         return 9;
      }
   }
   
   /**
    * Worker method for getBits, this returns the number of bits from the 
    * given position as an <code>integer</code>.
    * 
    * @param  pos
    *         The 1-based index position to set, must be &gt; 0.
    * @param  len
    *         The number of bytes to take into consideration when performing the operation.
    *         
    * @return The number of bits from the given position as an <code>integer</code>.
    */
   @Override
   public int64 getBitsWorker(double pos, double len)
   {
      // handle exception cases first 
      if (isUnknown())
      {
         // cannot set bits on unknown value
         return this;
      }
      if (pos < 1 || pos > 64 || len < 1 || len > 64 || len + pos > 65)
      {
         // these conditions assure that we are manipulating only the available 64 bits
         ErrorManager.recordOrThrowError(
                 9219,
                 "Both the position and num-bits arguments for PUT-BITS and GET-BITS must " + 
                       "be greater than 0 and less than 65. Their sum must be less than 66");
         return new int64();
      }

      // convert everything to native long
      long valCopy = this.value;
      long l_pos   = (long) pos - 1; // pos is 0-based
      long l_len   = (long) len;
      long l_rest  = 64 - l_pos - l_len; // number of bits in the left (we are not interested)

      // this shift will push the most important bits off
      valCopy  <<= l_rest;
      
      // this shift is in fact a double shift operation:
      // 1st: the l_rest bits pushed to the left before are dragged back and filled with 0s, 
      //          clearing the left part of the bits
      // the 2nd operation will move the remaining (l_len + l_pos) bits to the right with l_pos
      //          positions eliminating the rightmost l_pos bits and filling with 0s the l_pos 
      //          bits from the left.
      valCopy >>>= (l_rest + l_pos);
      
      // at the end of the above statement, the valCopy will contain exactly l_len bits aligned 
      // to the right, which is exactly what we need
      return new int64(valCopy);
   }

   /**
    * Worker method for setBits, this copies the bit representation of an 
    * integer to a location with a given number of bits within the current integer variable.
    *
    * @param    data
    *           The bit representation of an integer to be copied.
    * @param    pos
    *           The 1-based index position to set, must be &gt; 0.
    * @param    len
    *           The number of bytes to take into consideration when performing the operation.
    */
   @Override
   public void setBitsWorker(double data, double pos, double len)
   {
      // handle exception cases first 
      if (isUnknown())
      {
         // cannot set bits on unknown value
         return;
      }
      
      if (pos < 1 || pos > 64 || len < 1 || len > 64 || len + pos > 65)
      {
         ErrorManager.recordOrThrowError(
                 9219,
                 "Both the position and num-bits arguments for PUT-BITS and GET-BITS must " +
                 "be greater than 0 and less than 65. Their sum must be less than 66");
         return;
      }
      
      // convert everything to native long
      long l_pos  = (long) pos - 1; // pos is 0-based and represents the number of untouched bits from the right
      long l_len  = (long) len;
      long l_data = (long) data;
      long l_rest = 64 - l_pos - l_len;  // number of bits in the left which must remain untouched also
      
      // set all bits set to 1 in mask (0xFFFFFFFFFFFFFFFF). We will build a negative mask
      long mask = (long) -1;

      // this operation move the current 1 to the right preparing them to be filled with 0s at 
      // the next shift
      mask >>>= l_pos;
      
      // two steps at one: fill the rightmost l_pos bits with 0s and continue the shift to 
      // remove the leftmost l_rest bits that will be 0-ed at the next step
      mask  <<= (l_pos + l_rest);
      
      // fill the leftmost l_rest with 0s.  
      mask >>>= l_rest;
      
      // We have moved the mask back and forth so that the l_len bits are on the starting position 
      // the l_pos bits from the right were discarded and replaced with 0. The same stands for 
      // the l_rest bits from the left. 
      
      // Now, the mask represents (with 1) the bits that should be dropped so they are filled
      // with 0, by AND-ing with negated the mask (which has 1 on the left and right sides that 
      // must not be changed and 0 on the part that will be overwritten by new data)
      long lvalue = value & ~mask;

      // shift the l_data bits on the correct position, the right l_pos will be filled with 0
      l_data <<= l_pos;
      
      // we apply the mask just in case that l_data has more bits that we are interested
      l_data &= mask;

      // assemble all together: 
      setValue(lvalue | l_data);
   }

   /**
    * Replacement for the default object reading method. The latest state is 
    * read from the input source.
    * 
    * @param    in
    *           The input source from which fields will be restored.
    *
    * @throws   IOException
    *           In case of I/O errors.
    * @throws   ClassNotFoundException
    *           If payload can't be instantiated.
    */
   public void readExternal(ObjectInput in)
   throws IOException, ClassNotFoundException
   {
      value   = in.readLong();
      unknown = in.readBoolean();
   }

   /**
    * Replacement for the default object writing method. The latest state is 
    * written to the output destination.
    * 
    * @param    out
    *           The output destination to which fields will be saved.
    *
    * @throws   IOException
    *           In case of I/O errors.
    */
   public void writeExternal(ObjectOutput out)
   throws IOException
   {
      out.writeLong(value);
      out.writeBoolean(unknown);
   }
   
   /**
    * Set the value of this instance from the given numeric value.  Due to
    * rounding, a loss of precision may occur in this conversion (all digits
    * to the right of the decimal point will be truncated).
    *
    * @param   value
    *          The value to be represented in this instance.
    */
   protected void setValue(String value)
   {
      // special case: unknown value
      if (value == null || value.equals("?"))
      {
         setUnknown();
         return;
      }
      
      double  doubleValue = Double.NaN;
      boolean large       = false;
      
      try
      {
         doubleValue = parseDouble(value, true);
      }
      
      catch (ErrorConditionException e)
      {
         if (e.getProgressErrorCode() == 536)
         {
            // 'Decimal number is too large' error has occurred.
            large = true;
         }
         else
         {
            // Just re-throw the condition.
            throw e;
         }
      }

      boolean isUnknown = ((Double) doubleValue).equals(Double.NaN);
      
      if (isUnknown && ErrorManager.isSilentError() && ErrorManager.isPendingError())
      {
         int size = ErrorManager.getSize();
         
         if (size > 0 && ErrorManager.getErrorNumber(size).intValue() == 536)
         {
            // 'Decimal number is too large' error has occurred (ErrorManager is in silent mode)
            large = true;
         }
      }

      if (large || doubleValue > Long.MAX_VALUE || doubleValue < Long.MIN_VALUE)
      {
         ErrorManager.recordOrThrowError(78, "Value too large for integer");
      }
      else if (isUnknown)
      {
         // Math.round() will return 0 for NaN argument
         setUnknown();
      }
      else
      {
         setValue(Math.round(doubleValue));
      }
   }

   /**
    * Set the value of this instance as an <code>long</code>. No loss of precision will occur in
    * this conversion.  This also sets the <code>unknown value</code> state for this instance to
    * <code>false</code>.
    *
    * @param   newVal
    *          The value to be represented in this instance.
    *
    * @throws  ErrorConditionException
    *          Thrown if the <code>value</code> do not fit within this data-type.
    */
   public void setValue(long newVal)
   throws ErrorConditionException
   {
      if (checkBounds(newVal, false))
      {
         if (unknown || value != newVal)
         {
            checkUndoable(true);
         }

         unknown = false;
         value   = newVal;
      }
   }

   /**
    * Set the value of this instance as an <code>boolean</code>. 
    * When the parameter is <code>true</code> the resulting integer value will be 1.
    * Otherwise the value is 0.
    * 
    * @param    newVal
    *           The value to be represented in this instance.
    */
   public void setValue(boolean newVal)
   {
      long theVal = (newVal ? 1 : 0);
      if (unknown || value != theVal)
      {
         checkUndoable(true);
      }

      unknown = false;
      value = theVal;
   }

   /**
    * Set the value of this instance as an <code>logical</code>. 
    * If the parameter's represents the <code>unknown value</code>, this instance will also
    * represent the <code>unknown value</code>.
    * <p>
    * When the parameter is <code>true</code> the resulting integer value will be 1.
    * Otherwise the value is 0.
    * 
    * @param    newVal
    *           The value to be represented in this instance.
    */
   public void setValue(logical newVal)
   {
      if (newVal.isUnknown())
      {
         setUnknown();
         return;
      }
      
      setValue(newVal.booleanValue());
   }

   /**
    * Set the value after casting the parameter into the internal representation of this class. 
    * Loss of precision may occur in this conversion (all digits to the right of the decimal 
    * point will be truncated).
    *
    * @param   newVal
    *          The value to be represented in this instance.
    *
    * @throws  ErrorConditionException
    *          Thrown if the <code>value</code> do not fit within this data-type.
    */
   public void setValue(NumberType newVal)
   throws ErrorConditionException 
   {
      if (newVal.isUnknown())
      {
         setUnknown();
         return;
      }
      
      if (newVal instanceof decimal)
      {
         // only decimals can be out-of-bounds
         BigDecimal crtVal = ((decimal) newVal).toJavaType();
         
         if (LOWER_BOUND.compareTo(crtVal) > 0 || UPPER_BOUND.compareTo(crtVal) < 0)
         {
            // first check for very large numbers
            ErrorManager.recordOrThrowError(78, "Value too large for integer");
            return;
         }
      }
      
      long val = newVal.longValue();
      
      if (isAssignDirect() || checkBounds(val, false))
      {
         if (unknown || value != val)
         {
            checkUndoable(true);
         }
         
         unknown = false;
         value   = val;
      }
   }

   /**
    * Return the current value as a Java Long wrapper.
    *
    * @return  Current value or <code>null</code> if unknown.
    */
   public Long toJavaLongType()
   {
      return isUnknown() ? null : longValue();
   }
   
   /**
    * Return the current value as a Java Integer wrapper.
    *
    * @return  Current value or <code>null</code> if unknown.
    */
   public Integer toJavaIntegerType()
   {
      return isUnknown() ? null : intValue();
   }
   
   /**
    * Checks if <code>value</code> can be assigned to this data type. This is important for
    * <code>integer</code> objects where the values must fit into 32 bit storage bounds.
    * 
    * @param   value
    *          The value
    * @param   warn
    *          {@code true} if any generated error should actually be a warning (the 
    *          {@code error-status:error} set to {@code false}.
    *
    * @return  Always true.
    *
    * @throws  ErrorConditionException
    *          Thrown if the <code>value</code> do not fit within this data-type.
    */
   protected boolean checkBounds(long value, boolean warn)
   throws ErrorConditionException
   {
      return true;
   }

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

   /**
    * Stores global data relating to the state of the current context.
    */
   private static class WorkArea
   {  
      /**
       * Stores the context-specific default format string (which honors the user's defined group 
       * and decimal separators). Initialization is delegated to the implementation class.
       */
      private String defFmt = null;
   }

   /**
    * Simple container that stores and returns a context-local instance of the global work area.
    */
   private static class ContextContainer
   extends ContextLocal
   {
      /**
       * Obtains the context-local instance of the contained global work area.
       *
       * @return  The work area associated with this context.
       */
      public WorkArea obtain()
      {
         return (WorkArea) this.get();
      }
      
      /**
       * Initializes the work area, the first time it is requested within a new context.
       *
       * @return  The newly instantiated work area.
       */
      protected synchronized Object initialValue()
      {
         return new WorkArea();
      }
   }

   /**
    * An immutable int64 type, used for caching singleton instances for specific values.
    */
   public static class int64Constant
   extends int64
   implements BaseDataTypeConstant
   {
      /**
       * Default constructor, creates an instance that represents the unknown value.
       */
      public int64Constant()
      {
         this.unknown = true;
      }

      /**
       * Constructs an instance with a specific value.
       *
       * @param    value
       *           The value of the int64Constant instance.
       */
      public int64Constant(Long value)
      {
         this.value = value;
         this.unknown = false;
      }

      /**
       * Operation not possible.
       *
       * @throws UnsupportedOperationException
       *         This is an immutable int64 instance.
       */
      @Override
      public void setUnknown()
      {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

      /**
       * Operation not possible.
       *
       * @throws UnsupportedOperationException
       *         This is an immutable int64 instance.
       */
      @Override
      public void setValue(long value) {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

      /**
       * Operation not possible.
       *
       * @throws   UnsupportedOperationException
       *           This is an immutable int64 instance.
       */
      @Override
      public void assign(NumberType value)
      {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

      /**
       * Operation not possible.
       *
       * @throws   UnsupportedOperationException
       *           This is an immutable int64 instance.
       */
      @Override
      public void assign(Undoable value)
      {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

      /**
       * Operation not possible.
       *
       * @throws   UnsupportedOperationException
       *           This is an immutable int64 instance.
       */
      @Override
      public void assign(Object value)
      {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

      /**
       * Operation not possible.
       *
       * @throws   UnsupportedOperationException
       *           This is an immutable int64 instance.
       */
      @Override
      Supplier<AbstractParameter> getAssigner()
      {
         throw new UnsupportedOperationException("This is an immutable int64 instance.");
      }

   }
}