Call.java
/*
** Module : Call.java
** Abstract : The implementation of a CALL resource.
**
** Copyright (c) 2018-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------------Description----------------------------------------
** 001 CA 20181029 Created initial version.
** 002 CA 20181220 Changed setParameter to have int64 as first argument.
** 003 CA 20190219 Some minor fixes.
** 004 IAS 20190501 Added SECURITY-POLICY support.
** 20190506 Added AUDIT-POLICY, AUDIT-CONTROL support.
** 005 CA 20190628 Changes caused by moving InvokeConfig.invoke to ControlFlowOps.
** 006 OM 20190523 Unified parameter options handling for TempTables and DataSet parameters.
** 007 CA 20190710 Extracted inner classes to their own top-level type, so they can be reused by OO
** ParameterList.
** 008 CA 20200122 Javadoc fixes.
** 009 ME 20200810 Improvements in parameter processing including error handling and delayed validation for
** table/dataset handle input parameters.
** CA 20200924 Replaced Method.invoke with ReflectASM.
** SVL 20201009 Reflected changes in FieldReference.
** ME 20201009 Keep initial value for input/input-output parameters, 4GL uses the value at the time
** setParameter was called although the variable might mutate before invoke.
** CA 20210216 Fixed an issue when setParameter passes an unknown literal as the value argument.
** OM 20210309 Removed double display of error message number.
** ME 20210507 Improve error handling for POLY conversion, OO parameter validation.
** CA 20210525 Fixed a problem with Progress.Lang.Object arguments.
** CA 20210609 Fixed OO property references used as arguments.
** 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.
** CA 20220120 Do not used TypeFactory.object when the OO reference must not be tracked or registered.
** ME 20220214 Fix variable class type check for block's inline variables.
** CA 20220930 Refactored the callback invocation to be performed via a call-site and InvokeConfig, to
** allow caching of the resolved target.
** CA 20221006 UNIQUE-ID is kept as Java type instead of BDT. Refs #6827
** OM 20221124 Added special processing for buffer-fields passed as argument via
** CALL:SET-PARAMETER or to Progress.Lang.ParameterList:SetParameter.
** OM 20221216 Reworked table and dataset handle parameters.
** CA 20230104 Track if an output parameter is associated with a table field, so batch processing is done
** only in these cases, for output parameter assignment.
** CA 20230116 Avoid using handle.unwrap, handle.getReference or other BDT usage from within FWD runtime.
** 010 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 011 CA 20230724 Further reduce context-local usage.
** 012 CA 20230802 Reset the OutputParameterAssigner after invoke finishes, as the parameters are copied
** explicitly, without the OutputParameterAssigner support.
** 013 CA 20231031 Fixed unknown literal parameter.
** 014 CA 20231107 Fixed string representation of the call mode, which must not include any option.
** 015 CA 20240215 Fixed issues using CALL property/field references as arguments, associated with a
** DATASET/TABLE-HANDLE parameter.
** 016 CA 20240708 Allow RETURN ERROR to propagate to the caller for Progress.Lang.Class:Invoke and CALL:Invoke.
** 017 PBB 20250509 Added validation for SetParameter method when parameter is an extent.
** 018 PBB 20250522 Fixed part of the behavior related to SetParameter object method when giving a
** DYNAMIC-PROPERTY call as argument.
** 019 ES 20250522 Added setterAssignment method that validates and assign the value to variable instance.
*/
/*
** 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.*;
import java.util.*;
import java.util.logging.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.goldencode.p2j.convert.*;
import com.goldencode.p2j.library.*;
import com.goldencode.p2j.oo.lang._BaseObject_;
import com.goldencode.p2j.persist.*;
import com.goldencode.p2j.uast.*;
import com.goldencode.p2j.ui.*;
import com.goldencode.p2j.util.UniqueIdGenerator.IdKind;
import com.goldencode.p2j.util.logging.*;
/**
* Defines a CALL resources.
*/
public class Call
extends HandleChain
implements CallResource
{
/** Constant mapping the legacy PROCEDURE-CALL-TYPE value. */
public static final int PROCEDURE_CALL_TYPE = 1;
/** Constant mapping the legacy FUNCTION-CALL-TYPE value. */
public static final int FUNCTION_CALL_TYPE = 2;
/** Constant mapping the legacy GET-ATTR-CALL-TYPE value. */
public static final int GET_ATTR_CALL_TYPE = 3;
/** Constant mapping the legacy SET-ATTR-CALL-TYPE value. */
public static final int SET_ATTR_CALL_TYPE = 4;
/** Constant mapping the legacy DLL-CALL-TYPE value. */
public static final int DLL_CALL_TYPE = 5;
/** Logger */
static final CentralLogger LOG = CentralLogger.get(Call.class.getName());
/** The call-site associated with the call's invocation. */
private static final InvokeConfig CALL_SITE = new InvokeConfig();
/** The first value of valid call types. */
private static final int BEGIN_CALL_TYPE = PROCEDURE_CALL_TYPE;
/** The last value of valid call types. */
private static final int END_CALL_TYPE = DLL_CALL_TYPE;
/** A cache of attributes and methods token types to their actual type. */
private static final Map<Integer, Integer> ATTRIBUTES_AND_METHODS =
ProgressParser.getAttributesAndMethods();
/** A cache of system handle keywords. */
private static final Set<String> SYS_HANDLES = ProgressParser.getSysHandles();
/** A cache of data types. */
private static final Set<String> DATA_TYPES = new HashSet<>();
private static Pattern dataTypePattern = Pattern.compile("^(CLASS\\s+)?([\\w|\\.]+)(\\s+EXTENT(\\s+\\d+)?)?$", Pattern.CASE_INSENSITIVE);
static
{
DATA_TYPES.add("BLOB");
DATA_TYPES.add("CHARACTER");
DATA_TYPES.add("CLOB");
DATA_TYPES.add("DATASET-HANDLE");
DATA_TYPES.add("DATE");
DATA_TYPES.add("DATETIME");
DATA_TYPES.add("DATETIME-TZ");
DATA_TYPES.add("DECIMAL");
DATA_TYPES.add("HANDLE");
DATA_TYPES.add("INT64");
DATA_TYPES.add("INTEGER");
DATA_TYPES.add("LOGICAL");
DATA_TYPES.add("LONGCHAR");
DATA_TYPES.add("MEMPTR");
DATA_TYPES.add("RAW");
DATA_TYPES.add("RECID");
DATA_TYPES.add("ROWID");
DATA_TYPES.add("TABLE-HANDLE");
}
/** The value of the ADM-DATA attribute. */
private String admData = null;
/** Corresponds to UNIQUE-ID attribute. */
private Long uniqueID = null;
/** Flag indicating if this resource has been deleted or not. */
private boolean deleted = false;
/** The value of the CALL-NAME attribute. */
private String callName;
/** The value of the CALL-TYPE attribute. */
private int callType;
/** The value of the IN-HANDLE attribute. */
private handle inHandle;
/** The value of the NUM-PARAMETERS attribute. */
private int numParameters;
/** The value of the RETURN-VALUE attribute. */
private BaseDataType returnValue;
/** The value of the RETURN-VALUE-DATA-TYPE attribute. */
private String returnValueDataType;
/** The value of the SERVER attribute. */
private handle server;
/** The value of the PERSISTENT attribute. */
private boolean persistent;
/** Flag indicating if this dynamic run will be emulated in SINGLE-RUN mode. */
private boolean singleRun;
/** Flag indicating if this dynamic run will be emulated in SINGLETON mode. */
private boolean singleton;
/** The value of the PROCEDURE-TYPE attribute. */
private String procedureType;
/** The value of the THREAD-SAFE attribute. */
private boolean threadSafe;
/** The value of the ASYNCHRONOUS attribute. */
private boolean async;
/** The value of the ASYNC-REQUEST-HANDLE attribute. */
private handle asyncHandle;
/** The value of the EVENT-PROCEDURE attribute. */
private String eventProcedure;
/** The value of the EVENT-PROCEDURE-CONTEXT attribute. */
private handle eventProcedureContext;
/** The value of the ORDINAL attribute. */
private Integer ordinal;
/** The value of the LIBRARY attribute. */
private String library;
/** The value of the RETURN-VALUE-DLL-TYPE attribute. */
private String returnValueDllType;
/** The value of the LIBRARY-CALLING-CONVENTION attribute. */
private String libraryCallingConvention;
/**
* The list of configured parameters. Created when {@link #setNumParameters NUM-PARAMETERS}
* attribute is set.
*/
private CallParameter[] parameters;
/**
* Create a new resource and {@link #clear() initialize} it with the default values.
*/
private Call()
{
super(true, false);
clear();
}
/**
* Create a new resource and assign it to the specified handle.
*
* @param h
* The handle where to save the resource. Must be not-null.
*/
public static void create(handle h)
{
create(h, (character) null);
}
/**
* Create a new resource and assign it to the specified handle.
* <p>
* If a widget pool is not specified, then the resource will be added to the SESSION pool, and
* not the closest unnamed pool.
*
* @param h
* The handle where to save the resource. Must be not-null.
* @param widgetPool
* The named widget pool where to save the widget. If <code>null</code>, the SESSION
* widget pool will be used.
*/
public static void create(handle h, character widgetPool)
{
// validate widget pool before anything else
// unnamed pools are not used here, if one not specified, the SESSION pool will be used
if (widgetPool != null && !WidgetPool.validWidgetPool(widgetPool))
{
return;
}
Call c = new Call();
h.assign(c);
if (widgetPool != null)
{
c.addToPool(widgetPool);
}
}
/**
* Create a new resource and assign it to the specified handle.
* <p>
* If a widget pool is not specified, then the resource will be added to the SESSION pool, and
* not the closest unnamed pool.
*
* @param h
* The handle where to save the resource. Must be not-null.
* @param widgetPool
* The named widget pool where to save the widget. If <code>null</code>, the SESSION
* widget pool will be used.
*/
public static void create(handle h, String widgetPool)
{
create(h, new character(widgetPool));
}
/**
* Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
* properly detect whether it is a valid output parameter. In affirmative case, this static method will
* return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
* <p>
* This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
* at conversion to be a {@code BUFFER-VALUE()} result.
*
* @param aHandle
* A handle, possible of a buffer-field.
*
* @return If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
* created and returned. Otherwise, the original code is restored.
*/
public static Object valueReference(handle aHandle)
{
if (aHandle.isType(LegacyResource.BUFFER_FIELD))
{
BufferFieldImpl field = (BufferFieldImpl) aHandle.getResource();
return field.createFieldReference(null);
}
return aHandle.unwrapBufferField().value();
}
/**
* Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
* properly detect whether it is a valid output parameter. In affirmative case, this static method will
* return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
* <p>
* This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
* at conversion to be a {@code BUFFER-VALUE(k)} result.
*
* @param aHandle
* A handle, possible of a buffer-field.
* @param extIndex
* The index for a possible extent field.
*
* @return If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
* created and returned. Otherwise, the original code is restored.
*/
public static Object valueReference(handle aHandle, Integer extIndex)
{
if (aHandle.isType(LegacyResource.BUFFER_FIELD))
{
BufferFieldImpl field = (BufferFieldImpl) aHandle.getResource();
return field.createFieldReference(extIndex);
}
return aHandle.unwrapBufferField().value(extIndex);
}
/**
* Used for wrapping the last parameter of {@code CALL:SET-PARAMETER} method in order for the method to
* properly detect whether it is a valid output parameter. In affirmative case, this static method will
* return a {@code BaseDataTypeVariable}. Otherwise, the original object is returned.
* <p>
* This wrapper is injected only when the last parameter (the value) of {@code SET-PARAMETER} is detected
* at conversion to be a {@code BUFFER-VALUE(k)} result.
*
* @param aHandle
* A handle, possible of a buffer-field.
* @param k
* The index for a possible extent field.
*
* @return If the handle is indeed a valid BUFFER-FIELD, a custom-made {@code BaseDataTypeVariable} is
* created and returned. Otherwise, the original code is restored.
*/
public static Object valueReference(handle aHandle, NumberType k)
{
return valueReference(aHandle, k.isUnknown() ? null : k.intValue());
}
/**
* Get the value of the ADM-DATA attribute.
*
* @return See above.
*/
@Override
public character getADMData()
{
return new character(admData);
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value
* The new value.
*/
@Override
public void setADMData(String value)
{
this.admData = value;
}
/**
* Set the value of the ADM-DATA attribute.
*
* @param value
* The new value.
*/
@Override
public void setADMData(character value)
{
this.admData = value.getValue();
}
/**
* Gets the the unique ID number associated to this object by the
* underlying system. Not the same as the handle value itself.
*
* @return The integer number of the children.
*/
@Override
public integer getUniqueID()
{
if (uniqueID == null)
{
uniqueID = UniqueIdGenerator.getUniqueId(IdKind.CALL);
}
return new integer(uniqueID);
}
/**
* Reports if this object is valid for use.
*
* @return <code>true</code> if we are valid (can be used).
*/
@Override
public boolean valid()
{
return !deleted;
}
/**
* Get the <code>persistent</code> attribute of this procedure instance.
*
* @return See above.
*/
@Override
public logical isPersistent()
{
return new logical(persistent);
}
/**
* Set the <code>persistent</code> attribute of this CALL request.
*
* @param persistent
* The flag indicating how the call will be performed.
*/
@Override
public void setPersistent(boolean persistent)
{
if (singleRun || singleton)
{
int[] nums = { 17255, 3131 };
String[] texts =
{
"Cannot set PERSISTENT attribute to TRUE when PROCEDURE-TYPE attribute has " +
"already been set to " + procedureType + " (17255)",
"Unable to set attribute PERSISTENT in widget of type CALL. (3131)"
};
ErrorManager.recordOrThrowError(nums, texts, false, true);
return;
}
this.persistent = persistent;
this.procedureType = "PERSISTENT";
}
/**
* Set the <code>persistent</code> attribute of this CALL request.
*
* @param persistent
* The flag indicating how the call will be performed.
*/
@Override
public void setPersistent(logical persistent)
{
if (persistent.isUnknown())
{
unknownWarning("PERSISTENT");
return;
}
setPersistent(persistent.booleanValue());
}
/**
* Returns a handle to the app server in which the procedure is ran.
*
* @return See above
*/
@Override
public handle getServerHandle()
{
return server == null ? new handle() : new handle(server);
}
/**
* Sets a handle for the app server in which the procedure will be ran.
*
* @param h
* The server handle.
*/
@Override
public void setServerHandle(handle h)
{
if (h.isUnknown())
{
unknownWarning("SERVER");
return;
}
server = new handle(h);
}
/**
* Get the <code>CALL-NAME</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getCallName()
{
return callName == null ? new character() : new character(callName);
}
/**
* Set the <code>CALL-NAME</code> attribute of this CALL resource.
*
* @param callName
* The target's name (attribute, method, RUN's invoke target, native API).
*/
@Override
public void setCallName(character callName)
{
if (callName.isUnknown())
{
unknownWarning("CALL-NAME");
return;
}
setCallName(callName.getValue());
}
/**
* Set the <code>CALL-NAME</code> attribute of this CALL resource.
*
* @param callName
* The target's name (attribute, method, RUN's invoke target, native API).
*/
@Override
public void setCallName(String callName)
{
if (ordinal != null)
{
callOrdinalError("CALL-NAME");
return;
}
this.callName = callName;
}
/**
* Get the <code>CALL-TYPE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public integer getCallType()
{
return new integer(callType);
}
/**
* Set the <code>CALL-TYPE</code> attribute of this CALL resource.
*
* @param callType
* One of the call-type constant values.
*/
@Override
public void setCallType(NumberType callType)
{
if (callType.isUnknown())
{
unknownWarning("CALL-TYPE");
return;
}
setCallType(callType.intValue());
}
/**
* Set the <code>CALL-TYPE</code> attribute of this CALL resource.
*
* @param callType
* One of the call-type constant values.
*/
@Override
public void setCallType(int callType)
{
if (!(callType >= BEGIN_CALL_TYPE && callType <= END_CALL_TYPE))
{
unableToSetAttribute(10066,
"CALL object CALL-TYPE must be an integer between 1 and 5",
"CALL-TYPE");
return;
}
this.callType = callType;
}
/**
* Check if the specified parameter is set.
*
* @param paramNum
* The 1-based parameter index.
*
* @return <code>true</code> if the parameter is set.
*/
@Override
public logical isParameterSet(NumberType paramNum)
{
if (paramNum.isUnknown())
{
// no warning is shown
return new logical();
}
return isParameterSet(paramNum.intValue());
}
/**
* Check if the specified parameter is set.
*
* @param paramNum
* The 1-based parameter index.
*
* @return <code>true</code> if the parameter is set.
*/
@Override
public logical isParameterSet(int paramNum)
{
if (parameters == null)
{
// no warning is shown
return new logical();
}
if (paramNum < 1 || paramNum > numParameters)
{
// no warning is shown
return new logical();
}
return new logical(parameters[paramNum - 1] != null);
}
/**
* Get the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public integer getNumParameters()
{
return new integer(numParameters);
}
/**
* Set the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
*
* @param num
* The number of parameters.
*/
@Override
public void setNumParameters(NumberType num)
{
if (num.isUnknown() || num.intValue() < 1)
{
if (num.isUnknown())
{
unknownWarning("NUM-PARAMETERS");
}
parameters = null;
if (!num.isUnknown())
{
numParameters = num.intValue();
}
return;
}
numParameters = num.intValue();
parameters = new CallParameter[numParameters];
}
/**
* Set the <code>NUM-PARAMETERS</code> attribute of this CALL resource.
*
* @param num
* The number of parameters.
*/
@Override
public void setNumParameters(long num)
{
setNumParameters(new integer(num));
}
/**
* Get the <code>IN-HANDLE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public handle getInHandle()
{
return inHandle == null ? new handle() : new handle(inHandle);
}
/**
* Set the <code>IN-HANDLE</code> attribute of this CALL resource.
*
* @param h
* The external program where the target function/procedure will be invoked or the
* resource for which the attribute/method will be accessed.
*/
@Override
public void setInHandle(handle h)
{
if (h.isUnknown())
{
unknownWarning("IN-HANDLE");
return;
}
inHandle = new handle(h);
}
/**
* Set the <code>IN-HANDLE</code> attribute of this CALL resource.
*
* @param resource
* The external program where the target function/procedure will be invoked or the
* resource for which the attribute/method will be accessed.
*/
@Override
public void setInHandle(character resource)
{
setInHandle(resource.isUnknown() ? null : resource.getValue());
}
/**
* Set the <code>IN-HANDLE</code> attribute of this CALL resource.
*
* @param resource
* The external program where the target function/procedure will be invoked or the
* resource for which the attribute/method will be accessed.
*/
@Override
public void setInHandle(String resource)
{
boolean valid = (resource != null);
if (!valid)
{
unknownWarning("IN-HANDLE");
return;
}
else
{
// the IN-HANDLE is reset now
inHandle = null;
String tokenText = ProgressAst._keyword(resource);
valid = (tokenText != null && SYS_HANDLES.contains(tokenText));
if (valid)
{
int tokenType = ProgressAst.lookupKeywordToken(resource);
switch (tokenType)
{
case ProgressParserTokenTypes.KW_ACT_WIN:
inHandle = LogicalTerminal.activeWindow();
break;
case ProgressParserTokenTypes.KW_AUD_CTRL:
inHandle = AuditControlManager.asHandle();
break;
case ProgressParserTokenTypes.KW_AUD_POL:
inHandle = AuditPolicyManager.asHandle();
break;
case ProgressParserTokenTypes.KW_CLIP:
inHandle = ClipboardManager.asHandle();
break;
case ProgressParserTokenTypes.KW_CODEBASE:
// TODO: assign this after FWD supports this resource
break;
case ProgressParserTokenTypes.KW_COLR_TAB:
inHandle = ColorTable.asHandle();
break;
case ProgressParserTokenTypes.KW_COM_SELF:
// TODO: assign this after FWD supports this resource
break;
case ProgressParserTokenTypes.KW_COMPILER:
// TODO: assign this after FWD supports this resource
break;
case ProgressParserTokenTypes.KW_CUR_WIN:
inHandle = LogicalTerminal.currentWindow();
break;
case ProgressParserTokenTypes.KW_DEBUGGER:
// TODO: assign this after FWD supports this resource
break;
case ProgressParserTokenTypes.KW_DEF_WIN:
inHandle = LogicalTerminal.defaultWindow();
break;
case ProgressParserTokenTypes.KW_DSL_MGR:
inHandle = LegacyLogOps.dsLogMgrHandle();
break;
case ProgressParserTokenTypes.KW_ERR_STAT:
inHandle = ErrorManager.asHandle();
break;
case ProgressParserTokenTypes.KW_FIL_INFO:
inHandle = FileSystemOps.asHandle();
break;
case ProgressParserTokenTypes.KW_FOCUS:
inHandle = LogicalTerminal.focus();
break;
case ProgressParserTokenTypes.KW_FONT_TAB:
inHandle = FontTable.asHandle();
break;
case ProgressParserTokenTypes.KW_LAST_EVT:
inHandle = KeyReader.asHandle();
break;
case ProgressParserTokenTypes.KW_LOG_MGR:
inHandle = LegacyLogOps.logMgrHandle();
break;
case ProgressParserTokenTypes.KW_PROFILER:
inHandle = ProfilerUtils.asHandle();
break;
case ProgressParserTokenTypes.KW_RCOD_INF:
inHandle = RcodeInfoOps.asHandle();
break;
case ProgressParserTokenTypes.KW_SECUR_P:
inHandle = SecurityPolicyManager.asHandle();
break;
case ProgressParserTokenTypes.KW_SELF:
inHandle = LogicalTerminal._self();
break;
case ProgressParserTokenTypes.KW_SESSION:
inHandle = SessionUtils.asHandle();
break;
case ProgressParserTokenTypes.KW_SRC_PROC:
inHandle = ProcedureManager.sourceProcedure();
break;
case ProgressParserTokenTypes.KW_TAR_PROC:
inHandle = ProcedureManager.targetProcedure();
break;
case ProgressParserTokenTypes.KW_THIS_PRC:
inHandle = ProcedureManager.thisProcedure();
break;
case ProgressParserTokenTypes.KW_WEB_CTX:
// TODO: assign this after FWD supports this resource
break;
}
valid = (inHandle != null);
}
}
if (!valid)
{
int[] nums = new int[] { 10064, 3131 };
String[] texts = new String[]
{
"Invalid character system handle expression given to IN-HANDLE (must be a system " +
"handle name like SESSION, FILE-INFO, etc.)",
"Unable to set attribute IN-HANDLE in widget of type CALL"
};
ErrorManager.recordOrThrowError(nums, texts, false);
return;
}
}
/**
* Get the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getProcedureType()
{
return procedureType == null ? new character() : new character(procedureType);
}
/**
* Set the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
*
* @param procType
* The procedure's type, one of PERSISTENT, SINGLE-RUN or SINGLETON strings.
*/
@Override
public void setProcedureType(character procType)
{
if (procType.isUnknown())
{
// no error is raised
procType = null;
return;
}
setProcedureType(procType.getValue());
}
/**
* Set the <code>PROCEDURE-TYPE</code> attribute of this CALL resource.
*
* @param procType
* The procedure's type, one of PERSISTENT, SINGLE-RUN or SINGLETON strings.
*/
@Override
public void setProcedureType(String procType)
{
String stype = procType.toUpperCase();
if (!("PERSISTENT".equals(stype) || "SINGLE-RUN".equals(stype) || "SINGLETON".equals(stype)))
{
// Unable to set PROCEDURE-TYPE CALL attribute (15482)
ErrorManager.recordOrShowError(15482,
"Unable to set PROCEDURE-TYPE CALL attribute (15482)",
false, false, false, true);
return;
}
if (persistent && !"PERSISTENT".equals(stype))
{
int[] nums = { 17254, 3131 };
String[] texts =
{
"Cannot set PROCEDURE-TYPE attribute to " + stype + " when PERSISTENT attribute " +
"has already been set to TRUE (17254)",
"Unable to set attribute PROCEDURE-TYPE in widget of type CALL. (3131)"
};
ErrorManager.recordOrThrowError(nums, texts, false, true);
return;
}
this.procedureType = stype;
this.persistent = "PERSISTENT".equals(stype);
this.singleRun = "SINGLE-RUN".equals(stype);
this.singleton = "SINGLETON".equals(stype);
}
/**
* Get the <code>THREAD-SAFE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public logical isThreadSafe()
{
return new logical(threadSafe);
}
/**
* Set the <code>THREAD-SAFE</code> attribute of this CALL resource.
*
* @param threadSafe
* The new state of this flag.
*/
@Override
public void setThreadSafe(logical threadSafe)
{
if (threadSafe.isUnknown())
{
unknownWarning("THREAD-SAFE");
return;
}
setThreadSafe(threadSafe.booleanValue());
}
/**
* Set the <code>THREAD-SAFE</code> attribute of this CALL resource.
*
* @param threadSafe
* The new state of this flag.
*/
@Override
public void setThreadSafe(boolean threadSafe)
{
this.threadSafe = threadSafe;
}
/**
* Get the <code>RETURN-VALUE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public BaseDataType getReturnValue()
{
return returnValue == null ? new character() : returnValue;
}
/**
* Get the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getReturnValueDataType()
{
return returnValueDataType == null ? new character() : new character(returnValueDataType);
}
/**
* Set the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
*
* @param dataType
* The return type for the next call.
*/
@Override
public void setReturnValueDataType(character dataType)
{
if (dataType.isUnknown())
{
unknownWarning("RETURN-VALUE-DATA-TYPE");
return ;
}
setReturnValueDataType(dataType.getValue());
}
/**
* Set the <code>RETURN-VALUE-DATA-TYPE</code> attribute of this CALL resource.
*
* @param dataType
* The return type for the next call.
*/
@Override
public void setReturnValueDataType(String dataType)
{
dataType = ProgressAst._keyword(dataType);
if ("log".equalsIgnoreCase(dataType))
{
// we have a conflict with 'log' function
dataType = "logical";
}
if (BaseDataType.fromTypeName(dataType) == null)
{
unableToSetAttribute(10067,
"Unable to set CALL object RETURN-VALUE-DATA-TYPE to " + dataType,
"RETURN-VALUE-DATA-TYPE");
return;
}
this.returnValueDataType = dataType;
}
/**
* Get the <code>LIBRARY</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getLibrary()
{
return library == null ? new character() : new character(library);
}
/**
* Set the <code>LIBRARY</code> attribute of this CALL resource.
*
* @param libName
* The native library name.
*/
@Override
public void setLibrary(character libName)
{
if (libName.isUnknown())
{
unknownWarning("LIBRARY");
return;
}
setLibrary(libName.getValue());
}
/**
* Set the <code>LIBRARY</code> attribute of this CALL resource.
*
* @param libName
* The native library name.
*/
@Override
public void setLibrary(String libName)
{
if (this.library != null)
{
// this is reset if the library name was previously set
callName = "";
}
this.library = libName;
}
/**
* Get the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getLibraryCallingConvention()
{
return libraryCallingConvention == null ? new character()
: new character(libraryCallingConvention);
}
/**
* Set the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
*
* @param libCall
* The library's calling convention, CDECL or STDCALL.
*/
@Override
public void setLibraryCallingConvention(character libCall)
{
if (libCall.isUnknown())
{
unknownWarning("LIBRARY-CALLING-CONVENTION");
return;
}
setLibraryCallingConvention(libCall.getValue());
}
/**
* Set the <code>LIBRARY-CALLING-CONVENTION</code> attribute of this CALL resource.
*
* @param libCall
* The library's calling convention, CDECL or STDCALL.
*/
@Override
public void setLibraryCallingConvention(String libCall)
{
libCall = libCall.toUpperCase();
if (!("STDCALL".equals(libCall) || "CDECL".equals(libCall)))
{
// Unable to set LIBARARY-CALLING-COVENTION CALL attribute (15482)
ErrorManager.recordOrShowError(15482,
"Unable to set LIBARARY-CALLING-COVENTION CALL attribute (15482)",
false, false, false, true);
return;
}
libraryCallingConvention = libCall;
}
/**
* Get the <code>ORDINAL</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public integer getOrdinal()
{
return ordinal == null ? new integer() : new integer(ordinal);
}
/**
* Set the <code>ORDINAL</code> attribute of this CALL resource.
*
* @param ordinal
* The native API ordinal, if CALL-NAME is not specified.
*/
@Override
public void setOrdinal(NumberType ordinal)
{
if (ordinal.isUnknown())
{
unknownWarning("ORDINAL");
return;
}
setOrdinal(ordinal.intValue());
}
/**
* Set the <code>ORDINAL</code> attribute of this CALL resource.
*
* @param ordinal
* The native API ordinal, if CALL-NAME is not specified.
*/
@Override
public void setOrdinal(int ordinal)
{
if (callName != null)
{
callOrdinalError("ORDINAL");
return;
}
this.ordinal = ordinal;
}
/**
* Get the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public character getReturnValueDllType()
{
return returnValueDllType == null ? new character() : new character(returnValueDllType);
}
/**
* Set the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
*
* @param dataType
* The return type for the next native API call.
*/
@Override
public void setReturnValueDllType(character dataType)
{
if (dataType.isUnknown())
{
unknownWarning("RETURN-VALUE-DLL-TYPE");
return;
}
setReturnValueDllType(dataType.getValue());
}
/**
* Set the <code>RETURN-VALUE-DLL-TYPE</code> attribute of this CALL resource.
*
* @param dataType
* The return type for the next native API call.
*/
@Override
public void setReturnValueDllType(String dataType)
{
dataType = dataType.toLowerCase();
if (!validDllType(dataType))
{
unableToSetAttribute(15483,
"Unable to set CALL object RETURN-VALUE-DLL-TYPE to " + dataType,
"RETURN-VALUE-DLL-TYPE");
return;
}
this.returnValueDllType = dataType;
}
/**
* Get the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public logical isAsync()
{
return new logical(async);
}
/**
* Set the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
*
* @param l
* The new state of this flag.
*/
@Override
public void setAsync(logical l)
{
if (l.isUnknown())
{
unknownWarning("ASYNCHRONOUS");
return;
}
setAsync(l.booleanValue());
}
/**
* Set the <code>ASYNCHRONOUS</code> attribute of this CALL resource.
*
* @param l
* The new state of this flag.
*/
@Override
public void setAsync(boolean l)
{
this.async = l;
}
/**
* Get the <code>ASYNC-REQUEST-HANDLE</code> attribute of this CALL resource.
*
* @return See above.
*/
@Override
public handle getAsyncHandle()
{
return new handle(asyncHandle);
}
/**
* Get the <code>EVENT-PROCEDURE</code> attribute of this async request.
*
* @return See above.
*/
@Override
public character getEventProcedure()
{
return eventProcedure == null ? new character() : new character(eventProcedure);
}
/**
* Set the <code>EVENT-PROCEDURE</code> attribute of this async request.
*
* @param procName
* The procedure name.
*/
@Override
public void setEventProcedure(character procName)
{
if (procName.isUnknown())
{
unknownWarning("EVENT-PROCEDURE");
return;
}
setEventProcedure(procName.getValue());
}
/**
* Set the <code>EVENT-PROCEDURE</code> attribute of this async request.
*
* @param procName
* The procedure name.
*/
@Override
public void setEventProcedure(String procName)
{
this.eventProcedure = procName;
}
/**
* Get the <code>EVENT-PROCEDURE-CONTEXT</code> attribute of this async request.
*
* @return See above.
*/
@Override
public handle getEventProcedureContext()
{
return eventProcedureContext == null ? new handle() : new handle(eventProcedureContext);
}
/**
* Set the <code>EVENT-PROCEDURE-CONTEXT</code> attribute of this async request.
*
* @param h
* The procedure handle.
*/
@Override
public void setEventProcedureContext(handle h)
{
if (h.isUnknown())
{
unknownWarning("EVENT-PROCEDURE-CONTEXT");
return;
}
eventProcedureContext = new handle(h);
}
/**
* Returns the resource to its clear state.
*
* @return true if operation is successful
*/
@Override
public logical clear()
{
callName = null;
callType = PROCEDURE_CALL_TYPE;
inHandle = null;
server = null;
numParameters = 0;
parameters = null;
returnValue = null;
returnValueDataType = null;
persistent = false;
procedureType = null;
threadSafe = false;
async = false;
asyncHandle = new handle();
eventProcedure = null;
eventProcedureContext = null;
ordinal = null;
library = null;
returnValueDllType = null;
libraryCallingConvention = "STDCALL";
return new logical(true);
}
/**
* Execute this dynamic invoke, based on the current CALL configuration.
* <p>
* The dynamic invoke will be delegated to {@link #accessAttributeOrMethod} in case of
* {@link #GET_ATTR_CALL_TYPE} and {@link #SET_ATTR_CALL_TYPE} modes.
* <p>
* For {@link #PROCEDURE_CALL_TYPE} and {@link #FUNCTION_CALL_TYPE}, the call will be delegated
* to {@link ControlFlowOps#invoke(InvokeConfig)}, which in turn will relies on the normal
* {@link ControlFlowOps} APIs to perform the call. If the {@link #PROCEDURE_CALL_TYPE} mode
* is used and the target is resolved as an <code>PROCEDURE ... EXTERNAL</code>, then this
* will still be invoked using the RUN emulated call.
* <p>
* The {@link #DLL_CALL_TYPE} mode will rely on the
* {@link NativeInvoker#invoke(NativeAPIEntry, boolean, String, Class[], String, Object...)}:
* this will use a synthetic {@link NativeAPIEntry}, which will be configured as if a
* <code>PROCEDURE ... EXTERNAL</code> was defined in the 4GL code, using the current
* {@link Call CALL's} configuration. The actual invoke will bypass the {@link ControlFlowOps}
* APIs for this mode.
*/
@Override
public void invoke()
{
if (callName == null && ordinal == null)
{
ErrorManager.recordOrThrowError(14351,
"CALL-NAME or ORDINAL must be specified before using " +
"INVOKE with a CALL object",
false);
return;
}
if (parameters != null)
{
for (int i = 0; i < parameters.length; i++)
{
if (parameters[i] == null)
{
ErrorManager.recordOrThrowError(10044,
"All parameters must be specified with " +
"SET-PARAMETER before using INVOKE for " + callName,
false);
return;
}
}
}
int ct = callType;
if (ct == DLL_CALL_TYPE && (library == null || inHandle != null))
{
ct = PROCEDURE_CALL_TYPE;
}
switch (ct)
{
case FUNCTION_CALL_TYPE:
case PROCEDURE_CALL_TYPE:
// if inHandle is set, must be a procedure - but validate it at the CFO call
break;
case DLL_CALL_TYPE:
// nothing to do
break;
case GET_ATTR_CALL_TYPE:
case SET_ATTR_CALL_TYPE:
// must be a valid resource
if (inHandle == null || !inHandle._isValid())
{
ErrorManager.recordOrShowError(10091,
"CALL:INVOKE finds an invalid CALL:IN-HANDLE for " +
"GET/SET-ATTR-CALL-TYPE. It must be a valid 4gl " +
"OBJECT HANDLE",
false);
return;
}
break;
}
boolean silent = ErrorManager.isSilent();
boolean function = false;
switch (ct)
{
case FUNCTION_CALL_TYPE:
function = true;
case PROCEDURE_CALL_TYPE:
InvokeConfig cfg = buildInvokeConfig(function);
if (!silent)
{
// assume a silent mode, so that errors can be captured and re-raised
ErrorManager.setSilent(true);
}
boolean ran = false;
try
{
if (parameters != null)
{
for (CallParameter cparam : parameters)
{
Object arg = cparam.getArgument();
if (arg instanceof AbstractExtentParameter)
{
AbstractParameter.getCurrentScope()
.addExtentParameter((AbstractExtentParameter<?>) arg);
}
else if (arg instanceof AbstractSimpleParameter)
{
AbstractParameter.getCurrentScope()
.addSimpleParameter((AbstractSimpleParameter) arg);
}
}
}
BaseDataType val = cfg.executeForResource(this);
ran = true;
if (!silent && ErrorManager.isPendingError())
{
// raise a condition, this is from a RETURN ERROR - will be re-thrown in catch
throw new ErrorConditionException("return error");
}
// now, we need to copy back the output and input-output arguments
if (parameters != null)
{
for (CallParameter cparam : parameters)
{
if (cparam.mode.output || cparam.mode.inputOutput)
{
if (!cparam.copyOutput())
{
// if a parameter copy failed, stop processing
break;
}
}
}
}
// set return type
if (function && val != null)
{
returnValue = val;
returnValueDataType = val.getTypeName().toLowerCase();
}
else
{
returnValue = null;
returnValueDataType = null;
}
}
catch (LegacyErrorException lex)
{
throw lex;
}
catch (ErrorConditionException ece)
{
ErrorManager.setSilent(silent);
if (ran || ControlFlowOps.hadInvalidArguments())
{
// just re-throw it, this was an invalid argument issue
throw ece;
}
boolean pendingError = ErrorManager.isPendingError();
ErrorManager.clearPending();
int[] nums =
{
ece.getProgressErrorCode(),
5729,
10089
};
String[] texts =
{
ece.getMessage(),
"Incompatible datatypes found during runtime conversion",
"Unable to update output parameter for dynamic INVOKE"
};
if (function || !pendingError)
{
ErrorManager.recordOrShowError(nums, texts, false, false, false, true);
}
else
{
ErrorManager.recordOrThrowError(nums, texts, false, true);
}
}
finally
{
ErrorManager.setSilent(silent);
OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
if (opa != null)
{
opa.abort();
}
}
break;
case DLL_CALL_TYPE:
boolean hasRetParam = returnValueDllType != null;
Object[] args = new Object[numParameters + (hasRetParam ? 1 : 0)];
Class<?>[] signInput = new Class[numParameters + (hasRetParam ? 1 : 0)];
List<Parameter> nativeParameters = new ArrayList<>();
boolean noInvoke = false;
for (int i = 0; i < numParameters; i++)
{
CallParameter cparam = parameters[i];
if (!(cparam instanceof NativeCallParameter ||
cparam instanceof ExtentCallParameter))
{
// in this case, the run is aborted - it will only look if the entry exists in
// the library; no actual invoke is performed
noInvoke = true;
}
Object arg = cparam.getArgument();
if (arg instanceof AbstractExtentParameter)
{
AbstractParameter.getCurrentScope()
.addExtentParameter((AbstractExtentParameter<?>) arg);
}
else if (arg instanceof AbstractSimpleParameter)
{
AbstractParameter.getCurrentScope()
.addSimpleParameter((AbstractSimpleParameter) arg);
}
args[i] = arg;
signInput[i] = args[i].getClass();
CallMode cmode = parameters[i].mode;
String smode = cmode.input ? "INPUT"
: (cmode.output ? "OUTPUT" : "INPUT-OUTPUT");
Parameter param = new Parameter("call-param" + i, cparam.dataType, smode);
nativeParameters.add(param);
}
if (hasRetParam)
{
// add a new parameter with mode "RETURN", last
Parameter param = new Parameter("CALL-RET", returnValueDllType, "RETURN");
nativeParameters.add(param);
BaseDataType retParam = null;
switch (returnValueDllType)
{
case "byte":
case "short":
case "long":
case "unsigned-long":
case "unsigned-short":
retParam = new integer();
break;
case "int64":
retParam = new int64();
break;
case "character":
retParam = new character();
break;
case "double":
case "float":
retParam = new decimal();
break;
case "memptr":
retParam = new memptr();
break;
}
args[numParameters] = retParam;
signInput[numParameters] = retParam.getClass();
}
NativeAPIEntry nativeEntry = new NativeAPIEntry(callName, callName);
nativeEntry.setLibraryName(library);
nativeEntry.setPersistent(persistent);
if (ordinal != null)
{
nativeEntry.setOrdinal(ordinal);
}
nativeEntry.setThreadSafe(threadSafe);
nativeEntry.setCallingConvention(CallingConvention.valueOf(libraryCallingConvention));
nativeEntry.setParameterList(nativeParameters);
nativeEntry.initialize();
String modes = nativeEntry.getParameterModes();
boolean failed = false;
OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
try
{
NativeInvoker.invoke(nativeEntry, noInvoke, "DYNAMIC CALL", signInput, modes, args);
failed = ErrorManager.isPendingError();
}
catch (Exception exc)
{
failed = true;
throw exc;
}
finally
{
if (opa != null)
{
if (failed)
{
opa.abort();
}
else
{
opa.processAssignments(callName, false);
}
}
}
if (returnValueDllType != null)
{
returnValue = (BaseDataType) args[args.length - 1];
returnValueDataType = returnValue.getTypeName().toLowerCase();
}
else
{
returnValue = null;
returnValueDataType = null;
}
break;
case GET_ATTR_CALL_TYPE:
accessAttributeOrMethod(false);
break;
case SET_ATTR_CALL_TYPE:
accessAttributeOrMethod(true);
break;
}
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(int64 parmNum, character dataType, character ioMode, Object value)
{
if (numParameters <= 0)
{
// show error, do not raise
// Cannot SET-PARAMETER before specifying NUM-PARAMETERS for the CALL object. (10051)
ErrorManager.recordOrShowError(10051,
"Cannot SET-PARAMETER before specifying NUM-PARAMETERS " +
"for the CALL object",
false, false, false);
return new logical(false);
}
if (parmNum.isUnknown())
{
// SET-PARAMETER index must be 1 based and less than or equal to1. (10052)
ErrorManager.recordOrThrowError(10052,
"SET-PARAMETER index must be 1 based and less than or " +
"equal to" + numParameters,
false);
return new logical(false);
}
int pnum = parmNum.intValue();
if (pnum < 1 || pnum > numParameters)
{
// SET-PARAMETER index must be 1 based and less than or equal to1. (10052)
ErrorManager.recordOrThrowError(10052,
"SET-PARAMETER index must be 1 based and less than or " +
"equal to" + numParameters,
false);
return new logical(false);
}
CallMode cmode = CallMode.buildMode(ioMode);
if (cmode == null)
{
ErrorManager.recordOrThrowError(10057,
"SET-PARAMETER input-output mode must be one of " +
"INPUT, OUTPUT, INPUT-OUTPUT, OUTPUT-APPEND, enclosed " +
"in quotes or in an expression.. (10057)",
false,
true);
return new logical(false);
}
if (!cmode.input && !validOutputParameter(pnum, value))
{
return new logical(false);
}
if (dataType.isUnknown())
{
ErrorManager.recordOrThrowError(10053,
"SET-PARAMETER datatype ? is not supported for CALL object",
false);
return new logical(false);
}
CallParameter param = createParameter(dataType.getValue(), cmode, value, library != null, false);
if (param == null)
{
return new logical(false);
}
parameters[pnum - 1] = param;
return new logical(true);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(long parmNum, character dataType, character ioMode, Object value)
{
return setParameter(new integer(parmNum), dataType, ioMode, value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(int64 parmNum, String dataType, character ioMode, Object value)
{
return setParameter(parmNum, new character(dataType), ioMode, value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(long parmNum, String dataType, character ioMode, Object value)
{
return setParameter(new integer(parmNum), new character(dataType), ioMode, value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(int64 parmNum, character dataType, String ioMode, Object value)
{
return setParameter(parmNum, dataType, new character(ioMode), value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(long parmNum, character dataType, String ioMode, Object value)
{
return setParameter(new integer(parmNum), dataType, new character(ioMode), value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(int64 parmNum, String dataType, String ioMode, Object value)
{
return setParameter(parmNum, new character(dataType), new character(ioMode), value);
}
/**
* Set the specified parameter for this dynamic invocation.
*
* @param parmNum
* The 1-based parameter index.
* @param dataType
* The parameter's data type.
* @param ioMode
* The parameter's mode (INPUT, OUTPUT, INPUT-OUTPUT, with -APPEND or -BY-REFERENCE
* suffix).
* @param value
* The argument's value. Can be only a variable/field-ref or extent, if mode is
* OUTPUT or INPUT-OUTPUT.
*
* @return <code>true</code> if the parameter was set successfully.
*/
@Override
public logical setParameter(long parmNum, String dataType, String ioMode, Object value)
{
return setParameter(new integer(parmNum),
new character(dataType),
new character(ioMode),
value);
}
/**
* Check if this resource supports the NAME attribute.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasName()
{
return false;
}
/**
* Check if this resource supports the NAME attribute in read-only mode.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasNameReadOnly()
{
return false;
}
/**
* Check if this resource can be reported as chained.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean isChained()
{
return false;
}
/**
* Check if this resource supports the NEXT-SIBLING attribute.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasNextSibling()
{
return false;
}
/**
* Check if this resource supports the PREV-SIBLING attribute.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasPrevSibling()
{
return false;
}
/**
* Check if this resource has as parent the specified resource.
*
* @param parent
* The parent for which the first child is needed. If <code>null</code>, the first
* resource with no parent set is returned.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean hasParent(HandleChain parent)
{
return false;
}
/**
* Worker to be implemented by each resource. Called by {@link #delete()}.
*
* @return <code>true</code> if the resource was deleted.
*/
@Override
protected boolean resourceDelete()
{
deleted = true;
// release all references
clear();
ControlFlowOps.invalidateCallSiteCache(this);
return true;
}
/**
* Check if the resource may be implicitly deleted.
*
* @return Always <code>false</code>.
*/
@Override
protected boolean implicitDeletion()
{
return false;
}
/**
* Check if the specified data-type is valid to be used via {@link #setParameter SET-PARAMETER},
* for a native library API call.
*
* @param dataType
* The data type to be validated.
*
* @return <code>true</code> if this is a valid data type.
*/
public static boolean validDllType(String dataType)
{
dataType = ProgressAst._keyword(dataType);
return dataType.equals("byte") ||
dataType.equals("character") ||
dataType.equals("double") ||
dataType.equals("float") ||
dataType.equals("int64") ||
dataType.equals("long") ||
dataType.equals("memptr") ||
dataType.equals("short") ||
dataType.equals("unsigned-long") ||
dataType.equals("unsigned-short");
}
/**
* Verify if the given parameter value can be used in OUTPUT or INPUT-OUTPUT mode. In these
* modes, only variables, temp-table fields or subscripted vars/fields are allowed.
*
* @param pnum
* The parameter number.
* @param value
* The parameter's value.
*
* @return <code>true</code> if the value can be used.
*/
public static boolean validOutputParameter(int pnum, Object value)
{
boolean isVar = (value instanceof BaseDataTypeVariable);
if (isVar)
{
value = ((BaseDataTypeVariable) value).get();
}
if (value instanceof BaseDataType)
{
BaseDataType bdt = (BaseDataType) value;
if (isVar)
{
return true;
}
}
else if (value instanceof PropertyReference)
{
PropertyReference pref = (PropertyReference) value;
// TODO: other validation?
return true;
}
else if (value instanceof FieldReference)
{
FieldReference fref = (FieldReference) value;
if (!(fref.getParentBuffer() instanceof TemporaryBuffer))
{
String msg = "Buffer for output parameter " + pnum + " not available during SET-PARAMETER";
ErrorManager.recordOrThrowError(10062, msg, false, false);
return false;
}
Buffer buf = (Buffer) fref.getParentBuffer().getDMOProxy();
if (!buf._available())
{
// 4GL abends... we just continue
String msg = "Buffer for output parameter " + pnum + " not available during SET-PARAMETER";
ErrorManager.recordOrThrowError(10062, msg, false, false);
return false;
}
// docs state that undoable temp-tables are not allowed, but looks like they are
return true;
}
else if (value.getClass().isArray())
{
return true;
}
else if (value instanceof AbstractExtentParameter)
{
return true;
}
else if (value instanceof TableParameter || value instanceof DataSetParameter)
{
return true;
}
ErrorManager.recordOrThrowError(10060,
"SET-PARAMETER output parameter " + pnum +
" must be a program variable or temp-table field " +
"(subscript expressions are ok)", false);
return false;
}
/**
* Create a new parameter with the specified details, if at all possible.
* <p>
* If validation fails (invalid data type, mode or value combinations), then a <code>null</code>
* value will be returned.
*
* When calling external dynamic/shared library routine parameter data type is validated for
* supported external data types.
*
* SetParameter method in ParametersList does an extra validation on input value against data type.
*
* @param dataType
* The data type to be used.
* @param mode
* The parameter's mode.
* @param value
* The parameter's value.
* @param library
* Flag set to true if a dynamic/shared library routine is invoked.
* @param validateType
* Optional data type validation
* @return A new {@link CallParameter instance} with the parameter configuration.
*/
public static CallParameter createParameter(
String dataType, CallMode mode, Object value, boolean library, boolean validateType)
{
boolean isVar = (value instanceof BaseDataTypeVariable);
if (isVar)
{
value = ((BaseDataTypeVariable) value).get();
}
boolean isClass = false;
boolean isExtent = false;
Matcher dataTypeMatch = dataTypePattern.matcher(dataType);
if (dataTypeMatch.matches())
{
isClass = dataTypeMatch.group(1) != null;
dataType = dataTypeMatch.group(2);
isExtent = dataTypeMatch.group(3) != null;
}
if (!"DATASET".equalsIgnoreCase(dataType) && !"DATASET-HANDLE".equalsIgnoreCase(dataType) &&
!"TABLE".equalsIgnoreCase(dataType) && !"TABLE-HANDLE".equalsIgnoreCase(dataType))
{
dataType = resolveDataType(dataType);
}
if (library)
{
if (!validDllType(dataType))
{
// if LIBRARY is set, then only lib-specific datatypes can be set...
String msg = "SET-PARAMETER datatype " + dataType +
" is not supported for WINDOWS DLL or UNIX library CALL object";
ErrorManager.recordOrThrowError(15481, msg, false);
return null;
}
}
else
{
// dataset and table parameter
if (value instanceof DataSetParameter || value instanceof TableParameter ||
"DATASET".equalsIgnoreCase(dataType) || "TABLE".equalsIgnoreCase(dataType)||
"DATASET-HANDLE".equalsIgnoreCase(dataType) || "TABLE-HANDLE".equalsIgnoreCase(dataType))
{
Class<?> valueCls = value == null ? null : value.getClass();
Object argVal = value;
if (valueCls == FieldReference.class)
{
valueCls = ((FieldReference) value).getType();
argVal = ((FieldReference) value).get();
}
else if (valueCls == PropertyReference.class)
{
valueCls = ((PropertyReference) value).getType();
argVal = ((PropertyReference) value).get();
}
if (isExtent ||
(!"DATASET".equalsIgnoreCase(dataType) && value instanceof DataSetParameter) ||
(!"TABLE".equalsIgnoreCase(dataType) && value instanceof TableParameter) ||
("DATASET".equalsIgnoreCase(dataType) && !(value instanceof DataSetParameter)) ||
("TABLE".equalsIgnoreCase(dataType) && !(value instanceof TableParameter)) ||
("DATASET-HANDLE".equalsIgnoreCase(dataType) && !handle.class.isAssignableFrom(valueCls)) ||
("TABLE-HANDLE".equalsIgnoreCase(dataType) && !handle.class.isAssignableFrom(valueCls)))
{
String msg = "If either the 2nd or 4th parameters to SetParameter are TABLE or DATASET types," +
" then they both must be.";
ErrorManager.recordOrThrowError(15563, msg, false);
return null;
}
return new CallParameter(dataType, isClass, mode, argVal);
}
Class<? extends BaseDataType> cls = !isClass && dataType != null
&& DATA_TYPES.contains(dataType.toUpperCase())
? BaseDataType.fromTypeName(dataType)
: ObjectOps.resolveClass(dataType);
isClass = cls != null && _BaseObject_.class.isAssignableFrom(cls);
Runnable invalidType = () -> {
ErrorManager.recordOrThrowError(10059,
"Unable to convert SET-PARAMETER value to datatype passed", false);
};
Runnable incompatibleType = () -> {
int[] nums = { 5729, 10059 };
String[] texts = { "Incompatible datatypes found during runtime conversion",
"Unable to convert SET-PARAMETER value to datatype passed" };
ErrorManager.recordOrThrowError(nums, texts, false, false);
};
if (cls == null)
{
invalidType.run();
}
if (value != null)
{
Class valueCls = value.getClass();
BaseDataType val = value instanceof BaseDataType ? (BaseDataType) value : null;
if (valueCls == unknown.class)
{
valueCls = cls;
}
boolean isValExtent = valueCls.isArray() || value instanceof AbstractExtentParameter;
//TODO: Warning: the following condition is the subject of a conflict in the behavior
// of 2 similar but different methods. What works for one fails for the other.
// The methods are SetParameter for objects and SetParameter for handles.
// Validate parameter and throw the coresponding error if the declared datatype is not
// an extent but the value is
if (!isExtent && isValExtent)
{
String errMsg = ("SetParameter requires that the EXTENT keyword must " +
"FOLLOW the datatype when the value is an array, " +
"but not when the value is a scalar");
ErrorManager.recordOrThrowError(15308, errMsg, false);
return null;
}
if (value instanceof FieldReference)
{
FieldReference fieldRef = (FieldReference) value;
isValExtent = fieldRef.getExtent() != null && fieldRef.getIndex() == -1;
valueCls = fieldRef.getType();
if (isValExtent && !fieldRef.hasIndex())
{
// actually the error occurs when the called procedure returns, but this validation
// fails with NPE because the (output) value is evaluated now.
// because of that some information are not available here
Nameable caller = (Nameable) ProcedureManager.thisProcedure().getResource();
String msg = "Array parameter with extent from procedure " + caller.name().toJavaType() +
" is mismatched with parameter in procedure " +/*"callName" +*/" with extent N";
ErrorManager.recordOrThrowError(11428, msg, false);
return null;
}
val = fieldRef.get();
}
else if (value instanceof PropertyReference)
{
PropertyReference propRef = (PropertyReference) value;
isValExtent = propRef.getExtent() != null && propRef.getIndex() == null;
valueCls = propRef.getType();
}
else if (valueCls.isArray())
{
valueCls = valueCls.getComponentType();
}
else if (value instanceof object)
{
object ref = (object) value;
if (ref._isValid())
{
valueCls = ((object) value).ref.getClass();
}
else
{
valueCls = _BaseObject_.class;
}
}
else if (value instanceof BaseDataType && value.getClass() != unknown.class)
{
valueCls = ((BaseDataType) value).getType().getCls();
}
if ((valueCls.isAssignableFrom(memptr.class)) && !"MEMPTR".equalsIgnoreCase(dataType)
|| ("MEMPTR".equalsIgnoreCase(dataType) && !(valueCls.isAssignableFrom(memptr.class))))
{
ErrorManager.recordOrThrowError(10058,
"MEMPTR parameters may not be converted to any other " +
"type in dynamic invoke or SetParameter",
false);
return null;
}
else if (("TABLE-HANDLE".equalsIgnoreCase(dataType) || "DATASET-HANDLE".equalsIgnoreCase(dataType)) &&
!(valueCls.isAssignableFrom(handle.class)))
{
invalidType.run();
}
if (!isValExtent && (!_BaseObject_.class.isAssignableFrom(cls) || !object.class.isAssignableFrom(valueCls))) {
// run-time data type conversion seems to be attempted first even if data types does not match unless is extent
// oddly when class parameter and value is object the assignment is not done, only compatibility check
try
{
BaseDataType inst = _BaseObject_.class.isAssignableFrom(cls)
? new object(cls)
: BaseDataType.generateUnknown(cls);
if (!inst.isIncompatibleTypesOnConversion(val))
inst.assign(val);
}
catch (ErrorConditionException ece)
{
ErrorManager.addError(10059, "Unable to convert SET-PARAMETER value to datatype passed", true, false);
throw ece;
}
catch (Exception ex)
{
LOG.log(Level.SEVERE, "Exception encountered when instantiating a value", ex);
}
}
if (validateType)
{
boolean isCompatible = false;
// numeric data types are compatible to some extent
// decimal accepts int/int64
// int64 accepts int
// recid is not accepted by neither decimal/int/int64
if (cls.equals(decimal.class))
{
isCompatible = !valueCls.equals(recid.class) && NumberType.class.isAssignableFrom(valueCls);
}
else if (cls.equals(int64.class))
{
isCompatible = !valueCls.equals(recid.class) && int64.class.isAssignableFrom(valueCls);
}
else if (NumberType.class.isAssignableFrom(cls))
{
// break int64->integer->recid hierarchy
isCompatible = valueCls.equals(cls);
}
else if (cls.equals(longchar.class))
{
// longchar accepts also character but not clob
isCompatible = !valueCls.equals(clob.class) && Text.class.isAssignableFrom(valueCls);
}
else if (_BaseObject_.class.isAssignableFrom(cls))
{
// value not an object
if (!(value instanceof object || value instanceof unknown))
{
isCompatible = false;
}
// value is object array
else if (isValExtent)
{
isCompatible = isAssignableFrom((object[]) value, cls);
}
else
{
if (value instanceof unknown)
{
object objVal = new object();
objVal.assign(value);
isCompatible = true;
}
else
{
object oVal = (object) value;
isCompatible = cls.isAssignableFrom(oVal.isUnknown() ? oVal.type() : oVal.ref().getClass());
}
}
}
else
{
// date->datetime->datetimetz hierarchy works
isCompatible = valueCls.isAssignableFrom(cls);
}
if (!isCompatible)
{
ErrorManager.recordOrThrowError(15315,
"SetParameter found incompatible data types used in dynamic parameter",
false);
return null;
}
if (isExtent && !isValExtent)
{
ErrorManager.recordOrThrowError(15308,
"SetParameter requires that the EXTENT keyword must FOLLOW the datatype when " +
"the value is an array, but not when the value is a scalar",
false);
return null;
}
}
}
}
if (isExtent)
{
return new ExtentCallParameter(dataType, isClass, mode, value);
}
CallParameter cparam = library ? new NativeCallParameter(dataType, mode, value)
: new CallParameter(dataType, isClass, mode, value);
return cparam;
}
/**
* Validates and assign the value to variable instance.
* <p>
* If validation fails (invalid data type or value combinations), an error is
* thrown.
*
* @param variable
* Instance of the field.
* @param value
* The parameter's value.
* @param dataType
* Data type of the variable to be set.
* @param isExtent
* Flag indicating that a variable instance is of type extent.
* @param validateType
* Optional data type validation.
*/
public static void setterAssignment(Object variable,
Object value,
String dataType,
boolean isExtent)
{
boolean isVar = (value instanceof BaseDataTypeVariable);
if (isVar)
{
value = ((BaseDataTypeVariable) value).get();
}
if (!"DATASET".equalsIgnoreCase(dataType) && !"DATASET-HANDLE".equalsIgnoreCase(dataType) &&
!"TABLE".equalsIgnoreCase(dataType) && !"TABLE-HANDLE".equalsIgnoreCase(dataType))
{
dataType = resolveDataType(dataType);
}
Class<? extends BaseDataType> cls = (Class<? extends BaseDataType>) variable.getClass();
Runnable invalidType = () -> {
ErrorManager.recordOrThrowError(5678,
"Unable to do run-time conversion of datatypes", false);
};
if (cls == null)
{
invalidType.run();
return;
}
if (value != null)
{
Class valueCls = value.getClass();
BaseDataType val = value instanceof BaseDataType ? (BaseDataType) value : null;
if (valueCls == unknown.class)
{
valueCls = cls;
}
boolean isValExtent = valueCls.isArray() || value instanceof AbstractExtentParameter;
if (value instanceof FieldReference)
{
FieldReference fieldRef = (FieldReference) value;
isValExtent = fieldRef.getExtent() != null && fieldRef.getIndex() == -1;
valueCls = fieldRef.getType();
if (isValExtent && !fieldRef.hasIndex())
{
// actually the error occurs when the called procedure returns, but this validation
// fails with NPE because the (output) value is evaluated now.
// because of that some information are not available here
Nameable caller = (Nameable) ProcedureManager.thisProcedure().getResource();
String msg = "Array parameter with extent from procedure " + caller.name().toJavaType() +
" is mismatched with parameter in procedure " +/*"callName" +*/" with extent N";
ErrorManager.recordOrThrowError(11428, msg, false);
return;
}
val = fieldRef.get();
}
else if (value instanceof PropertyReference)
{
PropertyReference propRef = (PropertyReference) value;
isValExtent = propRef.getExtent() != null && propRef.getIndex() == null;
valueCls = propRef.getType();
}
else if (valueCls.isArray())
{
valueCls = valueCls.getComponentType();
}
else if (value instanceof object)
{
object ref = (object) value;
if (ref._isValid())
{
valueCls = ((object) value).ref.getClass();
}
else
{
valueCls = _BaseObject_.class;
}
}
else if (value instanceof BaseDataType && value.getClass() != unknown.class)
{
valueCls = ((BaseDataType) value).getType().getCls();
}
if ((valueCls.isAssignableFrom(memptr.class)) && !"MEMPTR".equalsIgnoreCase(dataType)
|| ("MEMPTR".equalsIgnoreCase(dataType) && !(valueCls.isAssignableFrom(memptr.class))))
{
ErrorManager.recordOrThrowError(10058,
"MEMPTR parameters may not be converted to any other " +
"type in dynamic invoke or SetParameter",
false);
return;
}
else if (("TABLE-HANDLE".equalsIgnoreCase(dataType) || "DATASET-HANDLE".equalsIgnoreCase(dataType)) &&
!(valueCls.isAssignableFrom(handle.class)))
{
invalidType.run();
return;
}
if (!isValExtent && (!_BaseObject_.class.isAssignableFrom(cls) || !object.class.isAssignableFrom(valueCls)))
{
// run-time data type conversion seems to be attempted first even if data types does not match unless is extent
// oddly when class parameter and value is object the assignment is not done, only compatibility check
try
{
BaseDataType inst = (BaseDataType) variable;
if (!inst.isIncompatibleTypesOnConversion(val))
{
inst.assign(val);
}
}
catch (ErrorConditionException ece)
{
invalidType.run();
return;
}
catch (Exception ex)
{
LOG.log(Level.SEVERE, "Exception encountered when instantiating a value", ex);
}
}
}
}
/**
* Check if all values in the array/extent are compatible with the given data type.
*
* @param values The values array (extent).
* @param cls The data type to validate values against.
* @return True is all values in the array are compatible with the data type.
*/
private static boolean isAssignableFrom(object<? extends _BaseObject_>[] values, Class cls)
{
boolean isEmpty = true;
boolean isAssignable = true;
for (object<? extends _BaseObject_> value : values)
{
if (!value.isUnknown()) {
isEmpty = false;
// check the actual instance class, object type might be an invalid superclass like P.L.O
isAssignable = cls.isAssignableFrom(value.ref.getClass());
if (!isAssignable)
break;
}
}
if (!isEmpty)
return isAssignable;
Class valueCls = unknown.class;
if (values.length > 0)
{
valueCls = values[0].type();
}
else if (ArrayAssigner.isDynamicArray(values))
{
valueCls = ArrayAssigner.getObjectType(values);
}
return cls.isAssignableFrom(valueCls);
}
/**
* Worker method for the {@link #GET_ATTR_CALL_TYPE} and {@link #SET_ATTR_CALL_TYPE} modes.
* <p>
* It will try to access the attribute or method, using the current configuration.
*
* @param setter
* When <code>true</code>, we are in {@link #SET_ATTR_CALL_TYPE} mode, and only
* writable attributes are accessed; otherwise, all readable attributes and methods.
*/
private void accessAttributeOrMethod(boolean setter)
{
// first, check if this is a valid attribute/method token
int tokenType = ProgressAst.lookupKeywordToken(callName);
if (!ATTRIBUTES_AND_METHODS.containsKey(tokenType))
{
ErrorManager.recordOrShowError(10092,
"CALL:INVOKE finds an invalid CALL:CALL-NAME for " +
"GET/SET-ATTR-CALL-TYPE. It must be a valid 4gl widget " +
"attribute or method",
false, false, false);
return;
}
if (setter && numParameters != 1)
{
ErrorManager.recordOrShowError(10094,
"SET-ATTR-CALL-TYPE requires exactly 1 SET-PARAMETER " +
"which describes the value to be set",
false, false, false);
return;
}
Runnable invalidAttr = () ->
{
ErrorManager.recordOrShowError(4052,
"**" + callName + " is not a " +
(setter ? "settable" : "queryable") +
" attribute for " + inHandle.unwrap().name().getValue() +
" widget",
false, false, false);
};
// finally, check if this is a valid attribute or method name, in this resource
if (!((!setter && HandleOps.canQuery(inHandle, callName).booleanValue()) ||
(setter && HandleOps.canSet(inHandle, callName).booleanValue())))
{
invalidAttr.run();
return;
}
if (tokenType == ProgressParser.KW_INVOKE)
{
ErrorManager.recordOrShowError(10050,
"INVOKE method must occur as a stand-alone statement: " +
"hCall:INVOKE. It cannot occur inside an expression " +
"or on either side of an equal sign",
false, false, false);
return;
}
if (!setter && tokenType == ProgressParser.KW_HANDLE)
{
// in this case, we can only read, and just return the resource
returnValueDataType = "handle";
returnValue = new handle(inHandle);
return;
}
// for getter, the parameters are optional: find either with no parameters or with exact match
String methodName = HandleOps.findLegacy(inHandle, callName, true, setter);
if (methodName == null && !setter)
{
// if is not a setter, look for methods, too
methodName = HandleOps.findLegacy(inHandle, callName, false, false);
}
if (methodName == null)
{
invalidAttr.run();
return;
}
// reset this now (we may be accessing RETURN-VALUE from this same handle, don't leak prev
// call's value).
returnValue = null;
if (returnValueDataType == null)
{
int ttype = ATTRIBUTES_AND_METHODS.get(tokenType);
returnValueDataType = ExpressionConversionWorker.simpleExpressionType(ttype);
}
WrappedResource target = inHandle.getResource();
Class<? extends WrappedResource> tclass = target.getClass();
Method mthd = null;
if (!setter)
{
try
{
// this is a no-param method call or an attribute getter - check if we can resolve
// a no-argument method
mthd = tclass.getMethod(methodName);
}
catch (Exception e)
{
// do nothing, fallback
}
}
BaseDataType val = null;
if (mthd != null)
{
try
{
// if a no-argument method was found, then invoke it
val = (BaseDataType) Utils.invoke(mthd, target);
}
catch (Exception e)
{
LOG.log(Level.SEVERE,
"Problem calling " + callName + " from " + inHandle.unwrap().name(),
e);
val = null;
}
}
else
{
// now, look for a method call, attribute getter with an index arg or attribute setter
List<Class<? extends BaseDataType>> argTypes = new ArrayList<>();
// using METH_INT forces to check for a method's signature
ParmType[] lptypes = SignatureHelper.getSignature(tokenType, ProgressParser.METH_INT);
if (lptypes != null)
{
// this is a method call or an attribute getter which receives an extent arg
if (parameters != null && lptypes.length <= parameters.length)
{
// arguments are validated this way:
// - if an OUTPUT parameter is found, then abort
// - if the expected method signature is found, then continue with the call
boolean allParamsInput = true;
for (int i = 0; i < lptypes.length; i++)
{
if (parameters[i].mode.output)
{
// when an OUTPUT parameter is found, abort the call
allParamsInput = false;
break;
}
Class<? extends BaseDataType> ptype =
(Class<? extends BaseDataType>) lptypes[i].getParamType();
argTypes.add(ptype);
}
if (!allParamsInput)
{
// nothing to do
return;
}
}
else
{
// we can't match with a method signature (not enough arguments)
// abort the call, no invoke will be performed and no error will be shown
return;
}
}
else
{
// now, we are in an attribute setter - the argument type is resolved from the
// attribute's data type
if (parameters != null && parameters.length == 1 && !parameters[0].mode.output)
{
argTypes.add(BaseDataType.fromTypeName(returnValueDataType));
}
else
{
// incorrect number of arguments or data type, abort
return;
}
}
// look for a method with the specified data types
Class<? extends BaseDataType>[] ptypes = argTypes.toArray(new Class[0]);
try
{
mthd = tclass.getMethod(methodName, ptypes);
}
catch (Exception e)
{
mthd = null;
}
if (mthd == null)
{
// if still not found, search for a method with the same name and number of arguments,
// but having compatible data types
for (Method m1 : tclass.getMethods())
{
if (!m1.getName().equals(methodName))
{
continue;
}
Class<?>[] m1types = m1.getParameterTypes();
if (m1types.length != ptypes.length)
{
continue;
}
boolean ok = true;
for (int i = 0; i < ptypes.length; i++)
{
if (!m1types[i].isAssignableFrom(ptypes[i]))
{
ok = false;
break;
}
}
if (ok)
{
mthd = m1;
break;
}
}
}
if (mthd != null && ptypes.length == numParameters)
{
// found a good method, convert the arguments to the specified data types and call it
Object[] args = new Object[ptypes.length];
for (int i = 0; i < ptypes.length; i++)
{
args[i] = parameters[i].getArgument();
if (args[i] == null)
{
LOG.log(Level.WARNING,
"Argumenmt " + (i + 1) + " should have been prepared before INVOKE");
return;
}
}
try
{
val = (BaseDataType) Utils.invoke(mthd, target, args);
}
catch (Exception e)
{
LOG.log(Level.SEVERE,
"Problem calling " + callName + " from " + inHandle.unwrap().name(),
e);
val = null;
}
}
else
{
invalidAttr.run();
return;
}
}
if (!BaseDataType.class.isAssignableFrom(mthd.getReturnType()))
{
// there is an indeterminate behavior here: in some cases of uninitialized attributes
// (like PRIVATE-DATA and PROCEDURE-TYPE), the data-type for the returned (unknown)
// value is not predictable. we don't duplicate this, just take the attr/method
// return type and use that
returnValueDataType = null;
}
else if (returnValueDataType == null && val != null)
{
returnValueDataType = ((BaseDataType) val).getTypeName().toLowerCase();
}
if (returnValueDataType != null)
{
// this may be set explicitly by the user (or prior call usage)
// above we compute the implicit data-type
BaseDataType res = BaseDataType.generateUnknown(BaseDataType.fromTypeName(returnValueDataType));
if (val != null)
{
res.assign(val);
}
returnValue = res;
}
else
{
returnValue = new unknown();
}
}
/**
* Show a warning that the given attribute is unknown.
*
* @param attr
* The attribute name.
*/
private void unknownWarning(String attr)
{
String msg = "**Unable to assign UNKNOWN value to attribute " + attr + " on " + type() +
"widget. (4083)";
ErrorManager.displayWarning(4083, msg, false);
}
/**
* Raise an ERROR condition when both the ORDINAL and CALL-NAME attributes are being set.
*
* @param attr
* The attribute being set.
*/
private void callOrdinalError(String attr)
{
int[] nums = { 15566, 3131 };
String[] texts =
{
"Cannot set CALL-NAME and ORDINAL attributes at the same time",
"Unable to set attribute " + attr + " in widget of type CALL"
};
ErrorManager.recordOrThrowError(nums, texts, false);
}
/**
* Raise an error that the attribute can't be set.
*
* @param err1
* The first error code.
* @param msg1
* The first error message.
* @param attr
* The attribute's name.
*/
private void unableToSetAttribute(int err1, String msg1, String attr)
{
String[] errMsg =
{
msg1,
"Unable to set attribute " + attr + " in widget of type CALL"
};
int[] errorCodes = { err1, 3131 };
ErrorManager.recordOrThrowError(errorCodes, errMsg, false, false);
}
/**
* Build an {@link InvokeConfig configuration} to emulate a RUN or dynamic function call, using
* this CALL's configuration.
* <p>
* This can be later used to perform the actual {@link ControlFlowOps#invoke(InvokeConfig) invoke}.
*
* @param function
* Flag indicating if this is a function call.
*
* @return The new configuration.
*/
private InvokeConfig buildInvokeConfig(boolean function)
{
InvokeConfig cfg = CALL_SITE.clone().setTarget(callName);
cfg.setFunction(function)
.setInHandle(inHandle)
.setPersistent(persistent && inHandle == null)
.setSingleRun(singleRun && inHandle == null)
.setSingleton(singleton && inHandle == null);
// some adjustments related to CALL handle
if (cfg.isPersistent() || cfg.isSingleRun() || cfg.isSingleton())
{
// the persistent procedure will be saved in 'IN-HANDLE' argument
inHandle = new handle();
cfg.setProcedureHandle(inHandle);
}
if (server != null)
{
cfg.setServerHandle(server);
}
cfg.setAsynchronous(async)
.setAsyncHandle(asyncHandle);
if (eventProcedure != null)
{
cfg.setEventProcedure(eventProcedure);
}
if (eventProcedureContext != null)
{
cfg.setEventProcedureContext(eventProcedureContext);
}
if (numParameters > 0)
{
String modes = "";
Object[] args = new Object[numParameters];
for (int i = 0; i < numParameters; i++)
{
CallParameter cparam = parameters[i];
modes = modes + cparam.mode.asString();
args[i] = cparam.getArgument();
}
cfg.setModes(modes)
.setArguments(args);
}
return cfg;
}
private static String resolveDataType(String dataType)
{
String primitiveType = ProgressAst._keyword(dataType);
if (primitiveType != null && (DATA_TYPES.contains(primitiveType.toUpperCase())
|| validDllType(primitiveType)))
{
return "log".equalsIgnoreCase(primitiveType) ? "logical" : primitiveType;
}
// if not a primitive data type check a valid OO class
Class<_BaseObject_> cls = ObjectOps.resolveClass(dataType);
return cls != null ? dataType : null;
}
/**
* Given a decimal value, convert it to a byte representation; this representation is assumed
* as an 'in memory representation' of the value, usable for converting to another data type,
* where:
* <ul>
* <li>rightmost bit is "0x8#", where "#" is the number of fractional digits in hex</li>
* <li>concatenate the fractional and non-fractional digits</li>
* <li>if there is an odd number of digits, pad with 'F'</li>
* </ul>
*
* @param val
* The decimal value.
*
* @return The byte representation.
*/
static byte[] decimalBytes(decimal val)
{
String sval = ((decimal) val).toStringMessage();
int dot = sval.indexOf('.');
String fract = dot < 0 ? "" : sval.substring(dot + 1);
String dec = dot < 0 ? sval : sval.substring(0, dot);
String all = dec + fract;
int blen = 1 + (all.length() / 2) + (all.length() % 2);
byte[] bytes = new byte[blen];
bytes[0] = (byte) ((0x8 << 4) + fract.length());
int i = 1;
while (!all.isEmpty())
{
String chars = all.length() == 1 ? (all + "F") : all.substring(0, 2);
bytes[i++] = (byte) (Integer.parseInt(chars, 16) & 0xFF);
all = all.length() == 1 ? all.substring(1) : all.substring(2);
}
return bytes;
}
/**
* A wrapper for extent parameters (without a subscript).
*/
private static class ExtentCallParameter
extends CallParameter
{
/**
* Create a new wrapper for a parameter which will be used for a native OS API call.
*
* @param dataType
* The parameter's data type.
* @param isClass
* Flag indicating that the datatype is a legacy OO class.
* @param mode
* The call mode.
* @param value
* The parameter's value.
*/
public ExtentCallParameter(String dataType, boolean isClass, CallMode mode, Object value)
{
super(dataType, isClass, mode, value);
convertTo(null);
OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
if (opa != null)
{
opa.abort();
}
}
/**
* Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
* <p>
* Once converted, it will be cached in the {@link #argument} variable.
*
* @return The {@link #argument}.
*/
@Override
public Object convertTo(Class<?> cls)
{
if (argument != null)
{
return argument;
}
if (initialValue instanceof FieldReference)
{
FieldReference fr = (FieldReference) initialValue;
cls = fr.getType();
if (mode.output)
{
argument = new OutputExtentField((DataModelObject) fr.getDMO(), fr.getProperty());
}
else if (mode.inputOutput)
{
argument = new InputOutputExtentField((DataModelObject) fr.getDMO(), fr.getProperty());
}
else
{
argument = new FieldReference((DataModelObject) fr.getDMO(),
fr.getProperty(),
fr.isUpperCase(),
(NumberType) null,
true).getExtentValues();
}
return argument;
}
else if (initialValue instanceof AbstractExtentParameter)
{
// we already have an extent parameter
if (mode.input)
{
// in this case, just get the value
argument = ((AbstractExtentParameter) initialValue).getVariableSafe();
}
else if (mode.output && initialValue instanceof InputOutputExtentParameter)
{
argument = new OutputExtentParameter<BaseDataType>()
{
@Override
public BaseDataType[] getVariable()
{
return ((AbstractExtentParameter) initialValue).getVariableSafe();
}
@Override
public void setVariable(BaseDataType[] reference)
{
((AbstractExtentParameter) initialValue).setVariable(reference);
}
@Override
protected boolean isTableField()
{
return ((AbstractExtentParameter) initialValue).isTableField();
}
};
}
return argument != null ? argument : super.convertTo(cls);
}
// final case is a simple array reference
if (!mode.input)
{
// only INPUT is allowed - the other modes require conversion rules
throw new IllegalStateException("OUTPUT and INPUT-OUTPUT extent vars need to be " +
"passed as AbstractExtentParameter instances!");
}
argument = (BaseDataType[]) value;
return argument;
}
/**
* Extent arguments are automatically copied via {@link AbstractExtentParameter} wrappers,
* nothing to do.
*
* @return Always <code>true</code>.
*/
public boolean copyOutput()
{
// nothing to do, is done automatically by the AbstractExtentParameter wrappers
return true;
}
}
/**
* A wrapper for parameters which will be passed to a direct native OS API call.
*/
private static class NativeCallParameter
extends CallParameter
{
/**
* Create a new wrapper for a parameter which will be used for a native OS API call.
*
* @param dataType
* The parameter's data type.
* @param mode
* The call mode.
* @param value
* The parameter's value.
*/
public NativeCallParameter(String dataType, CallMode mode, Object value)
{
super(dataType, false, mode, value);
}
/**
* Convert this parameter's {@link CallParameter#value value} to be usable as an argument.
* <p>
* Once converted, it will be cached in the {@link #argument} variable.
*
* @return The {@link #argument}.
*/
public Object convertTo(Class<?> cls)
{
if (argument != null)
{
return argument;
}
if (initialValue instanceof BaseDataType || initialValue instanceof FieldReference)
{
// this handles both output and input-output modes
argument = initialValue;
return argument;
}
if (mode.input)
{
switch (dataType)
{
case "byte":
case "short":
case "long":
case "unsigned-long":
case "unsigned-short":
if (initialValue instanceof Number)
{
argument = new integer(((Number) initialValue).longValue());
}
else
{
argument = initialValue == null ? new integer() : new integer(initialValue.toString());
}
break;
case "int64":
if (initialValue instanceof Number)
{
argument = new int64(((Number) initialValue).longValue());
}
else
{
argument = initialValue == null ? new int64() : new int64(initialValue.toString());
}
break;
case "character":
argument = initialValue == null ? new character() : new character(initialValue.toString());
break;
case "double":
case "float":
if (initialValue instanceof Number)
{
argument = new decimal(((Number) initialValue).doubleValue());
}
else
{
argument = initialValue == null ? new decimal() : new decimal(initialValue.toString());
}
break;
}
}
return argument;
}
}
}