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 ≤-1, 0, or ≥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 '->,>>>,>>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 '->,>>>,>>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 > 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 > 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.");
}
}
}