integer.java
/*
** Module : integer.java
** Abstract : Progress 4GL compatible integer object
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------Description----------------------------
** 001 GES 20050517 @21251 Created initial version with full support
** for all Progress integer processing including
** some functions like integer, minimum and
** maximum.
** 002 GES 20050607 @21443 Minor additions for function support.
** 003 GES 20050614 @21489 Added increment and decrement methods.
** 004 GES 20050621 @21533 Added an assign() method to modify this
** instance's state based on the result of
** an expression of compatible type.
** 005 SVG 20050720 @21767 Cloning support for BaseDataType added.
** 006 GES 20050822 @22192 Added Undoable interface support.
** 007 GES 20050830 @22384 Added error condition processing.
** 008 ECF 20051004 @22970 Implement hashCode() method. Required to be
** consistent with BaseDataType's implementation
** of equals() method.
** 009 GES 20060123 @24024 Implemented support for decimal and group
** separator overrides.
** 010 GES 20060206 @24301 Made compareTo() safe to process any object
** type (avoids ClassCastException).
** 011 GES 20060405 @25368 Support any NumberType in lists for min/max.
** 012 GES 20060417 @25555 Added default value generation.
** 013 GES 20060530 @26665 Fixed issue with "hh:mm" formatting where the
** hours could be returned with only 1 digit.
** 014 NVS 20060602 @26937 Fixed assign() method for unknown values.
** 015 GES 20060724 @28162 Added a forced assign() abstract method.
** 016 GES 20060807 @28474 Moved to externalizable interface to optimize
** network performance.
** 017 ECF 20060812 @28588 Added methods needed for database server-side
** function support.
** 018 GES 20060825 @28900 Added context local default format data.
** 019 SIY 20070405 @32825 Part of numbers conversion quirk handling.
** 020 GES 20080911 @39790 Added assign(int) and assign(double)
** signatures.
** 021 ECF 20090212 @41299 Implemented a faster equals(). Handles most
** common cases without having to invoke the
** superclass' implementation. Also added
** several assign() variants and optimized
** compareTo() for the common case.
** 022 SVL 20090225 @41367 Make class not final.
** 023 SVL 20090824 @43722 "force" parameter should be false by default into
** assign(integer).
** 024 SVL 20090908 @43845 Fixed handling of large integers.
** 025 GES 20091102 @44301 Time format string processing was incorrect when
** the value is a large enough negative to cross
** back over the DST boundary. Removed all J2SE
** date/time processing and duplicated the Progress
** result directly.
** 026 GES 20110303 Moved maximum/minimum to varargs.
** 027 OM 20121220 Added int64 support.
** 028 CA 20130123 Added c'tor which builds 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.
** 029 CS 20130124 Added conversion support for memptr related
** functions task #1635
** 030 GES 20130126 Changed some constructors to handle NumberType.
** 031 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 implemented.
** 032 OM 20130328 Added missing runtime implementation. Changed the architecture so
** that this class is derived from int64. All assignments will call
** checkBounds in order to check if the new value fits inside 32 bit
** bounds. Most of the duplicate code common with int64 was removed
** and will be accessible through inheritance.
** 033 OM 20130710 Removed unneeded conversion from Date.
** 034 GES 20140115 Handle quirky out of bounds error message in an obscure case.
** 035 ECF 20150427 Override assign(NumberType, boolean) method to bypass the superclass'
** much more expensive implementation for the most common use case.
** 036 OM 20150630 Converting a DATETIME to INT is an error.
** 037 CA 20151222 Added instantiateDefaultExtent.
** 038 IAS 20160331 Fixed potential NPE
** 039 ECF 20160614 Implemented faster copy constructor.
** 040 GES 20171207 Removed explicit bypass on pending error (new silent error mode).
** 041 GES 20200520 Added 4GL enum constructor.
** GES 20200603 The INTEGER() built-in must not set error-status:error on failures.
** 042 IAS 20201007 Added Type enum
** 043 CA 20210216 If a RETURN <expr>; in a functions returns a runtime int64 value and the function
** is 'return integer', then the value must be set to unknown. For this reason,
** the 'isAssignDirect' protection was added in 'assign'.
** 044 AL2 20220328 Added proxy checks for BDT.
** 045 CA 20230215 'duplicate()' method returns the real type instead of BDT.
** 046 ICP 20250123 Added integer 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.util.function.*;
import com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.util.logging.*;
/**
* A class that represents a Progress 4GL compatible integer object whose
* data is mutable. This class provides the basic wrappering of the
* primitive data, instantiation, type conversion, <code>unknown value</code>
* 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.
*
* @author GES
*/
public class integer
extends int64
{
/** Logger */
private static final CentralLogger log = CentralLogger.get(integer.class);
/** Default maximum number of cached integerConstant instances */
private static final int DEFAULT_STATIC_CACHE_SIZE = 1024;
/** Default size for the dynamic cache */
private static final int DEFAULT_DYNAMIC_CACHE_SIZE = 512;
/** Singleton and immutable for the integer UNKNOWN value. */
public static final integerConstant UNKNOWN = new integerConstant();
/** Cache for the singleton and immutable integer values. */
private static integerConstant[] STATIC_CACHE;
/** Dynamic integer cache. */
private static volatile integerConstant[] DYNAMIC_CACHE;
/**
* Default constructor which creates an instance representing the <code>unknown value</code>.
*/
public integer()
{
}
/**
* 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 integer(BaseDataType value)
{
initialize(value);
}
/**
* Constructs an instance wth the given enum's value. If the value is larger than
* {@code Integer.MAX_VALUE} or smaller than {@code Integer.MIN_VALUE} (does not fit into the 32-bit
* representation) it cannot be represented and a warning 15747 will be raised:
* "Value too large to fit in INTEGER".
* <p>
* If the parameter is {@code unknown value} the resulting instance is the {@code unknown value}.
* <p>
* This constructor implements the {@code INTEGER(enum)} 4GL built-in function. Any failure will
* generate a warning instead of an error.
*
* @param enm
* The enum whose value to be used for this instance.
*/
public integer(object<? extends LegacyEnum> enm)
{
if (enm.isUnknown())
{
setUnknown();
}
else
{
// although the assign will call checkBounds() downstream, it will generate an error which we don't
// want; we force the check here so that we can cause the failure to be a warning
long num = enm.ref()._getValue();
if (!checkBounds(num, true))
{
return;
}
super.setValue(num);
}
}
/**
* Constructs an instance after casting the parameter into the internal representation of this
* class. If the value is larger than <code>Integer.MAX_VALUE</code> or smaller than
* <code>Integer.MIN_VALUE</code> (does not fit into the 32-bit representation) it
* cannot be represented and error 15747 will be raised: "Value too large to fit in INTEGER"
* <p>
* It is not possible to yield an <code>unknown value</code> from this constructor without
* calling the method {@link #setUnknown} separately.
*
* @param value
* The value to be used for this instance.
*/
public integer(long value)
{
super.setValue(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 integer(int value)
{
super.setValue(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 integer(double value)
{
super.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 integer(boolean value)
{
super.setValue(value);
}
/**
* Constructs an instance after casting the parameter into the internal representation of this
* class. Very large and very small values that do not fit into the 32 bit representation
* cannot be represented.
* <p>
* If the parameter represents the <code>unknown value</code>, this instance will also
* represent the <code>unknown value</code>.
* <p>
* This constructor implements the {@code INTEGER(int64)} 4GL built-in function. Any failure will
* generate a warning instead of an error.
*
* @param value
* The value to be used for this instance.
*/
public integer(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;
}
}
// although the assign will call checkBounds() downstream, it will generate an error which we don't
// want; we force the check here so that we can cause the failure to be a warning
if (value != null && !value.isUnknown())
{
long num = value.longValue();
if (!checkBounds(num, true))
{
return;
}
}
assign(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 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 integer(integer value)
{
super(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. Very large and very small values cannot be represented.
* <p>
* If the parameter 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 integer(decimal value)
{
assign(value);
}
/**
* 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 integer(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());
return;
}
}
super.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 integer(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 integer(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 integer(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
{
super.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 integer(Number value)
{
if (value == null)
{
setUnknown();
}
else
{
super.setValue(value.intValue());
}
}
/**
* 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 integer(Boolean value)
{
if (value == null)
{
setUnknown();
return;
}
super.setValue(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(integer.class,
"static-cache",
DEFAULT_STATIC_CACHE_SIZE,
integerConstant.class);
for (int i = 0; i < STATIC_CACHE.length; i++)
{
STATIC_CACHE[i] = new integerConstant(i);
}
DYNAMIC_CACHE = CacheManager.createArrayCache(integer.class,
"dynamic-cache",
DEFAULT_DYNAMIC_CACHE_SIZE,
integerConstant.class);
}
/**
* Returns an integerConstant 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 integer.
* @return An integerConstant instance.
*/
public static integer of(Integer value)
{
if (value == null)
{
return UNKNOWN;
}
if (STATIC_CACHE == null)
{
return new integerConstant(value);
}
//Check static cache
if (value >= 0 && value < STATIC_CACHE.length)
{
return STATIC_CACHE[value];
}
if (DYNAMIC_CACHE == null)
{
return new integerConstant(value);
}
// Check dynamic cache
int index = Math.abs(value % DYNAMIC_CACHE.length);
synchronized (DYNAMIC_CACHE)
{
integerConstant cachedInstance = DYNAMIC_CACHE[index];
if (cachedInstance != null && cachedInstance.intValue() == value)
{
return cachedInstance;
}
// Cache miss - create and replace the entry in dynamic cache
integerConstant newInstance = new integerConstant(value);
DYNAMIC_CACHE[index] = newInstance;
return newInstance;
}
}
/**
* Returns an integerConstant 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 integer.
* @return An integerConstant instance.
*/
public static integer of(integer value)
{
if (value == null || value.isUnknown())
{
return UNKNOWN;
}
return integer.of(value.intValue());
}
/**
* Get the type
* @return type
*/
public Type getType()
{
return Type.INTEGER;
}
/**
* Sets the state (data and unknown value) of this instance based on the state of the passed
* instance.
* <p>
* Overrides the superclass' implementatoin to optimize for the most common use case of
* copying an <code>integer</code> from another <code>integer</code> (vs. from a
* non-<code>integer</code> <code>NumberType</code>). In bypassing the superclass' more general
* implementation, we avoid a significantly slower implementation which performs unnecessarily
* (for this case) a bounds check and <code>BigDecimal</code> instantiation.
* <p>
* In the event <code>value</code> is not an <code>integer</code> instance, delegate to the
* superclass' implementation.
*
* @param value
* The instance from which to copy state.
*/
@Override
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;
}
if (!(value instanceof integer))
{
super.assign(value);
return;
}
if (value.isUnknown())
{
setUnknown();
return;
}
if (((integer) value).isAssignDirect() && !checkBounds(value.longValue(), false))
{
// convert it to unknown...
setUnknown();
return;
}
setValue(value.intValue());
}
/**
* Hash code implementation which is consistent with {@link BaseDataType#equals}.
*
* @return Hash code value for this object instance.
*/
public int hashCode()
{
return super.hashCode();
}
/**
* Creates a new instance of the same type that represents the <code>unknown value</code>.
*
* @return An instance that represents the <code>unknown value</code>.
*/
public BaseDataType instantiateUnknown()
{
return new integer();
}
/**
* Creates a new instance of the same type that represents the default initialized value.
*
* @return An instance that represents the default value.
*/
public BaseDataType instantiateDefault()
{
return new integer(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.
*/
public integer duplicate()
{
return new integer(this);
}
/**
* 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("Assignement of " + value.getClass() +
" to integer not implemented.");
}
}
/**
* 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
{
int value = in.readInt();
boolean unknown = in.readBoolean();
if (unknown)
{
setUnknown();
}
else
{
super.setValue(value);
}
}
/**
* 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.writeInt((int)getValue());
out.writeBoolean(isUnknown());
}
/**
* Get the default initialization for an extent variable of this type.
*
* @return See above.
*/
@Override
protected BaseDataType instantiateDefaultExtent()
{
return new integer(0);
}
/**
* 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 <code>true</code> if value fits into 32 bit storage bounds.
*
* @throws ErrorConditionException
* Thrown if the <code>value</code> do not fit within this data-type.
*/
@Override
protected boolean checkBounds(long value, boolean warn)
throws ErrorConditionException
{
if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE)
{
int errno = 15747;
// mimic the strange quirk of how the 4GL prints the smallest int64 value
String txt = (value == Long.MIN_VALUE) ? "-(" : String.format("%d", value);
String msg = "Value " + txt + " too large to fit in INTEGER";
if (warn)
{
ErrorManager.recordOrShowError(errno, msg, false, true, false);
}
else
{
ErrorManager.recordOrThrowError(errno, msg, false);
}
return false;
}
return true;
}
/**
* An immutable integer type, used for caching singleton instances for specific values.
*/
public static class integerConstant
extends integer
implements BaseDataTypeConstant
{
/**
* Default constructor, creates an instance that represents the unknown value.
*/
public integerConstant()
{
this.unknown = true;
}
/**
* Constructs an instance with a specific value.
*
* @param value
* The value of the integerConstant instance.
*/
public integerConstant(int value)
{
this.value = value;
this.unknown = false;
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
public void setUnknown()
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
public void setValue(long value)
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
public void assign(NumberType value)
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
public void assign(Undoable value)
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
public void assign(Object value)
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
/**
* Operation not possible.
*
* @throws UnsupportedOperationException
* This is an immutable integer instance.
*/
@Override
Supplier<AbstractParameter> getAssigner()
{
throw new UnsupportedOperationException("This is an immutable integer instance.");
}
}
}