BaseDataType.java
/*
** Module : BaseDataType.java
** Abstract : Progress 4GL compatible operator logic
**
** Copyright (c) 2005-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- --JPRM-- ----------------------------------Description-----------------------------------
** 001 GES 20050518 @21222 Created initial version with full support for all Progress logical
** operators and common functions.
** 002 GES 20050606 @21423 Minor additions for function support.
** 003 ECF 20050629 @21601 Implement Serializable. Will need this to push wrapper objects
** over the wire.
** 004 SVG 20050720 @21762 Cloning support added.
** 005 GES 20050815 @22117 Added Undoable interface support.
** 006 GES 20050825 @22252 Added getTypeName().
** 007 ECF 20051005 @22966 Introduced an abstract hashCode() method. This forces subclasses
** to provide an implementation which is consistent with this class'
** implementation of the equals() method.
** 008 GES 20060208 @24385 Added defaultFormatString().
** 009 GES 20060301 @24840 Added generateUnknown() static factory method.
** 010 NVS 20060407 @25407 Added isUnknownValue() and notUnknownValue() methods.
** 011 GES 20060417 @25551 Added default value generation.
** 012 GES 20060724 @28156 Added a forced assign() abstract method.
** 013 GES 20060807 @28469 Moved to externalizable interface to optimize network performance.
** 014 GES 20061109 @31049 Added formatLength().
** 015 ECF 20070720 @34626 Added safety check to equals(). Check type of object before casting
** it to BaseDataType to prevent ClassCastException.
** 016 SIY 20100812 Removed unnecessary cast.
** 017 CA 20130119 Added castLogical and castBoolean, which cast the current value
** using DYNAMIC-FUNCTION's rules.
** 018 GES 20130128 Clarified some javadoc.
** 019 GES 20130312 Removed castLogical() and castBoolean(). This same functionality is
** implemented in the logical(BDT) c'tor.
** 020 EVK 20131130 Added elementsOfType static method.
** 021 MAG 20140602 Added incompatibleTypesOnConversion() method.
** 022 OM 20150723 Updated javadoc.
** 023 CA 20151214 Added APIs to implicitly initialize an extent var and to check if two
** objects are of the same type.
** 024 GES 20160617 Added assign(BDT) for converting polymorphic return values.
** 025 CA 20160627 Reworked undoable support - the undoables register themselves with
** all blocks up the stack, until either 1. the tx block which created
** it is reached or 2. the last tx block where it was saved is reached.
** 026 GES 20171207 Removed explicit bypass on pending error (new silent error mode).
** 027 CA 20181029 Some refactoring required by the CALL's dynamic invoke support.
** 028 HC 20190127 Added method isAllKnown.
** OM 20190206 Added declaration of field size() needed by record-length.
** 029 CA 20190516 Added assign(Object) for values returned by POLY functions.
** 030 CA 20190710 Added OBJECT to fromTypeName.
** 031 CA 20190927 Added support for direct Java access from 4GL code.
** 032 CA 20200514 Added BLOB and CLOB to fromTypeName. Also, added the DATETIMETZ alias for
** DATETIME-TZ.
** 033 GES 20200628 Added invalidInitializer() helper to raise error 12003.
** 034 CA 20201003 Added isAllKnown(BaseDataType), to avoid array creation and iteration for single
** element calls.
** IAS 20201007 Added Type enum
** ME 20201009 Added new data type for table/dataset handle, mapped to handle.
** ME 20200322 Add info of which data type does not support a certain value assign.
** ME 20210504 Added method isIncompatibleTypesOnConversion to check if POLY data type conversion
** is to be attempted for given data type.
** 035 CA 20210512 Fixed 'getTypeName' when anon classes are used (for 'directAssign' usage).
** CA 20210609 Reworked INPUT/INPUT-OUTPUT parameters to a new approach, where they are explicitly
** initialized at the method's execution, and not at the caller's arguments.
** 036 AL2 20220328 Created proxy for BDT.
** CA 20221006 Performance improvement for 'getTypeName', use'getType' instead of the BDT actual
** class name.
** TJD 20220504 Java 11 compatibility related minor changes
** CA 20230116 Avoid fromTypeName when the ref type is valid and known.
** HC 20230118 Eliminated some of the uses of String.toUpperCase and/or
** String.toLowerCase for performance.
** CA 20230206 'instantiateDefault' must be aware of anon BDT classes.
** 037 CA 20230215 'instantiateDefault' now is implemented by each sub-class. Added unknown.UNKNOWN
** singleton (used by FWD runtime at this time).
** 038 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 039 RAA 20230525 Creating a new BaseDataType instance is now done using BaseDataTypeFactory.
** 040 RAA 20231009 Added check for dynamic extents in sameType.
** 041 CA 20231129 Allow 'null' doNotProxy arrays in ProxyFactory.getProxy.
** 042 PBB 20250509 Added validation for assignment of extent values to BDT properties.
** 043 ICP 20250414 Added implementation of the HASH-CODE builtin function.
** 044 AL2 20250530 Added getIndependentFromContext.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.util;
import java.io.*;
import java.lang.reflect.*;
import java.math.*;
import java.util.*;
import java.util.function.*;
import com.goldencode.p2j.persist.FieldReference;
import com.goldencode.p2j.util.logging.*;
import com.goldencode.proxy.ProxyFactory;
import com.goldencode.util.*;
/**
* An abstract class that implements a Progress 4GL compatible data type
* which has common logical operator processing. Meant to be the parent
* class for specific Progress compatible data type wrappers.
* <p>
* Each subclass should implement <code>Comparable</code> but is only
* required to implement <code>compareTo()</code> as the <code>equals()</code>
* method is implemented in this class.
* <p>
* <strong>Note:</strong> As result, classes from this hierarchy cannot be used as keys for normal
* java associative collections because of collisions caused by objects (Ex: an unknown
* {@link integer} and an unknown {@link logical} are considered equals in P4GL so only one
* unknown can be the key in such collection). If such collection is required, use a special one
* like {@link java.util.IdentityHashMap}.
* <p>
* All processing relies upon the implementation of the following methods
* in each concrete subclass:
* <p>
* <pre>
* boolean isUnknown()
* void setUnknown(boolean)
* int compareTo(Object) - this comes from the Comparable interface
* BaseDataType instantiateUnknown()
* BaseDataType duplicate()
* void assign(Undoable) - this comes from the Undoable interface
* void assign(BaseDataType)
* String toString()
* String toStringMessage()
* String toStringExport()
* String defaultFormatString()
* </pre>
*/
@SuppressWarnings("rawtypes")
public abstract class BaseDataType
extends LazyUndoable
implements Comparable,
Externalizable
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(BaseDataType.class.getName());
/**
* Default constructor (only used for de-serialization).
*/
public BaseDataType()
{
}
/**
* Mark this BDT instance as a 4GL-declared variable - it will be wrapped in a
* {@link BaseDataTypeVariable} instance.
*
* @param var
* The reference to be marked as a variable.
*
* @return The wrapped variable.
*/
public static BaseDataTypeVariable variable(BaseDataType var)
{
return new BaseDataTypeVariable(var);
}
/**
* Resolve the BDT sub-class from the specified 4GL type name.
* <p>
* This type name must be un-abbreviated and standardized (if multiple aliases exists).
*
* @param typeName
* The type name.
*
* @return The BDT sub-class or <code>null</code> if the type is unknown.
*/
public static Class<? extends BaseDataType> fromTypeName(String typeName)
{
Type type = Type.of(typeName);
return type == null ? null : type.getCls();
}
/**
* Creates a new instance of the given type that represents the
* <code>unknown value</code>.
*
* @param cls
* instance class
* @return An instance that represents the <code>unknown value</code>.
*/
public static BaseDataType generateUnknown(Class<?> cls)
{
BaseDataType bdt = null;
try
{
// use the default constructor
bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
}
catch (Exception expt)
{
// what else to do here (the calling code will probably fail with
// a class cast exception)?
return unknown.UNKNOWN;
}
return bdt.instantiateUnknown();
}
/**
* Creates a new instance of the given type that represents the
* default initialized value for that type.
*
* @param cls
* instance class
* @return An instance that represents the default value.
*/
public static BaseDataType generateDefault(Class<?> cls)
{
BaseDataType bdt = null;
try
{
// use the default constructor
bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
}
catch (Exception expt)
{
throw new RuntimeException(expt);
}
return bdt.instantiateDefault();
}
/**
* Calculate the length of a formatted value (the text form of the value)
* using this format string.
*
* @param fmt
* The format string.
* @param cls
* instance class
* @return The length in characters of the any resulting formatted
* value.
*/
public static int calcFormatLength(String fmt, Class<?> cls)
{
BaseDataType bdt = null;
try
{
// use the default constructor
bdt = (BaseDataType) BaseDataTypeFactory.instantiate(cls);
}
catch (Exception expt)
{
throw new RuntimeException(expt);
}
return bdt.formatLength(fmt);
}
/**
* Check list of BaseType of arguments and return {@code true} if all BaseTypes are specific
* type.
*
* @param cls
* Specific BaseDataType class witch will be checked to the arguments.
* @param args
* list of BaseType instances.
*
* @return The {@code true} if all BaseTypes are character.
*/
public static boolean elementsOfType(Class<? extends BaseDataType> cls, BaseDataType... args)
{
if (args.length == 0)
{
return true;
}
for (BaseDataType arg : args)
{
if (!cls.isInstance(arg))
{
return false;
}
}
return true;
}
/**
* Check if the types of the specified objects match.
* <p>
* In case of extent vars, the size of each var must be the same, and both must be extent.
*
* @param source
* The reference variable.
* @param target
* The candidate variable, which must match.
*
* @return <code>true</code> if the type of these two variables matches.
*/
protected static boolean sameType(Object source, Object target)
{
// extent vars
if (target.getClass().isArray())
{
if (!source.getClass().isArray())
{
// shared is not an array
return false;
}
Class<?> tcls = target.getClass().getComponentType();
Class<?> scls = source.getClass().getComponentType();
// check component type
if (!tcls.equals(scls))
{
return false;
}
else
{
int tlen = Array.getLength(target);
if (ArrayAssigner.isDynamicArray((BaseDataType[]) source) && tlen == 0)
{
// if both arrays have dynamic extent, no further length check is needed
return true;
}
// check extent
int slen = Array.getLength(source);
if (slen != tlen)
{
return false;
}
}
return true;
}
else
{
// check type directly (this will also fail if source is array and target
// is not array)
return source.getClass().equals(target.getClass());
}
}
/**
* Initialize the given array with the default value, based on the following table
* (the rules are from variable_definition.rules):
*
* <pre>
* integer new integer(0)
* int64 new int64(0)
* decimal new decimal(0)
* character new character("")
* logical new logical(false)
* longchar new longchar("")
* date new date()
* datetime new datetime()
* datetime-tz new datetimetz()
* raw new raw()
* memptr new memptr()
* recid new recid()
* </pre>
*
* @param var
* The extent variable which needs to be initialized.
*/
protected static void initializeDefaultExtent(BaseDataType[] var)
{
if (var == null || var.length == 0)
{
return;
}
Class<? extends BaseDataType> vcls =
(Class<? extends BaseDataType>) var.getClass().getComponentType();
try
{
BaseDataType bdinit = BaseDataTypeFactory.instantiate(vcls).instantiateDefaultExtent();
ArrayAssigner.assignMulti(var, bdinit);
}
catch (SecurityException |
ReflectiveOperationException |
IllegalArgumentException e)
{
throw new RuntimeException(e);
}
}
/**
* The method returns {@code true} if all the supplied values are "known", i.e. none of the
* values is {@code null} or unknown ({@link #isUnknown}).
*
* @param values
* The values to check.
*
* @return see above.
*/
public static boolean isAllKnown(BaseDataType... values)
{
for (BaseDataType val : values)
{
if (val == null || val.isUnknown())
{
return false;
}
}
return true;
}
/**
* The method returns {@code true} if all the supplied values are "known", i.e. none of the
* values is {@code null} or unknown ({@link #isUnknown}).
*
* @param value
* The values to check.
*
* @return see above.
*/
public static boolean isAllKnown(BaseDataType value)
{
return value != null && !value.isUnknown();
}
/**
* Create base data type proxy of the same data type as the provided value.
*
* @param value
* The value which should be wrapped with the proxy.
*
* @return A proxy which targets the provided value.
*/
public static BaseDataType createProxy(BaseDataType value)
{
return createProxy(value, value.getType().getCls());
}
/**
* Create base data type proxy of a specified data type.
*
* @param value
* The value which should be wrapped with the proxy.
* @param clazz
* The data type of the proxy.
*
* @return A proxy which targets the provided value and has a specified data type.
*/
public static BaseDataType createProxy(BaseDataType value, Class<? extends BaseDataType> clazz)
{
return ProxyFactory.getProxy(clazz,
true, // intercept any call to parent methods
new Class<?>[] { }, // no extra interface to consider
false,
null,
null,
new WrapperHandler(value, clazz));
}
/**
* Check if a BDT is a proxy or a real value.
*
* @param value
* The BDT to be checked
*
* @return {@code true} if the provided value is a proxy
*/
public static boolean isProxy(BaseDataType value)
{
return value != null && value.val() != value;
}
/**
* Convenient method to initialize a base data type. If the provided value is null or
* unknown, this value is also unknown. Otherwise, the assign method is used.
*
* @param value
* The value to assign to this type.
*/
public void initialize(BaseDataType value)
{
if (value == null || value.isUnknown())
{
setUnknown();
}
else
{
assign(value);
}
}
/**
* Calculate the length of a formatted value (the text form of the value)
* using this format string.
*
* @param fmt
* The format string.
*
* @return The length in characters of the any resulting formatted
* value.
*/
public int formatLength(String fmt)
{
if (fmt == null)
{
fmt = defaultFormatString();
}
return fmt.length();
}
/**
* Determines if this instance and the given instance are equivalent. Only an object of type
* {@code BaseDataType} is compared with this instance; objects of other types cause this test
* to return {@code false}.
* <p>
* <strong>Note:</strong> Unknown values form different classes from this hierarchy are
* considered equals in P4GL, even if they have different types (Ex: an unknown {@link integer}
* and an unknown {@link logical}.
* <strong>Note:</strong> Classes from this hierarchy cannot be used as keys for normal
* java associative collections because of collisions caused by objects (Ex: only one
* unknown can be the key in such collection). If such collection is required, use a special
* one like {@link java.util.IdentityHashMap}.
*
* @param obj
* The instance to compare against.
*
* @return {@code true} if the objects compare equivalently; {@code false} if they do not,
* or if the parameter is not a {@code BaseDataType} instance.
*/
public boolean equals(Object obj)
{
if (!(obj instanceof BaseDataType))
{
return false;
}
return CompareOps.equals(this, (BaseDataType) obj);
}
/**
* The default implementation of this method provided by
* <code>Object</code> is overridden here with an abstract method to force
* subclasses to provide a concrete implementation which is consistent with
* this class' implementation of {@link #equals}.
*
* @return The hash code for this object instance.
*/
public abstract int hashCode();
/**
* Computes a hash code for the given arguments, implementation of the <code>HASH-CODE</code> built-in
* function.
* <p>
* This function is deterministic and order-sensitive.
* <ul>
* <li>If a single argument is provided, the result is simply the individual hash of that argument.</li>
* <li>If multiple arguments are provided, the result is computed using a nested expression:</li>
* </ul>
* <pre>
* result = 1;
* for each argument arg:
* result = 31 * result + getIndividualHash(arg);
* </pre>
* <p>
* If an argument is of type {@code decimal} and is numerically equal to an {@code integer}, it will be
* treated as an integer to ensure compatibility: <code>HASH-CODE(5.00) == HASH-CODE(5)</code>
* <p>
* Unknown values consistently hash to the same constant value.
*
* @param args
* The arguments to hash. May include any mix of base data types.
*
* @return An integer hash code representing the input values.
*/
public static integer hashCode(BaseDataType... args)
{
if (args == null || args.length == 0)
{
return new integer(0);
}
if (args.length == 1)
{
return new integer(getIndividualHash(args[0]));
}
int result = 31 + getIndividualHash(args[0]);
for (int i = 1; i < args.length; i++)
{
result = 31 * result + getIndividualHash(args[i]);
}
return new integer(result);
}
/**
* Computes the individual hash code for a single {@link BaseDataType} value.
* <p>
* - Unknown values return 0.
* - {@code decimal} values that are numerically equal to an {@code integer} use the integer hash.
* - All other values use their {@code hashCode()} directly.
*
* @param arg
* the value to hash.
*
* @return the individual hash code.
*/
private static int getIndividualHash(BaseDataType arg)
{
if (arg == null || arg.isUnknown())
{
return 0;
}
if (arg instanceof decimal)
{
decimal d = (decimal) arg;
try
{
integer i = new integer(d);
BigDecimal dec = d.toBigDecimal();
if (dec != null && dec.compareTo(BigDecimal.valueOf(i.getValue())) == 0)
{
return i.hashCode();
}
}
catch (Throwable ignored)
{
// Fall through
}
return d.hashCode();
}
return arg.hashCode();
}
/**
* Returns the largest instance.
*
* @param b
* The instance to compare against.
*
* @return The largest instance.
*/
public BaseDataType maximum(BaseDataType b)
{
if (isUnknown() || b.isUnknown())
return instantiateUnknown();
return (CompareOps.greaterThan(this, b)) ? this : b;
}
/**
* Returns the smallest instance.
*
* @param b
* The instance to compare against.
*
* @return The smallest instance.
*/
public BaseDataType minimum(BaseDataType b)
{
if (isUnknown() || b.isUnknown())
return instantiateUnknown();
return (CompareOps.lessThan(this, b)) ? this : b;
}
/**
* This is a form of a copy constructor that makes a deep copy of the
* current instance and returns this copy as a new instance of the same
* class.
*
* @return An instance of the same class whose data is identical to
* that of this instance.
*/
public Undoable deepCopy()
{
return (Undoable) duplicate();
}
/**
* Returns the legacy type name of the sub-class as the type was known in
* the 4GL. Generally, this is equivalent to an uppercased version of the
* base name of the class file (all package information is removed). Child
* classes can override this default behavior.
*
* @return The legacy type name.
*/
public String getTypeName()
{
return getType().typeName;
}
/**
* Get the type
* @return type
*/
public abstract Type getType();
/**
* Reports if this instance represents the <code>unknown value</code>.
*
* @return <code>true</code> if this instance is set to the
* <code>unknown value</code>.
*/
public logical isUnknownValue()
{
return new logical(isUnknown());
}
/**
* Reports if this instance represents some known value.
*
* @return <code>true</code> if this instance is not set to the
* <code>unknown value</code>.
*/
public logical notUnknownValue()
{
return new logical(!isUnknown());
}
/**
* Sets the state (data and unknown value) of this instance based on the state of the passed
* instance.
* <p>
* If the value is not of the same type, some form of automatic type conversion may occur, or
* an error will be raised.
* <p>
* This variant is meant to handle the cases of built-in functions and methods in the 4GL
* which have polymorphic return types (e.g. DYNAMIC-FUNCTION()). This should NOT be used for
* non-polymorphic assignments.
*
* @param value
* The instance from which to copy state.
*/
public abstract void assign(BaseDataType value);
/**
* Convert the specified Java value to a legacy BDT instance, and assign it to this instance.
*
* @param value
* The instance holding a Java type, compatible with BDT native types.
*/
public void assign(jobject<?> value)
{
assign((BaseDataType) jobject.fromJava(value));
}
/**
* Assign the given value to the current instance.
*
* @param value
* The current value. This is considered POLY, as it may be passed by DYNAMIC-INVOKE
* or other POLY functions for which their returned data type is unknown at conversion
* time.
*/
public void assign(Object value)
{
if (value == null)
{
setUnknown();
return;
}
if (value instanceof jobject)
{
assign((jobject<?>) value);
}
else if (value instanceof BaseDataType)
{
assign(value == null ? null : ((BaseDataType) value).val());
}
else if (value instanceof String)
{
assign(new character((String) value));
}
else if (value instanceof Number)
{
assign(new decimal((Number) value));
}
else if (value instanceof Boolean)
{
assign(new logical((Boolean) value));
}
else if (value instanceof FieldReference)
{
assign(((FieldReference) value).get());
}
else if (value instanceof PropertyReference)
{
assign(((PropertyReference) value).get());
}
else if (value.getClass().isArray())
{
String errMsg = "Whole-array assignment target and source must have the same extent " +
"unless the target is indeterminate";
ErrorManager.recordOrThrowError(14905, errMsg, false);
}
else
{
UnimplementedFeature.missing(String.format("%s.assign(%s)", getClass(), value.getClass()));
}
}
/**
* Reports if this instance represents the <code>unknown value</code>.
*
* @return <code>true</code> if this instance is set to the
* <code>unknown value</code>.
*/
public abstract boolean isUnknown();
/**
* Sets the state of this instance's <code>unknown value</code> flag or
* state to <code>true</code>.
* <p>
* <b>Warning: the data stored in this instance may be invalid after
* calling this method.</b>
*/
public abstract void setUnknown();
/**
* Does the same as standard <code>clone()</code> method but returns an
* instance of <code>BaseDataType</code> and doesn't throw the
* <code>CloneNotSupportedException</code>.
*
* @return A clone of this instance.
*/
public abstract BaseDataType duplicate();
/**
* Creates a new instance of the same type that represents the
* <code>unknown value</code>.
*
* @return An instance that represents the <code>unknown value</code>.
*/
public abstract BaseDataType instantiateUnknown();
/**
* Creates a new instance of the same type that represents the default initialized value.
*
* @return An instance that represents the default value.
*/
public abstract BaseDataType instantiateDefault();
/**
* Creates a string representation of the instance data using the given
* format string. If the instance represents the
* <code>unknown value</code>, a '?' will be returned.
*
* @param fmt
* The format string to use.
*
* @return The formatted string.
*/
public abstract String toString(String fmt);
/**
* Creates a string representation of the instance data in a form that is
* compatible with the <code>MESSAGE</code> language statement. If the
* instance represents the <code>unknown value</code>, a '?' will be
* returned.
*
* @return The 'message' formatted string.
*/
public abstract String toStringMessage();
/**
* Creates a string representation of the instance data using the 'export'
* format. If the instance represents the <code>unknown value</code>, a
* '?' will be returned.
*
* @return The 'export' formatted string.
*/
public abstract String toStringExport();
/**
* Return the default display format string for this type.
*
* @return The default format string.
*/
public abstract String defaultFormatString();
/**
* Obtain the length (in bytes) of this BDT will use when serialized.
*
* @return the length of this BDT will use when serialized.
*/
public int getSize()
{
return 1;
}
/**
* Retrieve the real value. This is used to ignore any proxy which is done over a value.
*
* @return this value or the target value if this is a proxy.
*/
public BaseDataType val()
{
return this;
}
/**
* Retrieve the real value in a proxy data type. This is used to honor any proxy
* which is done over a value, and convert the value to the needed data type.
*
* @return this value or a converted value if this is a proxy.
*/
public BaseDataType fallback()
{
return this;
}
/**
* Identify if this data is dependent upon the context local. This identifies
* if the data is safe to be cached in cross-session places or the data should
* not leak from the context. If is is not safe to be shared cross-session, then
* this should return {@code null}. If other representation is suitable for caching,
* then this can return another object that wraps internal values that are not
* dependent upon the context (e.g. date, date-time, date-time-tz).
*
* @return Usually {@code this}. The extensions should be eager to
* state if they are dependent upon the context. If they are, then either
* {@code null} should be returned or another object that is more suitable
* for caching.
*/
public Object getIndependentFromContext()
{
return this;
}
/**
* Check if this instance can be assigned directly, without boundary checks.
* <p>
* This API should be overridden by specific BDT sub-classes, and is currently used in CALL's
* OUTPUT/INPUT-OUTPUT parameter case, where data needs to be copied directly.
*
* @return Always <code>false</code>.
*/
boolean isAssignDirect()
{
return false;
}
/**
* Get the assigner supplier which will be used to register this instance as a parameter.
*
* @return See above.
*/
Supplier<AbstractParameter> getAssigner()
{
return null;
}
/**
* Get the default initialization for an extent variable of this type.
*
* @return See above.
*/
protected BaseDataType instantiateDefaultExtent()
{
return instantiateDefault();
}
/**
* Throws error message in case of incompatible types for a POLY conversion.
* (e.g. DYNAMIC-FUNCTION()).
*
* @throws ErrorConditionException
* Always.
*/
protected void incompatibleTypesOnConversion()
{
String err = "Incompatible datatypes found during runtime conversion";
ErrorManager.recordOrThrowError(5729, err);
}
/**
* Returns true if dynamic data type conversion should not be attempted,
* it can also throws in case of incompatible types for a POLY conversion.
* (e.g. DYNAMIC-FUNCTION()).
*
* @throws ErrorConditionException
* If dynamic conversion of input value is not supported.
*/
protected boolean isIncompatibleTypesOnConversion(BaseDataType value)
{
return false;
}
/**
* Display error message in case of an invalid initializer for the given type.
*
* @param text
* The string initializer for display in the error.
*
* @throws ErrorConditionException
* Always.
*/
protected void invalidInitializer(String text)
{
String err = "'%s' is not an appropriate initializer for items of type %s";
ErrorManager.recordOrThrowError(12003, String.format(err, text, getTypeName()));
}
/**
* Enum class describing all 4GL wrapper types. This makes it easy to map the BDT wrapper subclass
* to the 4GL type with less string manipulation/fewer map lookups.
*/
@SuppressWarnings("javadoc")
public static enum Type
{
BLOB("BLOB", blob.class),
CLOB("CLOB", clob.class),
CHARACTER("CHARACTER", character.class),
COMHANDLE("COMHANDLE", comhandle.class),
COM_HANDLE("COM-HANDLE", comhandle.class),
DATASET_HANDLE("DATASET-HANDLE", handle.class),
DATE("DATE", date.class),
DATETIME("DATETIME", datetime.class),
DATETIMETZ("DATETIMETZ", datetimetz.class),
DATETIME_TZ("DATETIME-TZ", datetimetz.class),
DECIMAL("DECIMAL", decimal.class),
HANDLE("HANDLE", handle.class),
INTEGER("INTEGER", integer.class),
INT64("INT64", int64.class),
JOBJECT("JOBJECT", jobject.class),
LOGICAL("LOGICAL", logical.class),
LONGCHAR("LONGCHAR", longchar.class),
MEMPTR("MEMPTR", memptr.class),
OBJECT("OBJECT", object.class),
RAW("RAW", raw.class),
RECID("RECID", recid.class),
ROWID("ROWID", rowid.class),
TABLE_HANDLE("TABLE-HANDLE", handle.class),
UNKNOWN("UNKNOWN", unknown.class);
/** The 4GL-compatible type name. */
private final String typeName;
/** The BDT sub-class that implements the type. */
private final Class<? extends BaseDataType> cls;
/** Types by type name map. */
private static final Map<String, Type> VALS =
Collections.unmodifiableMap(new CaseInsensitiveHashMap<String, Type>()
{
{ Arrays.stream(Type.values()).forEach(t -> put(t.getTypeName(), t)); }
});
/**
* Constructor.
*
* @param typeName
* type name
* @param cls
* class
*/
private Type(String typeName, Class<? extends BaseDataType> cls)
{
this.typeName = typeName;
this.cls = cls;
}
/**
* Get the class instance by type name.
*
* @param typeName
* type name
*
* @return Type instance or <code>null</code> if unknown
*/
public static Type of(String typeName)
{
return typeName == null ? null : VALS.get(typeName);
}
/**
* Get type name.
*
* @return The type name.
*/
public String getTypeName()
{
return typeName;
}
/**
* Get the class.
*
* @return The class which implements this type.
*/
public Class<? extends BaseDataType> getCls()
{
return cls;
}
}
/**
* An invocation handler dedicated for BDT proxy. This will wrap a provided value which
* should pass data type checks. This means that such proxy can pretend to be a character,
* but under the hood to refer a decimal. This can help to implement 4GL relaxed constraints
* which in Java are naturally hard constraints. For instance, the 4GL return data type check
* is ignored if the returning value is provided by a dynamic-function. In Java, we can
* emulate the bypass of a return data type check by encapsulating the value into such proxy.
*/
static class WrapperHandler
implements InvocationHandler
{
/** The data type of the proxy */
private Class<? extends BaseDataType> proxyDataType;
/** The underlying value which is proxied */
private BaseDataType target;
/** A fallback value of proxy's data type which is assigned from target */
private BaseDataType fallbackValue;
/** The target has the same data type as the proxy */
private boolean identity;
/**
* Basic constructor.
* @param target
* The underlying value which is proxied
* @param proxyDataType
* The data type of the proxy
*/
WrapperHandler(BaseDataType target, Class<? extends BaseDataType> proxyDataType)
{
this.target = target;
this.proxyDataType = proxyDataType;
this.identity = proxyDataType.isAssignableFrom(target.getClass());
}
/**
* The invoke method of the invocation handler. This should allow only {@code val} to identify
* the underlying value and {@code fallback} to trigger an assign of the underlying value
* to proxy's data type. Any other method is delegated to the fallback value to keep availability.
* The fallback plan should be avoided, usually, by a proxy check in the calling context.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)
{
if (method.getName().equals("val"))
{
return target.val();
}
try
{
if (method.getName().equals("fallback"))
{
initFallback();
return fallbackValue;
}
// unexpected situation - try to make it work
if (!identity)
{
LOG.severe("Using unsafe fallback plan for BDT value proxy. This means that there was no proxy check!");
}
initFallback();
return method.invoke(fallbackValue, args);
}
catch (Exception ex)
{
Throwable ex2 = ex;
while (ex2 != null && !(ex2 instanceof ConditionException))
{
ex2 = ex2.getCause();
}
if (ex2 == null)
{
// wrap in a RunnableException and rethrow
throw new RuntimeException(ex);
}
else
{
// ex2 is instance of ConditionException because of a 4GL validation/conversion
// error has occurred: propagate, will be handled elsewhere
throw (ConditionException) ex2;
}
}
}
/**
* Helper method to build a fallback value. This process can result in errors, so the fallback
* value is best to have a lazy initialization.
* @throws ReflectiveOperationException
*/
private void initFallback()
throws ReflectiveOperationException
{
if (fallbackValue == null)
{
fallbackValue = BaseDataTypeFactory.instantiate(proxyDataType);
fallbackValue.assign(target);
}
}
}
}