AbstractExtentParameter.java
/*
** Module : AbstractExtentParameter.java
** Abstract : Base class for special wrapper classes used for variables passed as extent
** parameters to functions or procedures.
**
** Copyright (c) 2014-2023, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 HC 20140613 Created initial version.
** 002 OM 20150701 Extracted AbstractParameter super class. Added implicit conversion support.
** 003 EVL 20160224 Javadoc fixes to make compatible with Oracle Java 8 for Solaris 10.
** 004 GES 20171207 Removed explicit bypass on pending error to match new silent error mode.
** 005 CA 20190508 Added support for CASE-SENSITIVE extent var option.
** 006 CA 20210223 If the original array is dynamic extent, register the new array reference as dynamic
** extent, too.
** 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.
** TJD 20220504 Java 11 compatibility related minor changes
** CA 20220428 Avoid a NPE when precision is not known.
** CA 20220609 Release all the callee's resources for this parameter, after assignment has taken place.
** CA 20220613 Do not release memptr variables, as these are passed 'by pointer'.
** 007 CA 20230711 Extent arguments must be explicitly copied to the field parameter at the caller.
*/
/*
** 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.lang.reflect.*;
/**
* Abstract class for special wrappers used for variables passed as extent parameters
* to functions or procedures. The class introduces common logic for all extent parameter cases.
*/
public abstract class AbstractExtentParameter<T extends BaseDataType>
extends AbstractParameter
{
/** The original variable set to the parameter. */
private T[] variable = null;
/** The latest parameter reference. */
private T[] parameter = null;
/**
* The class of the elements of the extent argument that was passed to procedure. When it is
* finished we need to implicitly converted to back this type.
*/
protected Class<? extends BaseDataType> argType = null;
/**
* The class of the elements of the extent parameter that procedure expects. When procedure is
* about to start, the elements of the extent argument are implicitly converted to this type.
*/
protected Class<? extends BaseDataType> paramType = null;
/**
* The optional conversion constructor that will convert the local parameter values back to
* argument item when the calling routine ends.
*/
protected Constructor<?> retConvCtor = null;
/**
* The optional conversion constructor that will convert the argument item to local parameter
* values back when the calling routine is about to start.
*/
protected Constructor<?> convCtor = null;
/**
* The constructor responsible for instance initialization.
* <p>
* The c'tor caches the variable reference in this instance. If {@code null} is
* passed to {@code variable}, the value reference is retrieved by calling
* {@link #getVariable()}, otherwise the passed-in value is used.
*
* @param variable
* The value reference passed to the function/procedure's parameter or {@code null}.
*/
protected AbstractExtentParameter(T[] variable)
{
// save the original array, in case it gets changed (at this time, the only way to change
// references is by a resize).
this.variable = (variable != null) ? variable : getVariableSafe();
}
/**
* Get the variable reference, passed to the function/procedure's parameter. Emitted and to be
* used only in cases of OUTPUT and INPUT-OUTPUT extent parameters.
*
* @return See above.
*/
public abstract T[] getVariable();
/**
* Change the caller's reference to the new reference. Emitted and to be used only in cases of
* OUTPUT and INPUT-OUTPUT extent parameters.
*
* @param reference
* The new reference.
*/
public abstract void setVariable(T[] reference);
/**
* Initializes the parameter value reference which is used in the function/procedure
* scope. The method is responsible for its proper initialization and registering
* the reference in this instance.
* <p>
* Use this method to initialize parameter values representing unfixed indeterminate
* extents.
*
* @return A valid and initialized parameter value reference.
*/
public abstract T[] initParameter();
/**
* Initializes the parameter value reference which is used in the function/procedure
* scope. The method is responsible for its proper initialization and registering
* the reference in this instance.
* <p>
* Use this method to initialize parameter values representing determinate extents.
*
* @param extent
* The extent size declared by the function/procedure parameter.
*
* @return A valid and initialized parameter value reference.
*/
public abstract T[] initParameter(int extent);
/**
* Resolve the variable reference, making sure the {@link OutputParameterAssigner} is not compromised if
* the reference is for a legacy OO property, for which its getter method is called.
*
* @return See above.
*/
public T[] getVariableSafe()
{
OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
try
{
return getVariable();
}
finally
{
TransactionManager.registerOutputParameterAssigner(opa);
}
}
/**
* Returns the current reference for the parameter.
*
* @return Current parameter reference.
*/
public T[] getParameter()
{
return parameter;
}
/**
* Set the current reference for the parameter. As this is mutable, we need to save it each
* time the parameter's reference gets changed.
*/
public void setParameter(T[] parameter)
{
this.parameter = parameter;
}
/**
* Prepare the input and (backward) output (implicit) conversion constructors for the base type
* of this extent argument.
* <br>
* In the case of OUTPUT parameters, only the {@code retConvCtor} will be used to convert the
* resulted values to caller datatype.
* <br>
* In case of INPUT-OUTPUT parameters, both constructors are used to convert the
* compatible datatypes forth and back.
*
* @param argType
* The element argument type that was used in procedure call.
* @param paramType
* The element type of expected parameter.
* @param convCtor
* The conversion constructor. Only for INPUT-OUTPUT parameters. In the case of
* OUTPUT parameters, this is ignored and should be {@code null}.
* @param retConvCtor
* The return conversion constructor.
*/
public void setReturnConversion(Class<? extends BaseDataType> argType,
Class<? extends BaseDataType> paramType,
Constructor<?> convCtor,
Constructor<?> retConvCtor)
{
this.argType = argType;
this.paramType = paramType;
this.convCtor = convCtor;
this.retConvCtor = retConvCtor;
}
/**
* Get the {@link #variable} direct reference.
*
* @return See above.
*/
protected T[] getVariableRef()
{
return variable;
}
/**
* Assign the current value of the parameter variable back to the caller's variable
* reference. This is expected to occur upon exit from the function or procedure
* with which this object is associated to.
*
* @param endingRoutine
* The legacy name of the routine that is ending when these back assignments are
* performed.
* @param function
* {@code true} if the called routine is a function.
*/
protected void assign(String endingRoutine, boolean function)
{
if (parameter == null)
{
throw new IllegalStateException(
"The extent parameter reference was not set, before the function/procedure end !");
}
if (parameter != variable)
{
// validate the assignment
boolean valid = validateAssignment(endingRoutine, function);
if (valid)
{
// if the reference has changed and validated ok, perform the assignment
performAssignment();
}
}
}
/**
* Release all the callee's resources for this parameter, after assignment has taken place.
*/
@Override
protected void release()
{
if (parameter == null)
{
return;
}
for (int i = 0; i < parameter.length; i++)
{
BaseDataType bdt = parameter[i];
if (canRelease(bdt))
{
bdt.setUnknown();
}
}
}
/**
* An extension point to allow the descendants to provide validation logic
* for parameter reference to variable assignment on exit from the function
* or procedure with which this object is associated to.
* <p>
* To indicate a failed validation, indicate an error with help of {@link ErrorManager}.
*
* @param endingRoutine
* The legacy name of the routine that is ending when these back assignments are
* performed.
* @param function
* {@code true} is the called routine is a function.
*
* @return This method always returns {@code true} which means that the parameters are safe
* to be assigned back to original data holders. Subclasses may return false (and
* use {@link ErrorManager} to indicate an error) if the held vaues are invalid.
*/
protected boolean validateAssignment(String endingRoutine, boolean function)
{
// no special validation at this level
return true;
}
/**
* An extension point to allow the descendants to provide custom logic for parameter reference
* to variable assignment on exit from the function or procedure with which this object is
* associated to.
* <p>
* The default implementation creates an array copy of the current parameter reference,
* implements decimal precision management and stores the copy to the target variable by
* calling {@link #setVariable(BaseDataType[])}.
* <p>
* The method is called only when there is no pending error being indicated by
* {@link ErrorManager}.
*/
@SuppressWarnings("unchecked")
protected void performAssignment()
{
// TODO: this need to use ObjectVar?
T[] retValues;
if (argType == null)
{
// get copy of the latest data
retValues = ArrayAssigner.copyOf(parameter, false);
}
else
{
retValues = (T[]) Array.newInstance(argType, parameter.length);
// copy the parameter array through retConvCtor morphism
for (int k = 0; k < parameter.length; k++)
{
try
{
retValues[k] = (T) retConvCtor.newInstance(parameter[k]);
}
catch (ReflectiveOperationException e)
{
// constructor-related exception should not happen, the constructor have been
// detected based on actual data types
throw new RuntimeException("Bad implicit conversion constructor");
}
}
}
if (ArrayAssigner.isDynamicArray(variable))
{
ArrayAssigner.registerDynamicArray(retValues, false);
}
if (decimal.class.equals(retValues.getClass().getComponentType()))
{
T[] variable = getVariableSafe();
Integer precision = ArrayAssigner.getDecimalPrecision((decimal[]) variable);
if (precision != null)
{
// precision can be null for dynamic arrays received from a remote side, outside of FWD server
for (T param : retValues)
{
// keep the decimal precision of the target variable value.
// note that the precision on the target variable will become effective
// only after the next value assignment, which is the expected behavior.
((decimal) param).setPrecision(precision);
}
}
}
if (Text.class.isAssignableFrom(retValues.getClass().getComponentType()))
{
T[] variable = getVariableSafe();
Boolean caseSens = ArrayAssigner.getCaseSensitive((Text[]) variable);
if (caseSens != null)
{
for (T param : retValues)
{
// keep the case-sensitive of the target variable value.
((Text) param).setCaseSensitive(caseSens);
}
}
}
if (variable.length == 0)
{
// update the reference, but via a resize
T[] newVar = ArrayAssigner.resize(variable,
retValues.length == 0 ? new int64() : new int64(retValues.length),
false,
false);
ArrayAssigner.assignMulti(newVar, retValues);
setVariable(newVar);
}
else
{
ArrayAssigner.assignMulti(variable, retValues);
}
}
/**
* Register this extent parameter in the current scope.
*/
void registerParameter()
{
getCurrentScope().addExtentParameter(this);
}
}