ObjectOps.java
/*
** Module : ObjectOps.java
** Abstract : Defines APIs to implement 4GL object-specific operations.
**
** Copyright (c) 2018-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 CA 20181213 Created initial version.
** OM 20181217 newInstance() returns the expected generic type.
** 002 OM 20190129 Remove reference after the destructors are invoked.
** CA 20190219 Runtime implementaiton.
** CA 20190326 Implemented ProcedureHelper and TransactionHelper instead of direct usage of
** the static API. This allows the elimination of context local usage in
** ProcedureManager and TransactionManager.
** 003 CA 20190418 Added no-arg DYNAMIC-NEW support.
** 004 CA 20190710 Fixed issues related to working with legacy OE classes for which FWD provides
** support. Do not delete a legacy instance if this is supposed to be returned
** by a function call (this delete check is postponed in the parent scope).
** CA 20190719 Plain object instance does not do reference counting.
** CA 20190729 When loading a legacy class, backup and restore the output parameter assigners.
** CA 20190995 Added cast(BDT) version.
** 005 AIL 20191120 Made the scope methods aware of DATABASE TRIGGER Block Type.
** CA 20191112 Fixed instantiation of Progress.Lang.Class (via GetClass) - this must be a
** unique instance for each invocation, and lifetime must match any other legacy
** 4GL instance.
** 006 CA 20200116 Javadoc fixes.
** 007 CA 20200304 A 4GL object instance must delay its delete if is still on stack.
** 008 CA 20200330 More fixes related to OE-compatible error management, in constructor case
** and 'implicit' 4GL ERROR conditions.
** 009 CA 20200427 Replaced isValid().booleanValue() calls with _isValid().
** CA 20200429 Added stubs for DYNAMIC-ENUM.
** CA 20200503 Added stubs for GET-CLASS().
** GES 20200506 Implemented DYNAMIC-ENUM().
** GES 20200511 Bypassed life cycle management for legacy enums.
** ME 20200521 Move getLegacyClass from LegacyClass, add class hierarchy registration.
** GES 20200528 Fixed enum error handling.
** GES 20200529 Added safety code to avoid life cycle processing for enums.
** GES 20200603 Reworked legacy name/class mapping to use static data in a thread-safe manner.
** 010 ME 20200810 Added registerClassWorker() to getClassInstance().
** CA 20201003 Replaced Guava identity HashSet with Collections.newSetFromMap(IdentityHashMap).
** ME 20201009 Let 'invoke' return plain Object as it can be anything (including array).
** CA 20201014 Reverted ME/20201009 - 'invoke' will return BDT, and the fix will either emit a
** 'invokeExtent' for calls where it is known for the lvalue to be an extent, or we add
** BDT(Object) constructors for all sub-classes.
** ME 20201127 Throw error instead of showing it if cast fails.
** ME 20210128 Added cast/dynamic cast for object arrays.
** CA 20210215 An INPUT parameter at the property SETTER must be registered as pending, so its reference
** can be tracked.
** CA 20210310 Performance improvement in hasDestructor - cache the result of this method.
** CA 20210322 asResource(LegacyEnum) will always return an ObjectResource instance (and a resource ID
** computed), so temp-table fields can refer enums.
** ME 20210330 Added convenient increment/decrement methods for object references.
** CA 20210804 Fixed a problem with caching available destructors - 'clsWithDtor' must have as key the
** object's defining class paired with the destructor name (which may be from a super-class).
** HC 20211011 Implemented i18n support.
** CA 20211214 The legacy class instances are always singleton for the current context.
** CA 20220106 THIS-OBJECT from a static method will return the legacy class object - this is required as
** in some cases (like 'clsEvt:subscribe(static-method)') FWD emits THIS-OBJECT as first
** argument and the parser does not resolve the static method name.
** CA 20220120 Fixed some cases of pending assignment. Some performance improvements.
** CA 20220203 A small performance improvement for scopeFinished.
** CA 20220208 Objects deserialized from a remote appserver call can be automatically registered as known
** instances. Fixed a 'pendingAssign' bug when the root block exits.
** CA 20220901 Refactored scope notification support: ScopeableFactory was removed, and the registration
** is now specific to each type of scopeable. For each case, the block will be registered
** for scope support (for that particular scopeable) only when the scopeable is 'active'
** (i.e. unnamed streams or accumulators are used). This allows a lazy registration of
** scopeables, to avoid the unnecessary overhead of processing all the scopeables for each
** and every block.
** CA 20221006 Improved context-local lookup. Refs #6824
** TJD 20220504 Java 11 compatibility minor changes
** CA 20221123 If an 'object' instance is assigned in the FINALLY block and also referenced by the RETURN
** statement, then do not remove it from 'pendingAssign'.
** EVL 20221209 Adding BaseDataType as valid object type for dynamicCast() method call.
** CA 20220426 Added stubs for DYNAMIC-PROPERTY statement and function (setter and getter).
** CA 20220520 The constructor (static or instance) and destructor lookup now relies on the
** LegacySignature annotation to resolve them, instead of the Java method name - this is
** required for ctor overload support at conversion and runtime.
** An unknown value returned by 'new' or 'cast' must be typed with the legacy class name.
** CA 20220524 Fixed destructor execution.
** CA 20220526 Added DYNAMIC-PROPERTY getter and setter implementation, not all argument validation is
** implemented.
** CA 20220531 Added a version of newDynamicInstance which emits for NEW with POLY arguments.
** CA 20220601 Any TableParameter or DataSetParameter instances at a legacy OO method or constructor call
** must be transformed to their mode'ed version, depending on the INPUT, OUTPUT or
** INPUT-OUTPUT mode, and the DATASET-HANDLE, DATASET, TABLE-HANDLE or TABLE versions.
** CA 20220602 Added basic support for transport of object instances over appserver call.
** CA 20220609 Fixed a regression in destructor processing.
** CA 20220617 Dynamic invocation uses the runtime reference type, and not the definition type, in any
** error message.
** CA 20230110 Added a ObjectHelper to provide direct access to context-local state.
** CA 20230116 Improvement from context-local state resolution.
** 011 CA 20230321 When a legacy class is first loaded, force its static constructor to execute.
** 012 CA 20230321 Better fix for H011 - avoid logging an error message for 'executeStaticConstructor'.
** 013 CA 20230615 Small optimization - avoid using obj.ref() and use instead obj.ref, if the 'obj' instance
** is known to be valid.
** 014 CA 20230731 An instance can be assigned during its initialization (constructor calls) - do not save it
** as 'pendingAssign' in such cases.
** 015 CA 20230724 Resolve and cache the 'execute methods' at SourceNameMapper$LegacyClass.executeMethods.
** 016 CA 20230817 Removed ObjectOps.register, as this registration is handled by runtime.
** 017 CA 20230928 The openedge method resolution when the match is done via a poly argument (like h::f1),
** it will assume the return type of the last found overload - the runtime will still
** resolve the target based on the arguments, but the lookup must be done on the type
** resolved by the conversion, and not the runtime time of the lvalue on the chain call.
** For this reason, and to allow the chained call to fail properly at runtime, both the
** lookup type and the return type of the call are emitted at the 'invokePoly' API call.
** CA 20231007 Arguments for direct method calls which can't be emitted as direct java calls (as they
** are considered 'dynamic poly') must be wrapped in a 'polyArg' to check the type.
** 018 CA 20231026 An object instance being returned must be tracked on the caller block, as the returned
** value may or may not be assigned, and the callee may return a freshly created instance
** which will never gets discarded if is never assigned again.
** A persistent procedure with a pending delete is no longer persistent, so scopeDeleted
** needs to be executed.
** CA 20231031 Added 'BlockDefinition' parameter to 'scopeStart', required when a top-level scopeable is
** lazy registered while in an inner block.
** 019 CA 20231026 Static poly calls do not require the string representation of the class.
** 020 CA 20231208 POLY OO method invocations must use the var's declared type and not the runtime type.
** 021 CA 20231213 The synthetic 'execute' methods emitted for legacy classes can be dropped if there is
** nothing emitted in them. These Java methods are also marked with Type.EXECUTE
** LegacySignature annotation, and their BlockManager API removed - the FWD runtime will run
** this only once.
** 022 CA 20231221 ObjectOps scopeable support is registered only when: a var is defined, a new instance is
** being created, or a function/method returns 'object'.
** 023 CA 20231216 Fixed regression related to widget pools used in OO (the issue was with the implicit
** widget pool for a class with USE-WIDGET-POOL, for which the instance gets deleted
** implicitly when a block finishes).
** 024 CA 20240105 The 'inScopeFinished' flag must be reset/restored while executing destructors.
** 025 CA 20240324 Use logical constants for TRUE, FALSE, UNKNOWN, within internal FWD runtime.
** Added a flag to allow tracking the objects valid state without a map lookup. This flag is
** set to false when the instance gets deleted.
** 026 CA 20240410 Track the instantiating class, when creating a new instance, so the scopeables know that
** they need to be registered as pending or not.
** 027 CA 20240520 Fixed validation of the DYNAMIC-PROPERTY arguments for statement and function.
** 028 ES 20240711 Call cleanupPending in case of StopConditionExeption is thrown by a stop-after timer.
** 029 AL2 20240717 Clear the newly constructed object from the objects collection if the constructor failed.
** 030 DDF 20240305 Do not delete the resource when the initialization fails due to a constructor
** that could not be found.
** DDF 20240311 Refactored newInstance() and newInstanceInternal() to include an additional parameter
** used for avoiding executing the constructor method during initialization.
** 031 VVT 20240826 SourceNameMapper.convertJavaProg() renamed. See #8613-16.
** 032 CA 20250422 The reference in TYPE-OF can be POLY, added overloads.
** 033 PBB 20250509 Added support for DYNAMIC-INVOKE when the result is or might be an extent.
** 034 PBB 20250522 Added DYNAMIC-PROPERTY support mostly related to returning extents.
** 035 ES 20250527 Return new ObjectResource if the reference object is not tracked.
*/
/*
** 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 com.goldencode.p2j.oo.lang.*;
import com.goldencode.p2j.security.*;
import java.lang.reflect.*;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import org.reflections.Reflections;
/**
* Defines APIs to manage 4GL-style objects (instantiation, deletion, cast, etc).
*/
public class ObjectOps
{
/** DYNAMIC-ENUM() error message specification. */
private static final String DE_SPEC = "Could not evaluate '%s' parameter for DYNAMIC-ENUM";
/** Legacy names for each loaded class. */
private static final Map<Class<? extends _BaseObject_>, String> legacyNames = new IdentityHashMap<>();
/** Map of legacy names converted classes. */
private static final Map<String, Class<? extends _BaseObject_>> name2cls = new HashMap<>();
/** Controls access to the static class/name lookup data. */
private static final Object lock = new Object();
/** Context-local data. */
private static final ContextLocal<WorkArea> local = new ContextLocal<WorkArea>()
{
protected WorkArea initialValue()
{
return new WorkArea();
}
};
static
{
// register all classes from 'oo sdk' implementation
Reflections reflections = new Reflections("com.goldencode.p2j.oo");
Set<Class<? extends _BaseObject_>> legacyClasses = reflections.getSubTypesOf(_BaseObject_.class);
for (Class<? extends _BaseObject_> cls : legacyClasses) {
registerClass(cls, false);
}
}
/**
* Get an instance of {@link ObjectHelper}, which has the context-local instance saved, so API access can
* be done without a context-local query.
*
* @return See above.
*/
public static ObjectHelper getObjectHelper()
{
return new ObjectHelper(local.get());
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(object<? extends _BaseObject_> ref, character prop, Object val)
{
//fail fast
if (ref.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(ref, prop.toStringMessage(), val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(object<? extends _BaseObject_> ref, String prop, Object val)
{
//fail fast
if (ref.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (!validDynamicPropertyArgs(ref, prop, null, true))
{
return;
}
PropertyReference pref = new PropertyReference(ref, prop);
if (val == null)
{
val = new unknown();
}
if (val.getClass().isArray())
{
pref.setExtent((BaseDataType[]) val);
}
else if (val instanceof BaseDataType)
{
pref.set((BaseDataType) val);
}
else
{
//try to convert the value to a BDT
try
{
BaseDataType btdVal = convertFromJavaToBTD(val);
pref.set(btdVal);
}
catch (Exception e)
{
UnimplementedFeature.missing(
"DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
}
}
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(object<? extends _BaseObject_> ref,
character prop,
NumberType idx,
Object val)
{
//fail fast
if (ref.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(ref, prop.toStringMessage(), idx, val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(object<? extends _BaseObject_> ref,
String prop,
NumberType idx,
Object val)
{
//fail fast
if (ref.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (!validDynamicPropertyArgs(ref, prop, idx, true))
{
return;
}
PropertyReference pref = new PropertyReference(ref, prop, idx);
if (val == null)
{
val = new unknown();
}
if (val.getClass().isArray())
{
pref.setExtent((BaseDataType[]) val);
}
else if (val instanceof BaseDataType)
{
pref.set((BaseDataType) val);
}
else
{
//try to convert the value to a BDT
try
{
BaseDataType btdVal = convertFromJavaToBTD(val);
pref.set(btdVal);
}
catch (Exception e)
{
UnimplementedFeature.missing(
"DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
}
}
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(character className, character prop, Object val)
{
// fail fast
if (className.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className.toStringMessage(), prop.toStringMessage(), val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(character className, String prop, Object val)
{
// fail fast
if (className.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className.toStringMessage(), prop, val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(String className, character prop, Object val)
{
//fail fast
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className, prop.toStringMessage(), val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
* @param val
* The property value.
*/
public static void setDynamicProperty(String className, String prop, Object val)
{
if (!validDynamicPropertyArgs(className, prop, null, true))
{
return;
}
PropertyReference pref = new PropertyReference(className, prop);
if (val == null)
{
val = new unknown();
}
if (val.getClass().isArray())
{
pref.setExtent((BaseDataType[]) val);
}
else if (val instanceof BaseDataType)
{
pref.set((BaseDataType) val);
}
else
{
//try to convert the value to a BDT
try
{
BaseDataType btdVal = convertFromJavaToBTD(val);
pref.set(btdVal);
}
catch (Exception e)
{
UnimplementedFeature.missing(
"DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
}
}
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(character className, character prop, NumberType idx, Object val)
{
//fail fast
if (className.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className.toStringMessage(), prop.toStringMessage(), idx, val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(String className, character prop, NumberType idx, Object val)
{
//fail fast
if (prop.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16581}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className, prop.toStringMessage(), idx, val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(character className, String prop, NumberType idx, Object val)
{
//fail fast
if (className.isUnknown())
{
// This error doesn't end in dot in progress
String errMsg = "Invalid object reference passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] {16580}, new String[]{errMsg}, false, false, false);
return;
}
setDynamicProperty(className.toStringMessage(), prop, idx, val);
}
/**
* Implementation of the DYNAMIC-PROPERTY statement (setter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
* @param val
* The property value.
*/
public static void setDynamicProperty(String className, String prop, NumberType idx, Object val)
{
if (!validDynamicPropertyArgs(className, prop, idx, true))
{
return;
}
PropertyReference pref = new PropertyReference(className, prop, idx);
if (val == null)
{
val = new unknown();
}
if (val.getClass().isArray())
{
pref.setExtent((BaseDataType[]) val);
}
else if (val instanceof BaseDataType)
{
pref.set((BaseDataType) val);
}
else
{
//try to convert the value to a BDT
try
{
BaseDataType btdVal = convertFromJavaToBTD(val);
pref.set(btdVal);
}
catch (Exception e)
{
UnimplementedFeature.missing(
"DYNAMIC-PROPERTY statement is not implemented for " + val.getClass());
}
}
}
/**
* Converts the value from java native type to progress base data type equivalent.
*
* @param value
* The value to be converted to BDT.
*
* @return The progress data type equivalent of the given value.
*/
private static BaseDataType convertFromJavaToBTD(Object value)
{
Class<?> jtype = value.getClass();
if (jtype == Integer.class || jtype == Short.class || jtype == Byte.class ||
jtype == int.class || jtype == short.class || jtype == byte.class)
{
return new integer(((Number) value).intValue());
}
else if (jtype == Long.class || jtype == long.class)
{
return new int64(((Number) value).longValue());
}
else if (jtype == Double.class || jtype == Float.class ||
jtype == double.class || jtype == float.class)
{
return new decimal(((Number) value).doubleValue());
}
else if (jtype == BigDecimal.class)
{
return new decimal((BigDecimal) value);
}
else if (jtype == Boolean.class || jtype == boolean.class)
{
return new logical(((Boolean) value).booleanValue());
}
else if (jtype == String.class)
{
return new character((String) value);
}
else if (jtype == byte[].class)
{
return new raw((byte[]) value);
}
else if (jtype == Date.class)
{
return new date((Date) value);
}
else if (jtype == Timestamp.class)
{
return new datetime((Timestamp) value);
}
ErrorManager.recordOrThrowError(-1, "Can not convert value " + value.toString() +
" of type " +jtype + " from Java!");
return unknown.UNKNOWN;
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref, character prop)
{
return getDynamicProperty(ref, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(object<? extends _BaseObject_> ref,
character prop)
{
return getDynamicPropExtent(ref, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(object<? extends _BaseObject_> ref, character prop)
{
return getDynamicPropPoly(ref, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref, String prop)
{
if (!validDynamicPropertyArgs(ref, prop, null, false))
{
return new unknown();
}
PropertyReference pref = new PropertyReference(ref, prop);
return pref.get();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(object<? extends _BaseObject_> ref, String prop)
{
if (!validDynamicPropertyArgs(ref, prop, null, false))
{
//TODO: Check what happens in progress if the execution reaches this place
throw new RuntimeException();
}
PropertyReference pref = new PropertyReference(ref, prop);
return pref.getExtent();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(object<? extends _BaseObject_> ref, String prop)
{
if (!validDynamicPropertyArgs(ref, prop, null, false))
{
//TODO: Check what happens in progress if the execution reaches this place
throw new RuntimeException();
}
PropertyReference pref = new PropertyReference(ref, prop);
if (pref.getPropertyExtent() != null)
{
// This seems to be a 4GL error but strangely, when trying to assign to a variable
// declared as extent 1, when DYNAMIC-PROPERTY is supposed to return an extent of 1,
// progress throws this error. This behavior is currently intentionally omitted.
Object[] result;
result = pref.getExtent();
if (result.getClass().getComponentType() == object.class)
{
object<?>[] castedResult = (object<?>[]) result;
boolean foundInst = false;
for (int i = 0; i < Array.getLength(result); i++)
{
if (castedResult[i]._isValid())
{
foundInst = true;
break;
}
}
if (foundInst)
{
return result;
}
else
{
BaseDataType[] res = new BaseDataType[Array.getLength(result)];
Arrays.fill(res, unknown.UNKNOWN);
return res;
}
}
return result;
}
else
{
Object result;
result = pref.get();
if (result.getClass() == object.class && ((object<?>) result).ref == null)
{
return unknown.UNKNOWN;
}
else
{
return result;
}
}
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref,
character prop,
NumberType idx)
{
if (!validDynamicPropertyArgs(ref, prop, idx, false))
{
return new unknown();
}
PropertyReference pref = new PropertyReference(ref, prop.toStringMessage(), idx);
return pref.get();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param ref
* The object reference to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(object<? extends _BaseObject_> ref,
String prop,
NumberType idx)
{
if (!validDynamicPropertyArgs(ref, prop, idx, false))
{
return new unknown();
}
PropertyReference pref = new PropertyReference(ref, prop, idx);
return pref.get();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(character className, character prop)
{
return getDynamicProperty(className.toStringMessage(), prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(character className, character prop)
{
return getDynamicPropExtent(className.toStringMessage(), prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(character className, character prop)
{
return getDynamicPropPoly(className.toStringMessage(), prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(String className, character prop)
{
return getDynamicProperty(className, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(String className, character prop)
{
return getDynamicPropExtent(className, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(String className, character prop)
{
return getDynamicPropPoly(className, prop.toStringMessage());
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(character className, String prop)
{
return getDynamicProperty(className.toStringMessage(), prop);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(character className, String prop)
{
return getDynamicPropExtent(className.toStringMessage(), prop);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(character className, String prop)
{
return getDynamicPropPoly(className.toStringMessage(), prop);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(String className, String prop)
{
if (!validDynamicPropertyArgs(className, prop, null, false))
{
return new unknown();
}
PropertyReference pref = new PropertyReference(className, prop);
return pref.get();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type should be an <code>EXTENT</code>.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The extent property value.
*/
public static BaseDataType[] getDynamicPropExtent(String className, String prop)
{
if (!validDynamicPropertyArgs(className, prop, null, false))
{
//TODO: Check what happens in progress if the execution reaches this place
throw new RuntimeException();
}
PropertyReference pref = new PropertyReference(className, prop);
return pref.getExtent();
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter). This method is generated if it is
* inferred from the surrounding context that the return type can either be an
* <code>EXTENT</code> or a scalar value.
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name.
*
* @return The property value.
*/
public static Object getDynamicPropPoly(String className, String prop)
{
if (!validDynamicPropertyArgs(className, prop, null, false))
{
//TODO: Check what happens in progress if the execution reaches this place
throw new RuntimeException();
}
PropertyReference pref = new PropertyReference(className, prop);
if (pref.getPropertyExtent() != null)
{
// This seems to be a 4GL error but strangely, when trying to assign to a variable
// declared as extent 1, when DYNAMIC-PROPERTY is supposed to return an extent of 1,
// progress throws this error. This behavior is currently intentionally omitted.
Object[] result;
result = pref.getExtent();
if (result.getClass().getComponentType() == object.class && Array.getLength(result) != 0)
{
object<?>[] castedResult = (object<?>[]) result;
boolean foundInst = false;
for (int i = 0; i < Array.getLength(result); i++)
{
if (castedResult[i]._isValid())
{
foundInst = true;
break;
}
}
if (foundInst)
{
return result;
}
else
{
BaseDataType[] res = new BaseDataType[Array.getLength(result)];
Arrays.fill(res, unknown.UNKNOWN);
return res;
}
}
return result;
}
else
{
Object result = pref.get();
if (result.getClass() == object.class && ((object<?>) result).ref == null)
{
return unknown.UNKNOWN;
}
else
{
return result;
}
}
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(character className, character prop, NumberType idx)
{
return getDynamicProperty(className.toStringMessage(), prop.toStringMessage(), idx);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(String className, character prop, NumberType idx)
{
return getDynamicProperty(className, prop.toStringMessage(), idx);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(character className, String prop, NumberType idx)
{
return getDynamicProperty(className.toStringMessage(), prop, idx);
}
/**
* Implementation of the DYNAMIC-PROPERTY function (getter).
*
* @param className
* The legacy class to which the property belongs.
* @param prop
* The legacy property name (an extent property).
* @param idx
* The index in the extent property.
*
* @return The property value.
*/
public static BaseDataType getDynamicProperty(String className, String prop, NumberType idx)
{
if (!validDynamicPropertyArgs(className, prop, idx, false))
{
return new unknown();
}
PropertyReference pref = new PropertyReference(className, prop, idx);
return pref.get();
}
/**
* Wrapper for an argument resulting from a {@link #invokePoly} or {@link #invokeStandalonePoly} call; in
* this case, the direct Java call must enforce the type at compile time, and the runtime will check if the
* returned value is compatible with this type.
*
* @param <T>
* The expected type of this call.
* @param type
* The expected type of this call.
* @param res
* The result to check.
*
* @return The returned value. An ERROR condition will be raised if the types are incompatible.
*/
public static <T extends _BaseObject_> object<T> polyObjectArg(Class<T> type, Object res)
{
Class<?> resType = null;
if (res instanceof object)
{
object<?> obj = (object<?>) res;
if (obj._isValid())
{
resType = obj.ref.getClass();
}
}
if (resType != null && !type.isAssignableFrom(resType))
{
// WARNING: the error message here deviates from the standard error message shown bellow. This is
// because 'polyArg' is emitted for direct Java calls, and we want to avoid the overhead of using
// the dynamic APIs to execute this call.
// Routine <program> <caller> sent called routine <target> <target-class> mismatched parameters. (2570)
// Error attempting to push run time parameters onto the stack. (984)
ErrorManager.recordOrThrowError(new int[] { 2570, 984 },
new String[]
{
"Routine sent called routine mismatched parameters",
"Error attempting to push run time parameters onto the stack"
},
false);
return new object<>(type);
}
return (object) res;
}
/**
* Wrapper for an argument resulting from a {@link #invokePoly} or {@link #invokeStandalonePoly} call; in
* this case, the direct Java call must enforce the type at compile time, and the runtime will check if the
* returned value is compatible with this type.
*
* @param <T>
* The expected type of this call.
* @param type
* The expected type of this call.
* @param res
* The result to check.
*
* @return The returned value. An ERROR condition will be raised if the types are incompatible.
*/
public static <T> T polyArg(Class<T> type, Object res)
{
Class<?> resType = res == null ? null : res.getClass();
if (resType != null && !type.isAssignableFrom(resType))
{
// WARNING: the error message here deviates from the standard error message shown bellow. This is
// because 'polyArg' is emitted for direct Java calls, and we want to avoid the overhead of using
// the dynamic APIs to execute this call.
// Routine <program> <caller> sent called routine <target> <target-class> mismatched parameters. (2570)
// Error attempting to push run time parameters onto the stack. (984)
ErrorManager.recordOrThrowError(new int[] { 2570, 984 },
new String[]
{
"Routine sent called routine mismatched parameters",
"Error attempting to push run time parameters onto the stack"
},
false);
return (T) BaseDataType.generateUnknown(type);
}
return (T) res;
}
/**
* Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted
* during conversion.
* <p>
* For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
* is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
* it tries to invoke the next method, so we need to enforce it early.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param <T>
* The expected return type.
* @param retType
* The expected return type. The actual runtime returned type must be the same or a sub-class
* of this type.
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Always a {@link object} instance with the specified type.
*/
public static <T extends _BaseObject_> object<T> invokePoly(Class<T> retType,
Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
if (!loadClass(local.get(), targetType))
{
return new object<T>(retType);
}
return (object<T>) invokePolyWorker(false, retType, targetType, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the method lookup is enforced via a type emitted during
* conversion.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any {@link BaseDataType} type, as this is not emitted as lvalue for chained OO calls.
*/
public static BaseDataType invokePoly(Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
if (!loadClass(local.get(), targetType))
{
return unknown.UNKNOWN;
}
return invokePolyWorker(false, null, targetType, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the method lookup is enforced via a type emitted during
* conversion.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalonePoly(Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
if (!loadClass(local.get(), targetType))
{
return;
}
invokePolyWorker(true, null, targetType, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted
* during conversion.
* <p>
* For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
* is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
* it tries to invoke the next method, so we need to enforce it early.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param <T>
* The expected return type.
* @param retType
* The expected return type. The actual runtime returned type must be the same or a sub-class
* of this type.
* @param ref
* The reference on which the invocation will be performed.
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Always a {@link object} instance with the specified type.
*/
public static <T extends _BaseObject_> object<T> invokePoly(Class<T> retType,
object<? extends _BaseObject_> ref,
Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
_BaseObject_ theRef = ref.ref();
if (theRef == null)
{
return new object<T>(retType);
}
return (object<T>) invokePolyWorker(false, retType, theRef, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the method lookup is enforced via a type emitted during
* conversion.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param ref
* The reference on which the invocation will be performed.
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any {@link BaseDataType} type, as this is not emitted as lvalue for chained OO calls.
*/
public static BaseDataType invokePoly(object<? extends _BaseObject_> ref,
Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
_BaseObject_ theRef = ref.ref();
if (theRef == null)
{
return unknown.UNKNOWN;
}
return invokePolyWorker(false, null, theRef, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the method lookup is enforced via a type emitted during
* conversion.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
*
* @param ref
* The reference on which the invocation will be performed.
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalonePoly(object<? extends _BaseObject_> ref,
Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
_BaseObject_ theRef = ref.ref();
if (theRef == null)
{
return;
}
invokePolyWorker(true, null, theRef, targetType, mthdName, modes, args);
}
/**
* Perform a dynamic method call, on which the return type and the lookup are enforced via types emitted
* during conversion.
* <p>
* For the return type, if is not compatible, FWD will raise a 'Caller compilation ... 12882' ERROR; this
* is earlier than OE does, but for chained calls, Java will abend if the correct type is not on stack when
* it tries to invoke the next method, so we need to enforce it early.
* <p>
* For the method lookup, it will be limited to the target type emitted during conversion, and not the
* runtime type.
* @param voidReturn
* Flag indicating if the method is expected to return a value or not.
* @param retType
* When set, the expected return type. The actual runtime returned type must be the same or a
* sub-class of this type.
* @param referent
* The reference on which the invocation will be performed. This is either a sub-type of
* {@link _BaseObject_} (for static calls), or a {@link _BaseObject_} instance, otherwise.
* @param targetType
* The target type where the method lookup will be performed.
* @param mthdName
* The target method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The method result.
*/
private static BaseDataType invokePolyWorker(boolean voidReturn,
Class<? extends _BaseObject_> retType,
Object referent,
Class<? extends _BaseObject_> targetType,
String mthdName,
String modes,
Object... args)
{
boolean isStatic = referent instanceof Class;
if (isStatic)
{
referent = ObjectOps.getStaticInstance((Class<? extends _BaseObject_>) referent);
}
String clsName = getLegacyName(targetType);
InternalEntry ie = ControlFlowOps.resolveLegacyEntry(true, targetType, clsName, mthdName, isStatic, modes, args);
if (ie == null && !isStatic)
{
isStatic = true;
referent = ObjectOps.getStaticInstance(targetType);
// allow a static method to be invoked from an instance context
ie = ControlFlowOps.resolveLegacyEntry(true, targetType, clsName, mthdName, isStatic, null, args);
}
if (ie == null)
{
WorkArea wa = local.get();
String callingProcedure = SessionUtils._isRemote() ? "(Remote client)" : wa.pm.getStackEntry(0);
String msg = "Routine %s sent called routine %s %s mismatched parameters";
msg = String.format(msg, callingProcedure, mthdName, clsName);
ErrorManager.recordOrThrowError(2570, msg, false);
return unknown.UNKNOWN;
}
Object ret = ControlFlowOps.invokeLegacyMethod(ie, voidReturn, referent, clsName, mthdName, null, args);
// OpenEdge is falling into an 'unstable' duck-typing mode when the return value of the runtime-resolved
// method and the compile-resolved method are not a match.
// in FWD, we do a 'best-of': the conversion-time resolved return type must be an exact match or a
// super-type for the runtime-returned method's value.
if (retType != null)
{
Class<?> mthdRet = null;
if (ret instanceof object)
{
if (((object) ret)._isValid())
{
mthdRet = ((object) ret).ref.getClass();
}
else
{
// unknown, assume the same
mthdRet = retType;
}
}
if (mthdRet == null || !retType.isAssignableFrom(mthdRet))
{
// the exact error message is this:
// Could not access element '<method-name>' of class '<qualified-class-name>' using object of type
// '<qualified-actual-return-type>' - caller compilation is out of sync with class compilation. (12882)
// this exact error is raised when the next chained call is being performed, and not when the
// 'mismatched' return type is determined in FWD - in OpenEdge terms, this is a programming error
// and needs to be fixed in the 4GL code.
// in FWD terms, to solve this, would mean that right-side chained call needs to be treated as
// 'poly', too, which just complicates the generated Java code.
// the error message is not the same as 4GL, because at this point there is no knowledge about
// what method will be called; we enforced just the type.
String msg = "Caller compilation is out of sync with class compilation - return type is " +
retType + " while the expected type is " + ie.getMethod().getReturnType();
ErrorManager.recordOrThrowError(12882, msg);
return retType == null ? unknown.UNKNOWN : new object<>(retType);
}
}
return (BaseDataType) ret;
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(String clsName, String mthdName)
{
return invoke(clsName, mthdName, null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(String clsName, String mthdName)
{
return invokeExtent(clsName, mthdName, null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(String clsName, String mthdName)
{
return dynamicInvoke(clsName, mthdName, null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(character clsName, String mthdName)
{
return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(character clsName, String mthdName)
{
return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(character clsName, String mthdName)
{
return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(String clsName, character mthdName)
{
return invoke(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(String clsName, character mthdName)
{
return invokeExtent(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(String clsName, character mthdName)
{
return dynamicInvoke(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(character clsName, character mthdName)
{
return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(character clsName, character mthdName)
{
return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(character clsName, character mthdName)
{
return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(character clsName,
String mthdName,
String modes,
Object... args)
{
return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
modes,
args);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(character clsName,
String mthdName,
String modes,
Object... args)
{
return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
modes,
args);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(character clsName,
String mthdName,
String modes,
Object... args)
{
return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(String clsName,
character mthdName,
String modes,
Object... args)
{
return invoke(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(String clsName,
character mthdName,
String modes,
Object... args)
{
return invokeExtent(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(String clsName,
character mthdName,
String modes,
Object... args)
{
return dynamicInvoke(clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(character clsName,
character mthdName,
String modes,
Object... args)
{
return invoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(character clsName,
character mthdName,
String modes,
Object... args)
{
return invokeExtent(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(character clsName,
character mthdName,
String modes,
Object... args)
{
return dynamicInvoke(clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(String clsName, String mthdName, String modes, Object... args)
{
Object result = invoke(false, clsName, mthdName, modes, args);
//Check for a non valid return type
if (result.getClass().isArray())
{
ErrorManager.recordOrThrowError(11428);
}
return (BaseDataType) result;
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(String clsName,
String mthdName,
String modes,
Object... args)
{
Object result = invoke(false, clsName, mthdName, modes, args);
//Check for a non valid return type
if (!result.getClass().isArray())
{
ErrorManager.recordOrThrowError(11428);
}
return (BaseDataType[]) result;
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(String clsName,
String mthdName,
String modes,
Object... args)
{
return invoke(false, clsName, mthdName, modes, args);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(object<? extends _BaseObject_> ref, character mthdName)
{
return invoke(ref, mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
character mthdName)
{
return invokeExtent(ref,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(object<? extends _BaseObject_> ref, character mthdName)
{
return dynamicInvoke(ref,
mthdName.isUnknown() ?
(String) null :
mthdName.toStringMessage(), null);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(object<? extends _BaseObject_> ref, String mthdName)
{
return invoke(ref, mthdName, null);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
String mthdName)
{
return invokeExtent(ref, mthdName, null);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
*
* @return The EXTENT or scalar result of the specified method.
*/
public static Object dynamicInvoke(object<? extends _BaseObject_> ref, String mthdName)
{
return dynamicInvoke(ref, mthdName, null);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(object<? extends _BaseObject_> ref,
character mthdName,
String modes,
Object... args)
{
return invoke(ref,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
character mthdName,
String modes,
Object... args)
{
return invokeExtent(ref,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static Object dynamicInvoke(object<? extends _BaseObject_> ref,
character mthdName,
String modes,
Object... args)
{
return dynamicInvoke(ref,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
public static BaseDataType invoke(object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
Object result = invoke(false, ref, mthdName, modes, args);
// Check the result for an EXTENT type
if (result.getClass().isArray())
{
ErrorManager.recordOrThrowError(11428);
}
return (BaseDataType) result;
}
/**
* Handles the invocation of the specified static method in the specified class. It is
* generated if inferred from the surrounding context that the result should be an
* <code>EXTENT</code>.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static BaseDataType[] invokeExtent(object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
Object result = invoke(false, ref, mthdName, modes, args);
//Check for a non valid return type
if (!result.getClass().isArray())
{
ErrorManager.recordOrThrowError(11428);
}
return (BaseDataType[]) result;
}
/**
* Handles the invocation of the specified method in the specified class. It is generated
* if inferred from the surrounding context that the result can either be an <code>EXTENT</code>
* or a scalar value.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return The EXTENT result of the specified method.
*/
public static Object dynamicInvoke(object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
return invoke(false, ref, mthdName, modes, args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(String clsName, String mthdName)
{
invoke(true, clsName, mthdName, null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(character clsName, String mthdName)
{
invoke(true,
clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(String clsName, character mthdName)
{
invoke(true,
clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(character clsName, character mthdName)
{
invoke(true,
clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
null);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(character clsName,
String mthdName,
String modes,
Object... args)
{
invoke(true,
clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName,
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(String clsName,
character mthdName,
String modes,
Object... args)
{
invoke(true,
clsName,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(character clsName,
character mthdName,
String modes,
Object... args)
{
invoke(true,
clsName.isUnknown() ? (String) null : clsName.toStringMessage(),
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified static method in the specified class.
*
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(String clsName,
String mthdName,
String modes,
Object... args)
{
invoke(true, clsName, mthdName, modes, args);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(object<? extends _BaseObject_> ref, character mthdName)
{
invoke(true, ref, mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(), null);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
*/
public static void invokeStandalone(object<? extends _BaseObject_> ref, String mthdName)
{
invoke(true, ref, mthdName, null);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(object<? extends _BaseObject_> ref,
character mthdName,
String modes,
Object... args)
{
invoke(true,
ref,
mthdName.isUnknown() ? (String) null : mthdName.toStringMessage(),
modes,
args);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*/
public static void invokeStandalone(object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
invoke(true, ref, mthdName, modes, args);
}
/**
* Deletes the given object.
*
* @param o
* The object to be deleted.
*/
public static void delete(object<?> o)
{
if (o.isUnknown() || !isValid(o.ref))
{
ErrorManager.recordOrThrowError(5425,
"Invalid or inappropriate handle value given to " +
"DELETE OBJECT or DELETE PROCEDURE statement",
false);
return;
}
// non-tracked instances cannot be deleted, this is a NOP
if (!o.ref.isTracked())
{
return;
}
boolean deleted = false;
try
{
deleted = delete(o.ref);
}
finally
{
if (deleted)
{
o.setUnknown();
}
}
}
/**
* Create a new dynamic object.
*
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newDynamicInstance(String legacyClsName)
{
return newDynamicInstance(legacyClsName, null);
}
/**
* Create a new dynamic object; this API is emitted for a NEW converted to DYNAMIC-NEW, as the arguments
* are POLY.
*
* @param cls
* The legacy class.
* @param modes
* The argument's modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newDynamicInstance(Class<T> cls,
String modes,
Object... args)
{
// enums cannot be instantiated
if (LegacyEnum.failEnumInstantiation(cls))
{
return new object<T>();
}
String legacyClsName = getLegacyName(cls);
return newInstanceInternal(cls, legacyClsName, modes, args);
}
/**
* Create a new dynamic object.
*
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument's modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newDynamicInstance(String legacyClsName,
String modes,
Object... args)
{
Class<T> cls = resolveClass(legacyClsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(14284,
"DYNAMIC-NEW could not find class " + legacyClsName,
false);
return new object<T>();
}
// enums cannot be instantiated
if (LegacyEnum.failEnumInstantiation(cls))
{
return new object<T>();
}
//TODO: Fix the case for throwing the error 13844 and not 15310
return newInstanceInternal(cls, legacyClsName, modes, args);
}
/**
* Create a new dynamic object.
*
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newDynamicInstance(character legacyClsName)
{
return newDynamicInstance(legacyClsName.toStringMessage(), null);
}
/**
* Create a new dynamic object.
*
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument's modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newDynamicInstance(character legacyClsName,
String modes,
Object... args)
{
return newDynamicInstance(legacyClsName.toStringMessage(), modes, args);
}
/**
* Create a new object.
*
* @param cls
* The object's class.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newInstance(Class<T> cls)
{
return newInstance(cls, null);
}
/**
* Create a new object.
*
* @param cls
* The object's class.
* @param bypassConstructor
* Avoid executing the constructor method during initialization if <code>true</code>.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newInstance(Class<T> cls,
boolean bypassConstructor,
Object... args)
{
String name = getLegacyName(cls);
return newInstanceInternal(cls, name, null, bypassConstructor, args);
}
/**
* Create a new object.
*
* @param cls
* The object's class.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newInstance(Class<T> cls,
String modes,
Object... args)
{
String name = getLegacyName(cls);
return newInstanceInternal(cls, name, modes, false, args);
}
/**
* Create a new object.
*
* @param cls
* The object's class.
* @param name
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newInstanceInternal(Class<T> cls,
String name,
String modes,
Object... args)
{
return newInstanceInternal(cls, name, modes, false, args);
}
/**
* Create a new object.
*
* @param cls
* The object's class.
* @param name
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument modes.
* @param bypassConstructor
* Avoid executing the constructor method during initialization if <code>true</code>.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
public static <T extends _BaseObject_> object<T> newInstanceInternal(Class<T> cls,
String name,
String modes,
boolean bypassConstructor,
Object... args)
{
if (cls == _BaseObject_.class)
{
cls = (Class<T>) BaseObject.class;
}
// must not be an interface, enum or an abstract class
if (LegacyEnum.class.isAssignableFrom(cls))
{
String cname = ObjectOps.getLegacyName(cls);
// in the 4GL, this is a compile error but this provides runtime safety
String msg = String.format("Cannot use NEW statement with '%s' because it is an enum",
cname);
ErrorManager.recordOrThrowError(17970, msg, false);
return new object<T>(cls);
}
if (Modifier.isAbstract(cls.getModifiers()))
{
ErrorManager.recordOrThrowError(15107,
"You cannot create an instance of " + name +
" because it is an abstract class",
false);
return new object<T>(cls);
}
if (cls.isInterface())
{
ErrorManager.recordOrThrowError(12879,
name + " is an interface and cannot be used as a " +
"procedure name for RUN or the type name for a NEW " +
"statement",
false);
return new object<T>(cls);
}
ObjectResource res = null;
WorkArea wa = local.get();
wa.tm.registerTopLevelScopeable(wa);
try
{
// before creating the resource, load the static constructor(s)
if (!loadClass(wa, cls))
{
return new object<T>(cls);
}
String oldName = wa.pm.getInstantiatingExternalProgram();
Class<?> oldCls = wa.pm.getInstantiatingExternalProgramClass();
T ref = null;
try
{
wa.pm.setInstantingExternalProgram(name);
wa.pm.setInstantingExternalProgramClass(cls);
ref = cls.getDeclaredConstructor().newInstance();
}
finally
{
wa.pm.setInstantingExternalProgram(oldName);
wa.pm.setInstantingExternalProgramClass(oldCls);
}
res = newInstanceInternal(wa, ref, cls, name, modes, bypassConstructor, args);
if (res == null)
{
String errMsg = "Ambiguous runtime method call. Could not resolve " +
cls.getSimpleName() + " reference";
ErrorManager.recordOrThrowError(13844, errMsg, false);
return null;
}
return new object<T>(ref);
}
catch (ReflectiveOperationException |
IllegalArgumentException e)
{
ControlFlowOps.cleanupPending();
if (res != null)
{
res.delete();
}
// TODO: log this?
return new object<T>(cls);
}
catch (RuntimeException exe)
{
ControlFlowOps.cleanupPending();
throw exe;
}
}
/**
* Create a new object.
*
* @param wa
* The context-local state.
* @param ref
* The Java reference for this {@link _BaseObject_} instance.
* @param cls
* The object's class.
* @param name
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
private static <T extends _BaseObject_> ObjectResource newInstanceInternal(WorkArea wa,
T ref,
Class<T> cls,
String name,
String modes,
Object... args)
{
return newInstanceInternal(wa, ref, cls, name, modes, false, args);
}
/**
* Create a new object.
*
* @param wa
* The context-local state.
* @param ref
* The Java reference for this {@link _BaseObject_} instance.
* @param cls
* The object's class.
* @param name
* The legacy class name. This may be qualified or unqualified.
* @param modes
* The argument modes.
* @param bypassConstructor
* Avoid executing the constructor method during initialization if <code>true</code>.
* @param args
* The arguments.
*
* @return A new created object wrapped in a {@link object} instance.
*/
private static <T extends _BaseObject_> ObjectResource newInstanceInternal(WorkArea wa,
T ref,
Class<T> cls,
String name,
String modes,
boolean bypassConstructor,
Object... args)
{
// enums can't be instantiated
if (LegacyEnum.class.isAssignableFrom(cls))
{
String cname = ObjectOps.getLegacyName(cls);
// in the 4GL, this is a compile error but this provides runtime safety
String msg = String.format("Cannot use NEW statement with '%s' because it is an enum",
cname);
ErrorManager.recordOrThrowError(17970, msg, false);
return null;
}
// create a synthetic resource tracking this object; this is performed before anything
// else (static or instance constructor) gets executed
ObjectResource res = new ObjectResource(ref);
List<InternalEntry> dtors = SourceNameMapper.getDestructors(cls);
if (dtors != null)
{
for (InternalEntry dtor : dtors)
{
res.pushDestructor(dtor);
}
}
// this is saved regardless if instantiation passes or not - it will be cleared on delete
wa.objects.put(ref, res);
ref.__setValidInternal__(true);
Method[] executes = SourceNameMapper.getExecuteMethods(cls);
// if an error was throw from within a c'tor (or UNDO, THROW with ROUTINE-LEVEL ON ERROR UNDO, THROW.)
// then the initialization failed and call the destructors for all initialized classes
pendingInitialize(wa, ref);
boolean ok = false;
boolean err = false;
try
{
ok = ControlFlowOps.initializeLegacyObject(ref, name, executes, modes, bypassConstructor, args);
}
// allow Progress compatible exceptions to flow through
catch (ConditionException | RetryUnwindException ce)
{
err = true;
throw ce;
}
finally
{
endInitialization(wa, ref);
if (!ok)
{
// couldn't initialize... delete the resource
res.delete();
if (!res.valid())
{
wa.objects.remove(ref);
ref.__setValidInternal__(false);
}
if (!err)
{
return null;
}
}
}
if (!isValid(ref))
{
// the object was deleted by the constructor - don't do anything else
return null;
}
// 4GL deletes the NEW statement's reference immediately (if was not saved by other
// means). we delay this check until the block exits.
pendingAssign(wa, ref, true);
return res;
}
/**
* Check if the given class is the same or a super-class of the given object.
*
* @param o
* The object instance.
* @param cls
* The type to check.
*
* @return <code>true</code> if the given class is the same or a super-class of the object's
* type.
*/
public static logical typeOf(BaseDataType o, Class<?> cls)
{
if (o.isUnknown())
{
return logical.UNKNOWN;
}
if (!(o instanceof object))
{
return logical.FALSE;
}
return typeOf((object) o, cls);
}
/**
* Check if the given class is the same or a super-class of the given object.
*
* @param o
* The object instance.
* @param cls
* The type to check.
*
* @return <code>true</code> if the given class is the same or a super-class of the object's
* type.
*/
public static logical typeOf(object<?> o, Class<?> cls)
{
if (o.isUnknown())
{
// unknown value is returned
return logical.UNKNOWN;
}
Object ref = o.ref;
return logical.of(cls != null && ref != null && cls.isInstance(ref));
}
/**
* Check if the given class is the same or a super-class of the given object.
*
* @param o
* The object instance.
* @param cls
* The type to check.
*
* @return <code>true</code> if the given class is the same or a super-class of the object's
* type.
*/
public static logical typeOf(BaseDataType o, String cls)
{
if (o.isUnknown())
{
return logical.UNKNOWN;
}
if (!(o instanceof object))
{
return logical.FALSE;
}
return typeOf((object) o, cls);
}
/**
* Check if the given class is the same or a super-class of the given object.
*
* @param o
* The object instance.
* @param cls
* The type to check.
*
* @return <code>true</code> if the given class is the same or a super-class of the object's
* type.
*/
public static logical typeOf(object<?> o, String cls)
{
if (o.isUnknown())
{
// unknown value is returned
return logical.UNKNOWN;
}
Class<? extends _BaseObject_> lcls = resolveClass(cls);
if (lcls == null)
{
return logical.UNKNOWN;
}
return typeOf(o, lcls);
}
/**
* Cast this object to the specified type.
*
* @param val
* The object instance.
* @param cls
* The type to check.
*
* @return An object of the new type. If the value is not an {@link object}, then unknown
* is returned.
*/
public static <T extends _BaseObject_> object<T> cast(BaseDataType val, Class<T> cls)
{
if (!(val instanceof object))
{
// if the value is not an object, then return unkown. 4GL doesn't show any error.
return new object();
}
return cast((object) val, cls);
}
/**
* Cast this object to the specified type.
*
* @param o
* The object instance.
* @param cls
* The type to check.
*
* @return A new {@link object} instance, if the cast is possible.
*/
public static <T extends _BaseObject_> object<T> cast(object<?> o, Class<T> cls)
{
if (o.isUnknown())
{
// no error is thrown
return new object<T>(cls);
}
if (!cls.isInstance(o.ref))
{
String t1 = getLegacyName((Class<? extends _BaseObject_>) o.ref.getClass());
String t2 = getLegacyName(cls);
// at least for enums this is a warning, the error-status:error is false
ErrorManager.recordOrThrowError(12869, "Invalid cast from " + t1 + " " + t2, false, false);
// unknown object is returned
return new object<T>(cls);
}
return new object<T>((T) o.ref);
}
/**
* Cast this object array to the specified type.
*
* @param o
* The object array instance.
* @param cls
* The type to check.
*
* @return A new {@link object} array instance, if the cast is possible.
*/
public static <T extends _BaseObject_> object<T>[] cast(object<?>[] o, Class<T> cls)
{
object<T>[] objArr = TypeFactory.objectExtent(o.length, cls, false);
for (int i = 0; i < objArr.length; i++)
{
objArr[i].assign(cast(o[i], cls));
}
return objArr;
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param value
* The enum value.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(String cname, int64 value)
{
return dynamicEnum(new character(cname), value);
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param value
* The enum value.
*
* @return The enum instance.
*/
public static <T extends LegacyEnum> object<T> dynamicEnum(character cname, int64 value)
{
Class<T> cls = LegacyEnum.findClass(cname,
ObjectOps::generateInvalidTypeDE,
LegacyEnum::generateFailedToFindEnumType);
if (cls == null)
{
return new object<T>();
}
return LegacyEnum.lookupWorker(cls, value, ObjectOps::generateInvalidValueDE);
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param name
* The enum name.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(String cname, character name)
{
return dynamicEnum(new character(cname), name);
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param name
* The enum name.
*
* @return The enum instance.
*/
public static <T extends LegacyEnum> object<T> dynamicEnum(character cname, character name)
{
Class<T> cls = LegacyEnum.findClass(cname,
ObjectOps::generateInvalidTypeDE,
LegacyEnum::generateFailedToFindEnumType);
if (cls == null)
{
return new object<T>();
}
return LegacyEnum.lookupWorker(cls, name, ObjectOps::generateInvalidValueDE);
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param value
* The enum value.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(String cname, long value)
{
return dynamicEnum(new character(cname), new int64(value));
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param value
* The enum value.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(character cname, long value)
{
return dynamicEnum(cname, new int64(value));
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param name
* The enum name.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(String cname, String name)
{
return dynamicEnum(new character(cname), new character(name));
}
/**
* Find and return the specified enum.
*
* @param cname
* The enum class name.
* @param name
* The enum name.
*
* @return The enum instance.
*/
public static object<? extends LegacyEnum> dynamicEnum(character cname, String name)
{
return dynamicEnum(cname, new character(name));
}
/**
* Cast this object to the specified type, resolved dynamically.
*
* @param o
* The object instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} instance, if the cast is possible.
*/
public static object<? extends _BaseObject_> dynamicCast(object<?> o, String legacyClsName)
{
Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(12900,
"CAST to '" + legacyClsName + "' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
return new object<>();
}
return cast(o, cls);
}
/**
* Cast this object to the specified type, resolved dynamically.
*
* @param o
* The object instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} instance, if the cast is possible.
*/
public static object<? extends _BaseObject_> dynamicCast(object<?> o, character legacyClsName)
{
if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
{
ErrorManager.recordOrThrowError(12900,
"CAST to 'UNKNOWN' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
return new object<>();
}
return dynamicCast(o, legacyClsName.toStringMessage());
}
/**
* Cast this object to the specified type, resolved dynamically.
*
* @param o
* The object instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} instance, if the cast is possible.
*/
public static object<? extends _BaseObject_> dynamicCast(BaseDataType o, String legacyClsName)
{
Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(12900,
"CAST to '" + legacyClsName + "' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
return new object<>();
}
return cast(o, cls);
}
/**
* Cast this object to the specified type, resolved dynamically.
*
* @param o
* The object instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} instance, if the cast is possible.
*/
public static object<? extends _BaseObject_> dynamicCast(BaseDataType o, character legacyClsName)
{
if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
{
ErrorManager.recordOrThrowError(12900,
"CAST to 'UNKNOWN' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
return new object<>();
}
return dynamicCast(o, legacyClsName.toStringMessage());
}
/**
* Cast this object array to the specified type, resolved dynamically.
*
* @param o
* The object array instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} array instance, if the cast is possible.
*/
public static object<? extends _BaseObject_>[] dynamicCast(object<?>[] o, character legacyClsName)
{
if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
{
ErrorManager.recordOrThrowError(12900,
"CAST to 'UNKNOWN' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
}
return dynamicCast(o, legacyClsName.toStringMessage());
}
/**
* Cast this object array to the specified type, resolved dynamically.
*
* @param o
* The object array instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} array instance, if the cast is possible.
*/
public static object<? extends _BaseObject_>[] dynamicCast(object<?>[] o, String legacyClsName)
{
Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(12900,
"CAST to '" + legacyClsName + "' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
}
object<_BaseObject_>[] objArr = TypeFactory.objectExtent(o.length, _BaseObject_.class, false);
int numErr = 0;
int[] errNums = new int[o.length];
String[] errMsgs = new String[o.length];
for (int i = 0; i < objArr.length; i++)
{
final int idx = i;
if (ErrorManager
.silent(() -> objArr[idx].assign(cast(o[idx], cls))))
{
errNums[numErr] = ErrorManager.getErrorNumber(1).intValue();
errMsgs[numErr] = ErrorManager.getErrorText(1).getValue();
numErr++;
}
}
if (numErr > 0)
ErrorManager.recordOrThrowError(Arrays.copyOf(errNums, numErr), Arrays.copyOf(errMsgs, numErr), false, false);
return objArr;
}
/**
* Cast this object array to the specified type, resolved dynamically.
*
* @param o
* The object array instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} array instance, if the cast is possible.
*/
public static object<? extends _BaseObject_>[] dynamicCast(BaseDataType[] o, character legacyClsName)
{
if (legacyClsName.isUnknown() || legacyClsName.toStringMessage().isEmpty())
{
ErrorManager.recordOrThrowError(12900,
"CAST to 'UNKNOWN' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
}
return dynamicCast(o, legacyClsName.toStringMessage());
}
/**
* Cast this object array to the specified type, resolved dynamically.
*
* @param o
* The object array instance.
* @param legacyClsName
* The legacy class name. This may be qualified or unqualified.
*
* @return A new {@link object} array instance, if the cast is possible.
*/
public static object<? extends _BaseObject_>[] dynamicCast(BaseDataType[] o, String legacyClsName)
{
Class<? extends _BaseObject_> cls = resolveClass(legacyClsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(12900,
"CAST to '" + legacyClsName + "' not allowed. " +
"CAST target must be a user-defined type",
false,
false);
}
object<_BaseObject_>[] objArr = TypeFactory.objectExtent(o.length, _BaseObject_.class, false);
int numErr = 0;
int[] errNums = new int[o.length];
String[] errMsgs = new String[o.length];
for (int i = 0; i < objArr.length; i++)
{
final int idx = i;
if (ErrorManager
.silent(() -> objArr[idx].assign(cast(o[idx], cls))))
{
errNums[numErr] = ErrorManager.getErrorNumber(1).intValue();
errMsgs[numErr] = ErrorManager.getErrorText(1).getValue();
numErr++;
}
}
if (numErr > 0)
ErrorManager.recordOrThrowError(Arrays.copyOf(errNums, numErr), Arrays.copyOf(errMsgs, numErr), false, false);
return objArr;
}
/**
* Get the THIS-OBJECT system reference.
*
* @return See above.
*/
public static object<? extends _BaseObject_> thisObject()
{
object<? extends _BaseObject_> o = new object<>();
WorkArea wa = local.get();
Object ref = wa.pm._thisProcedure();
if (ref instanceof _BaseObject_)
{
o.set((_BaseObject_) ref);
}
else if (ref.getClass() == Object.class)
{
Class<? extends _BaseObject_> cls = wa.referents2static.get(ref);
o.set(getClassInstance(cls));
}
return o;
}
/**
* Check if this object is valid.
*
* @param ref
* The legacy object.
*
* @return <code>true</code> if this object is valid.
*/
public static boolean isValid(_BaseObject_ ref)
{
return ref != null && (!ref.isTracked() || ref.__isValidInternal__());
}
/**
* Check if the given instance is serializable.
*
* @param ref
* The instance to check.
*
* @return See above.
*/
public static boolean isSerializable(_BaseObject_ ref)
{
// TODO: check the annotation at the type, and not 'instanceof Serializable'
// TODO: see RemoteInvocationResult.setError, the 'send' flag needs to be adjusted once this is complete.
return false;
}
/**
* Convert this object to its associated pseudo-resource.
*
* @param ref
* The legacy object.
*
* @return The pseudo-resource.
*/
public static ObjectResource asResource(_BaseObject_ ref)
{
return asResource(local.get(), ref);
}
/**
* Convert this object to its associated pseudo-resource.
*
* @param wa
* The {@link WorkArea} instance.
* @param ref
* The legacy object.
*
* @return The pseudo-resource.
*/
public static ObjectResource asResource(WorkArea wa, _BaseObject_ ref)
{
if (ref instanceof LegacyEnum || !ref.isTracked())
{
return new ObjectResource(ref);
}
else
{
return wa.objects.get(ref);
}
}
/**
* Load the given legacy class. This will initialize it by recursively execute the static
* c'tor for all super-classes (if defined).
*
* @param cls
* The class to initialize.
*/
public static void load(Class<? extends _BaseObject_> cls)
{
loadClass(local.get(), cls);
}
/**
* Increment the reference counter for this instance.
*
* @param ref
* The legacy instance.
*/
public static void increment(_BaseObject_ ref)
{
increment(local.get(), ref);
}
/**
* Increment the reference counter for this object instance.
*
* @param obj
* The legacy object instance.
*/
public static void increment(object<? extends _BaseObject_> obj)
{
if (obj != null && obj._isValid())
increment(obj.ref);
}
/**
* Decrement the reference counter for this instance.
*
* @param ref
* The legacy instance.
*/
public static void decrement(_BaseObject_ ref)
{
decrement(local.get(), ref);
}
/**
* Decrement the reference counter for this object instance.
*
* @param obj
* The legacy object instance.
*/
public static void decrement(object<? extends _BaseObject_> obj)
{
if (obj != null && obj._isValid())
decrement(obj.ref);
}
/**
* Decrement the reference counter for this instance.
*
* @param wa
* The context-local state.
* @param ref
* The legacy instance.
*/
public static void decrement(WorkArea wa, _BaseObject_ ref)
{
// non-tracked instances cannot be reference counted, this is a NOP
if (ref != null && !ref.isTracked())
{
return;
}
if (isValid(ref))
{
wa.objects.get(ref).decrement();
}
else
{
// TODO: throw exception?
}
}
/**
* Increment the reference counter for this instance.
*
* @param wa
* The context-local state.
* @param ref
* The legacy instance.
*/
static void increment(WorkArea wa, _BaseObject_ ref)
{
// non-tracked instances cannot be reference counted, this is a NOP
if (ref != null && !ref.isTracked())
{
return;
}
if (isValid(ref))
{
wa.objects.get(ref).increment();
}
else
{
// TODO: throw exception?
}
}
/**
* Register this instance as a local resource. This is used when the instance was created from a remote
* side, and send via serialization.
*
* @param ref
* The instance to register.
*/
static void registerObject(_BaseObject_ ref)
{
WorkArea wa = local.get();
wa.objects.put(ref, new ObjectResource(ref));
}
/**
* When an INPUT object variable is initialized in the top-level block via {@link TypeFactory#object},
* it needs to be registered as 'pending'. This will defer its registration with the
* {@link WorkArea#definitions} until the top-level block starts.
*
* @param objects
* The list of {@link object} definitions.
*/
static void registerPending(Object... objects)
{
WorkArea wa = local.get();
for (Object obj : objects)
{
wa.pendingObjects.add(obj);
}
wa.tm.registerPendingScopeable(wa);
}
/**
* Mark this instance as a pending assignment.
*
* @param ref
* The legacy instance.
*/
static void pendingAssign(_BaseObject_ ref)
{
pendingAssign(local.get(), ref, true);
}
/**
* Mark this instance as a pending assignment.
*
* @param wa
* The {@link WorkArea} instance.
* @param ref
* The legacy instance.
* @param register
* Flag indicating if the scopeable support must be registered.
*/
static void pendingAssign(WorkArea wa, _BaseObject_ ref, boolean register)
{
if (!isValid(ref))
{
return;
}
// pending initialization or the instance was already assigned
if (!ref.isTracked() || isInitializing(wa, ref) || asResource(wa, ref).getReferences() > 0)
return;
if (register)
{
wa.tm.registerBlockScopeable(wa);
}
Set<_BaseObject_> pending = wa.pendingAssigned.peek();
if (pending == Collections.EMPTY_SET)
{
wa.pendingAssigned.pop();
wa.pendingAssigned.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
}
pending.add(ref);
}
/**
* Mark this instance as a pending initialization.
*
* @param wa
* The {@link WorkArea} instance.
* @param ref
* The legacy instance.
*/
private static void pendingInitialize(WorkArea wa, _BaseObject_ ref)
{
List<Object> savePending = wa.pendingObjects.isEmpty() ? null : new ArrayList<>(wa.pendingObjects);
try
{
// clear and restore the pending objects, as 'registerBlockScopeable' may register current block for
// scope notifications, but the 'pending' objects are not ours.
wa.pendingObjects.clear();
wa.tm.registerBlockScopeable(wa);
Set<_BaseObject_> pending = wa.pendingInitialized.peek();
if (pending == Collections.EMPTY_SET)
{
wa.pendingInitialized.pop();
wa.pendingInitialized.push(pending = Collections.newSetFromMap(new IdentityHashMap<>()));
}
pending.add(ref);
}
finally
{
wa.pendingObjects.clear();
if (savePending != null)
{
wa.pendingObjects.addAll(savePending);
}
}
}
/**
* Remove this instance from the pending initializations, as it was initialized.
*
* @param wa
* The {@link WorkArea} instance.
* @param ref
* The legacy instance.
*/
static void endInitialization(WorkArea wa, _BaseObject_ ref)
{
for (Set<_BaseObject_> pending : wa.pendingInitialized)
{
if (pending.remove(ref))
{
break;
}
}
}
/**
* Return true if this instance is pending initialization.
*
* @param wa
* The {@link WorkArea} instance.
* @param ref
* The legacy instance.
*/
static boolean isInitializing(WorkArea wa, _BaseObject_ ref)
{
if (wa == null)
{
wa = local.get();
}
for (Set<_BaseObject_> pending : wa.pendingInitialized)
{
if (pending.contains(ref))
{
return true;
}
}
return false;
}
/**
* Check if this instance is a legacy object or a pseudo-instance associated with a static
* class.
*
* @param ref
* The legacy object.
*
* @return See above.
*/
static boolean isLegacyObject(Object ref)
{
return ref != null && (ref instanceof _BaseObject_ || ref.getClass() == Object.class);
}
/**
* Deletes the given object.
*
* @param ref
* The object to be deleted.
*
* @return <code>true</code> if the delete was possible (the instance is not on stack).
*/
static boolean delete(_BaseObject_ ref)
{
return delete(local.get(), ref);
}
/**
* Deletes the given object.
*
* @param wa
* The context-local state.
* @param ref
* The object to be deleted.
*
* @return <code>true</code> if the delete was possible (the instance is not on stack).
*/
static boolean delete(WorkArea wa, _BaseObject_ ref)
{
if (ref instanceof LegacyEnum)
return false;
for (Set<_BaseObject_> pending : wa.pendingAssigned)
{
if (pending.contains(ref))
{
// this reference may be assigned - let it survive and will be cleaned up on the next
// block exit
return false;
}
}
ObjectResource res = wa.objects.get(ref);
if (res.isPendingDelete())
{
return false;
}
// allow the destructors to be called before actually removing the object
res.delete();
if (!res.valid())
{
wa.objects.remove(ref);
ref.__setValidInternal__(false);
}
return !res.valid();
}
/**
* Checks whether an object has any destructors pushed on stack ready to be executed when the
* lifetime of the object ends.
*
* @param ref
* The reference to the object to be tested.
*
* @return {@code true} if when the object is deleted at least one destructor will be
* executed.
*/
public static boolean hasDestructors(_BaseObject_ ref)
{
// non-tracked instances cannot have destructors
if (ref != null && !ref.isTracked())
{
return false;
}
return isValid(ref) && local.get().objects.get(ref).hasActiveDestructors();
}
/**
* When resizing an legacy object array with dynamic extent, we must re-register the array
* reference.
*
* @param oldArray
* The old array.
* @param newArray
* The new array.
*/
static void reRegister(BaseDataType[] oldArray, BaseDataType[] newArray)
{
WorkArea work = local.get();
// go through all scopes
for (Set<Object> scopeArrays : work.definitions)
{
if (scopeArrays.remove(oldArray))
{
// oldArray exists in the scope, replace with newArray
scopeArrays.add(newArray);
return;
}
}
for (Set<Object> scopeArrays : work.extProgDefinitions.values())
{
if (scopeArrays.remove(oldArray))
{
// oldArray exists in the scope, replace with newArray
scopeArrays.add(newArray);
return;
}
}
}
/**
* Get the {@link LegacyClass} mapped in FWD by the specified converted class.
*
* @param cls
* The converted class.
*
* @return A {@link LegacyClass} instance.
*/
public static object<? extends LegacyClass> getLegacyClass(Class<? extends _BaseObject_> cls)
{
return new object<>(getClassInstance(cls));
}
/**
* Get the {@link LegacyClass} mapped in FWD by the specified converted class name.
*
* @param typeName
* The converted class name.
*
* @return A {@link LegacyClass} instance.
*/
public static object<? extends LegacyClass> getLegacyClass(String typeName)
{
Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(typeName);
if (cls == null)
{
ErrorManager.recordOrThrowError(15287,
"Could not dynamically find class'" + typeName + "'",
false, false);
return new object<>();
}
registerClassWorker(typeName, cls);
return getLegacyClass(cls);
}
/**
* Get the {@link LegacyClass} mapped in FWD by the specified converted class name.
*
* @param cls
* The converted class name.
*
* @return A {@link LegacyClass} instance.
*/
public static object<? extends LegacyClass> getLegacyClass(character cls)
{
return getLegacyClass(cls.isUnknown() ? "" : cls.getValue());
}
/**
* Resolve the legacy name for this class.
*
* @param cls
* The class.
*
* @return The legacy name.
*/
public static String getLegacyName(Class<? extends _BaseObject_> cls)
{
return legacyNameWorker(cls);
}
/**
* Given a legacy class, get the pseudo-instance associated with the static members.
*
* @param cls
* The legacy class.
*
* @return The pseudo-instance for this class.
*/
public static Object getStaticInstance(Class<? extends _BaseObject_> cls)
{
// enums are not tracked and have no static legacy state
if (LegacyEnum.class.isAssignableFrom(cls))
{
// TODO: is there a better return value, this seems unsafe
return null;
}
return local.get().staticReferents.get(cls);
}
/**
* Given a static class pseudo-instance, get its associated legacy class.
*
* @param instance
* The pseudo-instance for a static class.
*
* @return The resolved legacy class.
*/
public static Class<? extends _BaseObject_> getStaticClass(Object instance)
{
// enums are not tracked and have no static legacy state
if (instance != null && LegacyEnum.class.isAssignableFrom(instance.getClass()))
{
// TODO: is there a better return value, this seems unsafe
return null;
}
return local.get().referents2static.get(instance);
}
/**
* Emulate an implicit static constructor.
*
* @param cls
* The legacy class for which the constructor will be executed.
*/
static void defaultStaticConstructor(Class<?> cls)
{
// for enums, this is a NOP
if (LegacyEnum.class.isAssignableFrom(cls))
{
return;
}
BlockManager.externalProcedure(cls, new Block());
}
/**
* Load the given legacy class. This will initialize it by recursively execute the static
* c'tor for all super-classes (if defined).
*
* @param wa
* The context-local state.
* @param cls
* The class to initialize.
*
* @return <code>true</code> if the initialization completed.
*/
static boolean loadClass(WorkArea wa, Class<? extends _BaseObject_> cls)
{
// enums are not tracked, legacy lifecycle processing is bypassed
if (LegacyEnum.class.isAssignableFrom(cls))
{
return true;
}
// before trying to load this class, we need to backup (and restore) any state specified
// for the invocation at the variables. This includes the output parameter assigner
OutputParameterAssigner opa = wa.tm.deregisterOutputParameterAssigner();
try
{
return loadClassInt(wa, cls);
}
finally
{
if (opa != null)
{
wa.tm.registerOutputParameterAssigner(opa);
}
}
}
/**
* Perform recursive loading of all classes, up the hierarchy.
*
* @param wa
* The context-local state.
* @param cls
* The class to initialize.
*
* @return <code>true</code> if the initialization completed.
*/
private static boolean loadClassInt(WorkArea wa, Class<? extends _BaseObject_> cls)
{
// enums are not tracked, legacy lifecycle processing is bypassed (we especially need to
// avoid access to staticReferents and referents2static
if (LegacyEnum.class.isAssignableFrom(cls))
{
return true;
}
// TODO: if first static c'tor fails, and second loading attempt doesn't, does 4GL allow
// the class to load?
if (wa.staticReferents.containsKey(cls) || wa.pendingLoadedClasses.contains(cls))
{
// prevent recursive loading, if the static c'tor accesses static members
return true;
}
wa.pendingLoadedClasses.add(cls);
try
{
boolean parentLoaded = true;
if (cls != BaseObject.class)
{
parentLoaded = loadClassInt(wa, (Class<? extends _BaseObject_>) cls.getSuperclass());
}
if (!parentLoaded)
{
return false;
}
Object referent = new Object();
wa.staticReferents.put(cls, referent);
wa.referents2static.put(referent, cls);
String name = getLegacyName(cls);
// first load of a class causes its language to be applied, for good
TranslationManager.getInstance().applyLanguage(cls);
// execute the static c'tor
if (!ControlFlowOps.initializeLegacyClass(cls, referent, name))
{
new ExternalProgramWrapper(referent).delete();
wa.staticReferents.remove(cls);
ErrorManager.recordOrThrowError(14631,
"Static instance failed to load. " +
"Cannot reference class " + name,
false, false);
return false;
}
return true;
}
finally
{
wa.pendingLoadedClasses.remove(cls);
}
}
/**
* Resolve the associated converted class name for this legacy class.
*
* @param cname
* The fully qualified legacy class name.
*
* @return The found {@link Class} or <code>null</code> if it can't be found.
*/
public static <T extends _BaseObject_> Class<T> resolveClass(String cname)
{
if (cname == null)
{
return null;
}
Class<T> type = (Class<T>) lookupClassWorker(cname);
if (type != null)
{
return type;
}
String clsName = SourceNameMapper.convertOOToClass(cname);
if (clsName == null)
{
return null;
}
try
{
return (Class<T>) Class.forName(clsName);
}
catch (ClassNotFoundException ex)
{
return null;
}
}
/**
* Create a mapping between a given legacy class name and the converted Java class instance.
*
* @param cname
* The fully qualified legacy class name.
* @param cls
* The converted type.
*/
public static void registerClass(String cname, Class<? extends _BaseObject_> cls)
{
registerClassWorker(cname, cls);
}
/**
* Create a mapping between a given legacy class name and the converted Java class instance. In this
* the legacy class name will be looked up.
*
* Complete class hierarchy is registered or not depending on the hierarchy flag.
*
* @param cls
* The converted type.
* @param hierarchy
* Flag to load the complete class hierarchy
*/
public static void registerClass(Class<? extends _BaseObject_> cls, boolean hierarchy)
{
String clsName = legacyNameWorker(cls);
if (clsName != null)
{
if (hierarchy)
{
if (cls.getSuperclass() != null && BaseObject.class.isAssignableFrom(cls.getSuperclass()))
{
registerClass((Class<? extends _BaseObject_>) cls.getSuperclass(), hierarchy);
}
for (Class i : cls.getInterfaces())
{
if (_BaseObject_.class.isAssignableFrom(i))
{
registerClass(i, hierarchy);
}
}
}
}
}
/**
* Resolve the {@link LegacyClass} of super class or interface of the class, register it as needed.
*
* @param superName
* name of assignable super class or interface
* @param cls
* The converted type.
*/
public static Class<? extends _BaseObject_> getAssignableClass(String superName,
Class<? extends _BaseObject_> cls)
{
String clsName = getLegacyName(cls);
Class<? extends _BaseObject_> superCls = null;
if (clsName != null && clsName.equalsIgnoreCase(superName))
{
registerClassWorker(superName, cls);
return cls;
}
if (cls.getSuperclass() != null && BaseObject.class.isAssignableFrom(cls.getSuperclass()))
{
superCls = getAssignableClass(superName,
(Class<? extends _BaseObject_>) cls.getSuperclass());
}
if (superCls == null)
{
for (Class i : cls.getInterfaces())
{
if (_BaseObject_.class.isAssignableFrom(i))
{
superCls = getAssignableClass(superName, i);
if (superCls != null)
break;
}
}
}
return superCls;
}
/**
* Get the {@link LegacyClass} instance for the given type.
*
* @param cls
* The converted type.
*
* @return See above.
*/
public static LegacyClass getClassInstance(Class<? extends _BaseObject_> cls)
{
WorkArea wa = local.get();
LegacyClass lc = wa.legacyClasses.get(cls);
if (lc == null)
{
executeStaticConstructor(cls);
LegacyResource lr = cls.getAnnotation(LegacyResource.class);
String clsName = (lr != null ? lr.resource() : SourceNameMapper.getLegacyClassName(cls.getName()));
lc = new LegacyClass(clsName, cls);
registerClassWorker(clsName, cls);
loadClass(wa, LegacyClass.class);
newInstanceInternal(wa, lc, LegacyClass.class, "Progress.Lang.Class", null);
wa.legacyClasses.put(cls, lc);
}
return lc;
}
/**
* Force executing the static constructor now. If this is a legacy enum class, and all access is done
* via reflection, then the 'enum' static fields do not get initialized via reflection access.
*
* @param cls
* The class to check.
*/
private static void executeStaticConstructor(Class<? extends _BaseObject_> cls)
{
if (LegacyEnum.class.isAssignableFrom(cls))
{
// force loading of the static initializer
ErrorManager.nestedSilent(() ->
{
try
{
Method m = cls.getDeclaredMethod("getEnum", int64.class);
// just force execution of the method, we don't want to retreive any enum
m.invoke(null, new int64());
}
catch (Throwable t)
{
// ignore, this also catches the ErrorConditionException from the method call
}
});
}
}
/**
* Return the converted Java class instance for the given legacy class name.
*
* @param cname
* The fully qualified legacy class name.
*
* @return The converted Java class.
*/
private static Class<? extends _BaseObject_> lookupClassWorker(String cname)
{
Class<? extends _BaseObject_> cls = null;
synchronized (lock)
{
cls = name2cls.get(cname.toLowerCase());
}
return cls;
}
/**
* Create a mapping between a given legacy class name and the converted Java class instance.
*
* @param cname
* The fully qualified legacy class name.
* @param cls
* The converted type.
*/
private static void registerClassWorker(String cname, Class<? extends _BaseObject_> cls)
{
if (cname == null || cls == null)
{
// something is wrong, this cannot work
return;
}
synchronized (lock)
{
if (legacyNames.putIfAbsent(cls, cname) == null)
{
name2cls.putIfAbsent(cname.toLowerCase(), cls);
}
}
}
/**
* Resolve the legacy name for this class.
*
* @param cls
* The class.
*
* @return The legacy name.
*/
private static String legacyNameWorker(Class<? extends _BaseObject_> cls)
{
if (cls == _BaseObject_.class)
{
cls = BaseObject.class;
}
String name = null;
synchronized (lock)
{
name = legacyNames.get(cls);
if (name == null)
{
if (cls.isAnnotationPresent(LegacyResource.class))
{
name = cls.getAnnotation(LegacyResource.class).resource();
}
else
{
name = SourceNameMapper.getLegacyClassName(cls.getName());
}
registerClassWorker(name, cls);
}
}
return name;
}
/**
* Invoke the specified static method in the specified class.
*
* @param voidReturn
* Flag indicating that the method can return void.
* @param clsName
* The legacy class name.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
private static Object invoke(boolean voidReturn,
String clsName,
String mthdName,
String modes,
Object... args)
{
if (mthdName == null)
{
ErrorManager.recordOrThrowError(15300,
"Could not evaluate method name parameter for Invoke",
false);
return new unknown();
}
Class<? extends _BaseObject_> cls = resolveClass(clsName);
if (cls == null)
{
ErrorManager.recordOrThrowError(15285,
"A valid class instance or static class name is " +
"required for dynamically invoking a method or " +
"accessing a property",
false);
return new unknown();
}
if (!loadClass(local.get(), cls))
{
return new unknown();
}
return ControlFlowOps.invokeLegacyMethod(voidReturn,
cls,
clsName,
mthdName,
modes,
args);
}
/**
* Invoke the specified instance method in the specified object.
*
* @param voidReturn
* Flag indicating that the method can return void.
* @param ref
* The legacy object reference.
* @param mthdName
* The static method name.
* @param modes
* The argument modes.
* @param args
* The arguments.
*
* @return Any returned value, or unknown if the method is void.
*/
private static Object invoke(boolean voidReturn,
object<? extends _BaseObject_> ref,
String mthdName,
String modes,
Object... args)
{
if (mthdName == null)
{
ErrorManager.recordOrThrowError(15300,
"Could not evaluate method name parameter for Invoke",
false);
return new unknown();
}
if (ref.isUnknown())
{
ErrorManager.recordOrThrowError(15285,
"A valid class instance or static class name is " +
"required for dynamically invoking a method or " +
"accessing a property",
false);
return new unknown();
}
// TODO: invoking methods from builtin classes in some cases raises a
// "Dynamic access to certain built-in methods and properties is not permitted (17008)"
String clsName = getLegacyName(ref.ref.getClass());
return ControlFlowOps.invokeLegacyMethod(voidReturn,
ref.ref,
clsName,
mthdName,
modes,
args);
}
/**
* Generate the invalid type error for ToObject.
*/
private static void generateInvalidTypeDE()
{
ErrorManager.recordOrThrowError(18007, String.format(DE_SPEC, "enum-type-name"));
}
/**
* Generate the invalid value error for ToObject.
*
* @param cls
* The enum for which the value is invalid.
*/
private static void generateInvalidValueDE(Class<?> cls)
{
ErrorManager.recordOrThrowError(18007, String.format(DE_SPEC, "member-name-or-value"));
}
/**
* Validate the reference, property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param ref
* The reference to validate against.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(object<? extends _BaseObject_> ref,
character prop,
NumberType idx,
boolean setter)
{
if (!ref._isValid())
{
ErrorManager.recordOrThrowError(new int[] { 16580 },
new String[] { "Invalid object reference passed to DYNAMIC-PROPERTY" },
false, false, false);
return false;
}
Class<? extends _BaseObject_> cls = ref.ref.getClass();
return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
}
/**
* Validate the reference, property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param ref
* The reference to validate against.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(object<? extends _BaseObject_> ref,
String prop,
NumberType idx,
boolean setter)
{
if (!ref._isValid())
{
ErrorManager.recordOrThrowError(new int[] { 16580 },
new String[] { "Invalid object reference passed to DYNAMIC-PROPERTY" },
false, false, false);
return false;
}
Class<? extends _BaseObject_> cls = ref.ref.getClass();
return validDynamicPropertyArgs(cls, prop, idx, setter);
}
/**
* Validate the class name, property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param className
* The class where the property belongs.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(String className,
character prop,
NumberType idx,
boolean setter)
{
Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(className);
if (cls == null)
{
ErrorManager.recordOrThrowError(15287,
"Could not dynamically find class'" + className + "'",
false, false);
return false;
}
return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
}
/**
* Validate the class name, property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param className
* The class where the property belongs.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(String className,
String prop,
NumberType idx,
boolean setter)
{
Class<? extends _BaseObject_> cls = ObjectOps.resolveClass(className);
if (cls == null)
{
ErrorManager.recordOrThrowError(15287,
"Could not dynamically find class'" + className + "'",
false, false);
return false;
}
return validDynamicPropertyArgs(cls, prop, idx, setter);
}
/**
* Validate the property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param cls
* The class where the property belongs.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(Class<? extends _BaseObject_> cls,
character prop,
NumberType idx,
boolean setter)
{
return validDynamicPropertyArgs(cls, prop.toStringMessage(), idx, setter);
}
/**
* Validate the property and index for a DYNAMIC-PROPERTY getter or setter.
*
* @param cls
* The class where the property belongs.
* @param prop
* The property name.
* @param idx
* The index, non-null only for indexed DYNAMIC-PROPERTY cases.
* @param setter
* Flag indicating if this is a setter or getter call.
*
* @return <code>true</code> if the arguments are valid.
*/
private static boolean validDynamicPropertyArgs(Class<? extends _BaseObject_> cls,
String prop,
NumberType idx,
boolean setter)
{
String msg;
if (prop.isEmpty())
{
msg = "Invalid property name passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] { 16581 }, new String[] { msg }, false, false, false);
return false;
}
String propName = prop;
InternalEntry agetter = SourceNameMapper.findLegacyAccessor(cls, propName, true, false, idx != null);
InternalEntry asetter = SourceNameMapper.findLegacyAccessor(cls, propName, false, false, idx != null);
if (asetter == null && agetter == null)
{
InternalEntry agetterbulk = idx == null
? null
: SourceNameMapper.findLegacyAccessor(cls, propName, true, true, false);
InternalEntry asetterbulk = idx == null
? null
: SourceNameMapper.findLegacyAccessor(cls, propName, false, true, false);
if (agetterbulk != null || asetterbulk != null)
{
if (setter)
{
asetter = asetterbulk;
idx = null;
}
else
{
msg = "Invalid runtime use of indeterminate extent. It must be set to a fixed dimension";
ErrorManager.recordOrThrowError(11387, msg, false);
return false;
}
}
else
{
msg = "Could not find property '" + propName + "' in class '" + getLegacyName(cls) + "'";
ErrorManager.recordOrThrowError(16548, msg, false);
return false;
}
}
if (!setter && agetter == null)
{
msg = "Property '" + propName + "' in Class '" +
getLegacyName((Class<? extends _BaseObject_>) asetter.getMethod().getDeclaringClass()) +
"' does not allow read access";
ErrorManager.recordOrThrowError(13822, msg, false);
return false;
}
if (setter && asetter == null)
{
msg = "Property '" + propName + "' in Class '" +
getLegacyName((Class<? extends _BaseObject_>) agetter.getMethod().getDeclaringClass()) +
"' does not allow write access";
ErrorManager.recordOrThrowError(13851, msg, false);
return false;
}
// check modifier
Method mthd = setter ? asetter.getMethod() : agetter.getMethod();
int mods = mthd.getModifiers();
if (!Modifier.isPublic(mods))
{
WorkArea wa = local.get();
Class thisProcClass = wa.pm._thisProcedure().getClass();
if (Modifier.isPrivate(mods))
{
// same class
if (thisProcClass != mthd.getDeclaringClass())
{
if (setter)
{
msg = "Property " + propName + " has a PRIVATE SET accessor so " +
"can be set only from within the class where it's defined";
ErrorManager.recordOrThrowError(13835, msg, false);
}
else
{
msg = "Property " + propName + " has a PRIVATE GET accessor so " +
"can be read only from within the class where it's defined";
ErrorManager.recordOrThrowError(13833, msg, false);
}
return false;
}
}
else if (Modifier.isProtected(mods))
{
// same class or the accessor is from a super-class
if (!mthd.getDeclaringClass().isAssignableFrom(thisProcClass))
{
if (setter)
{
msg = "Property " + propName + " has a PROTECTED SET accessor so can be set only from " +
"within the class hierarchy where it's defined";
ErrorManager.recordOrThrowError(13836, msg, false);
}
else
{
msg = "Property " + propName + " has a PROTECTED GET accessor so can be read only from " +
"within the class hierarchy where it's defined";
ErrorManager.recordOrThrowError(13834, msg, false);
}
return false;
}
}
}
if (idx != null)
{
if (idx.isUnknown())
{
if (setter)
{
msg = "Array subscript 0 is out of range";
ErrorManager.recordOrThrowError(26, msg);
}
else
{
msg = "Invalid array index passed to DYNAMIC-PROPERTY";
ErrorManager.recordOrThrowError(new int[] { 16582 }, new String[] { msg }, false, false, false);
}
return false;
}
Integer extent = SourceNameMapper.findLegacyPropertyExtent(cls, propName);
if (extent == null)
{
if (setter)
{
msg = "** Array subscript " + idx.intValue() + " is out of range";
ErrorManager.recordOrThrowError(26, msg, false);
}
else
{
msg = "Invalid runtime use of indeterminate extent. It must be set to a fixed dimension";
ErrorManager.recordOrThrowError(11387, msg, false);
}
return false;
}
else if (idx.intValue() > extent || idx.intValue() < 1)
{
if (setter)
{
msg = "** Array subscript " + idx.intValue() + " is out of range";
ErrorManager.recordOrThrowError(26, msg, false);
}
else
{
msg = "Array subscript " + idx.intValue() + " is out of range. " +
"The indeterminate extent is fixed to a dimension of " + extent;
ErrorManager.recordOrThrowError(11388, msg, false);
}
return false;
}
}
// TODO: yes Built-in class Progress.Lang.Object, property Next-Sibling is not settable. (16939)
return true;
}
/**
* Helper to expose APIs which have direct access to the context-local state.
*/
public static class ObjectHelper
{
/** The {@link WorkArea} instance. */
private final WorkArea wa;
/**
* Create a new instance and associate the given WorkArea instance.
*
* @param wa
* The {@link WorkArea} instance.
*/
public ObjectHelper(WorkArea wa)
{
this.wa = wa;
}
/**
* Register the {@link WorkArea} scopeable as pending, to be added to the next block.
*/
public void registerPendingScopeable()
{
wa.tm.registerPendingScopeable(wa);
}
/**
* Get the state of the {@link WorkArea#inScopeFinished} flag.
*
* @return <code>true</code> if we are in {@link WorkArea#scopeDeleted()} or
* {@link WorkArea#scopeFinished()}.
*/
public boolean inScopeFinished()
{
return wa.inScopeFinished;
}
/**
* Set the state of the {@link WorkArea#inScopeFinished} flag.
*
* @param state
* The flag's value.
*/
public void setInScopeFinished(boolean state)
{
wa.inScopeFinished = state;
}
/**
* Register the {@link WorkArea} scopeable in this current block.
*/
public void registerScopeable()
{
wa.tm.registerTopLevelScopeable(wa);
}
/**
* Register this instance as if it was defined in the current block, so the reference can be tracked
* again - this is useful for 'zero counter' reference returned from a function call, which, if not
* assigned to some other var, will never get deleted.
*
* @param obj
* The object to register.
*/
public void registerObject(ObjectVar<?> obj)
{
BlockType bt = wa.tm.getNearestTopLevelType();
Set<Object> defs;
if (bt == BlockType.EXTERNAL_PROC)
{
Object referent = wa.pm._thisProcedure();
defs = wa.extProgDefinitions.get(referent);
if (defs == null)
{
defs = Collections.newSetFromMap(new IdentityHashMap<>());
wa.extProgDefinitions.put(referent, defs);
}
}
else
{
defs = wa.definitions.peek();
if (defs == Collections.EMPTY_SET)
{
defs = Collections.newSetFromMap(new IdentityHashMap<>());
wa.definitions.pop();
wa.definitions.push(defs);
}
}
defs.add(obj);
}
/**
* Mark this instance as a pending assignment.
*
* @param ref
* The legacy instance.
*/
public void pendingAssign(_BaseObject_ ref)
{
ObjectOps.pendingAssign(wa, ref, true);
}
/**
* Load the given legacy class. This will initialize it by recursively execute the static
* c'tor for all super-classes (if defined).
*
* @param cls
* The class to initialize.
*/
public void load(Class<? extends _BaseObject_> cls)
{
ObjectOps.loadClass(wa, cls);
}
/**
* Deletes the given object.
*
* @param ref
* The object to be deleted.
*
* @return <code>true</code> if the delete was possible (the instance is not on stack).
*/
public boolean delete(_BaseObject_ ref)
{
return ObjectOps.delete(wa, ref);
}
/**
* Deregister the specified instance.
*
* @param ref
* The instance to deregister.
*/
public void deregisterObject(_BaseObject_ ref)
{
wa.objects.remove(ref);
ref.__setValidInternal__(false);
}
/**
* Decrement the reference counter for this instance.
*
* @param ref
* The legacy instance.
*/
public void decrement(_BaseObject_ ref)
{
ObjectOps.decrement(wa, ref);
}
/**
* Increment the reference counter for this instance.
*
* @param ref
* The legacy instance.
*/
public void increment(_BaseObject_ ref)
{
ObjectOps.increment(wa, ref);
}
/**
* Check if the reference is marked as {@link WorkArea#pendingAssigned}.
*
* @param ref
* The reference to check.
*
* @return See above.
*/
public boolean isPendingAssigned(_BaseObject_ ref)
{
return !wa.pendingAssigned.isEmpty() && wa.pendingAssigned.peek().contains(ref);
}
/**
* Check if this reference is saved to be returned by a function or method call.
*
* @param ref
* The legacy instance.
*
* @return <code>true</code> if the instance is pending to be returned by a function or method call.
*/
public boolean instanceReturned(_BaseObject_ ref)
{
return BlockManager.checkFunctionReturn((ret) ->
{
if (ret instanceof object)
{
object o = (object) ret;
if (ref == o.ref)
{
return true;
}
}
return false;
});
}
/**
* Remove this instance from the pending assignments, as it was assigned.
*
* @param ref
* The legacy instance.
*/
public void instanceAssigned(_BaseObject_ ref)
{
if (instanceReturned(ref))
{
return;
}
for (Set<_BaseObject_> pending : wa.pendingAssigned)
{
if (pending.remove(ref))
{
break;
}
}
}
/**
* Return true if this instance is pending initialization.
*
* @param ref
* The legacy instance.
*/
public boolean isInitializing(_BaseObject_ ref)
{
return ObjectOps.isInitializing(wa, ref);
}
/**
* Check if this object is valid.
*
* @param ref
* The legacy object.
*
* @return <code>true</code> if this object is valid.
*/
public boolean isValid(_BaseObject_ ref)
{
return ObjectOps.isValid(ref);
}
}
/**
* Context-local data.
*/
private static class WorkArea
implements Scopeable
{
/** Helper to use the ProcedureManager without any context local lookups. */
private final ProcedureManager.ProcedureHelper pm = ProcedureManager.getProcedureHelper();
/** Helper to use the TM without any context local lookups. */
private final TransactionManager.TransactionHelper tm = TransactionManager.getTransactionHelper();
/** The set of classes pending to be loaded. */
private Set<Class<? extends _BaseObject_>> pendingLoadedClasses = Collections.newSetFromMap(new IdentityHashMap<>());
/** Map of legacy objects to their pseudo-resource. */
private Map<_BaseObject_, ObjectResource> objects = new IdentityHashMap<>();
/** Pending object definitions which need to be registered in the next scope. */
private Set<Object> pendingObjects = Collections.newSetFromMap(new IdentityHashMap<>());
/** Variable definitions which are top-level block members. */
private Deque<Set<Object>> definitions = new ArrayDeque<>();
/** Map of variable definitions which are class members. */
private Map<Object, Set<Object>> extProgDefinitions = new IdentityHashMap<>();
/**
* Bogus referents associated with a static class. These are required to be able to track
* this as a 'persistent procedure' which will never get deleted.
*/
private Map<Class<? extends _BaseObject_>, Object> staticReferents = new IdentityHashMap<>();
/** Bogus referents associated with a static class. */
private Map<Object, Class<? extends _BaseObject_>> referents2static = new IdentityHashMap<>();
/**
* The set of newly created objects which wait to be assigned. These will be deleted on
* the next block's scope finish, if they are not yet assigned.
*/
private Deque<Set<_BaseObject_>> pendingAssigned = new ArrayDeque<>();
/**
* The set of newly created objects which wait to be initialized. These will be deleted
* when the object initialization completes.
*/
private Deque<Set<_BaseObject_>> pendingInitialized = new ArrayDeque<>();
/** The map of instantiated {@link LegacyClass} objects. */
private Map<Class<? extends _BaseObject_>, LegacyClass> legacyClasses = new IdentityHashMap<>();
/** Flag indicating if we are processing {@link #scopeFinished()} or {@link #scopeDeleted()}. */
private boolean inScopeFinished = false;
/**
* Provides a notification that a new scope is about to be entered.
* <p>
* Used to create new collections where to register the variable definitions for that block.
*
* @param block
* The explicit block definition which required this notification.
*/
@Override
public void scopeStart(BlockDefinition block)
{
BlockType bt = block.type;
if (bt.equals(BlockType.INTERNAL_PROC) ||
bt.equals(BlockType.FUNCTION) ||
bt.equals(BlockType.TRIGGER) ||
bt.equals(BlockType.DATABASETRIGGER))
{
if (!pendingObjects.isEmpty())
{
Set<Object> refs = Collections.newSetFromMap(new IdentityHashMap<>());
refs.addAll(pendingObjects);
definitions.push(refs);
}
else
{
definitions.push(Collections.emptySet());
}
pendingObjects.clear();
}
else if (bt.equals(BlockType.EXTERNAL_PROC))
{
if (!pendingObjects.isEmpty())
{
Object referent = pm._thisProcedure();
Set<Object> extProgDefs = extProgDefinitions.get(referent);
if (extProgDefs == null)
{
extProgDefs = Collections.newSetFromMap(new IdentityHashMap<>());
extProgDefinitions.put(referent, extProgDefs);
}
extProgDefs.addAll(pendingObjects);
pendingObjects.clear();
}
}
pendingAssigned.push(Collections.emptySet());
pendingInitialized.push(Collections.emptySet());
}
/**
* Provides a notification that a scope is about to be exited.
* <p>
* All variable definitions defined in this block will have the reference counter decremented
* for the wrapped object.
*/
@Override
public void scopeFinished()
{
boolean prevScopeFinished = inScopeFinished;
inScopeFinished = true;
try
{
BlockType bt = tm.getBlockType();
if (bt.equals(BlockType.INTERNAL_PROC) ||
bt.equals(BlockType.FUNCTION) ||
bt.equals(BlockType.TRIGGER) ||
bt.equals(BlockType.DATABASETRIGGER))
{
Set<Object> defs = definitions.pop();
decrement(defs);
}
if (!pendingInitialized.isEmpty())
pendingInitialized.pop();
// this is done AFTER any decrement is performed - because in case of functions returning
// an object, that will be marked as 'pending assigned' and the object will not be
// deleted (but the reference will be decremented).
if (!pendingAssigned.isEmpty())
{
Set<_BaseObject_> pending = pendingAssigned.peek();
if (pending != null && !pending.isEmpty())
{
// 4GL deletes an object immediately if it was not assigned - we track them here,
// and if when this scope is exit their reference counter is 0, we delete them.
Object funcRet = BlockManager.getFunctionReturn();
Set<_BaseObject_> funcRetVals = null;
if (funcRet != null &&
(funcRet instanceof object ||
(funcRet.getClass().isArray() &&
object.class.isAssignableFrom(funcRet.getClass().getComponentType()))))
{
funcRetVals = Collections.newSetFromMap(new IdentityHashMap<>());
if (funcRet instanceof object)
{
object<?> obj = (object<?>) funcRet;
if (obj._isValid() && obj.isTracked())
{
funcRetVals.add(obj.ref);
}
}
else
{
for (int i = 0; i < Array.getLength(funcRet); i++)
{
object<?> obj = (object<?>) Array.get(funcRet, i);
if (obj != null && obj._isValid() && obj.isTracked())
{
funcRetVals.add(obj.ref);
}
}
}
}
for (_BaseObject_ obj : pending)
{
if (!obj.isTracked())
{
continue;
}
ObjectResource res = objects.get(obj);
if (res.getReferences() == 0)
{
if (funcRetVals == null || !funcRetVals.contains(obj))
{
res.deleteSilent();
}
else
{
// move this one level down
if (pendingAssigned.size() > 1)
{
pendingAssigned.pop();
pendingAssign(this, obj, false);
pendingAssigned.push(pending);
}
}
}
}
}
pendingAssigned.pop();
}
}
finally
{
inScopeFinished = prevScopeFinished;
}
}
/**
* Provides a notification that an external scope is about to be deleted.
* <p>
* All variables defined at this external program will have the reference counter decremented.
*/
@Override
public void scopeDeleted()
{
boolean prevScopeFinished = inScopeFinished;
inScopeFinished = true;
try
{
Object referent = pm.getProcessedProcedure();
if (referent == null ||
pm._isPersistentAndNotPendingDelete(referent) ||
(referent == pm._thisProcedure() && !tm.isExternalBlock()))
{
// if no referent or procedure is still persistent, do not clean up yet
return;
}
Set<Object> defs = extProgDefinitions.remove(referent);
if (defs != null)
{
decrement(defs);
}
}
finally
{
inScopeFinished = prevScopeFinished;
}
}
/**
* Get the {@link ScopeId} for the instance.
*
* @return {@link ScopeId#OBJECT_OPS}.
*/
@Override
public ScopeId getScopeId()
{
return ScopeId.OBJECT_OPS;
}
/**
* Decrement the references for all this objects.
*
* @param defs
* Scalar or array object variables.
*/
private void decrement(Set<Object> defs)
{
for (Object o : defs)
{
if (o instanceof object)
{
object<? extends _BaseObject_> obj = (object<?>) o;
if (!obj.isUnknown())
{
obj.decrement();
}
}
else if (o instanceof object[])
{
object<? extends _BaseObject_>[] objs = (object<?>[]) o;
for (object<? extends _BaseObject_> obj : objs)
{
if (!obj.isUnknown())
{
obj.decrement();
}
}
if (objs.length == 0)
{
// this was never resized, so remove its type
ArrayAssigner.removeObjectType((object[]) objs);
}
}
else
{
// TODO: error out?
}
}
}
}
}