Legacy4GLMethod.java
/*
** Module : Legacy4GLMethod.java
** Abstract : Implementation of the builtin class.
**
** Copyright (c) 2019-2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- -------------------------------Description--------------------------------
** 001 CA 20190526 First version, stubs taken by converting the skeleton using FWD.
** RFB 20191022 Added LegacyResource annotation
** 002 CA 20191024 Added method support levels and updated the class support level.
** 003 CA 20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy
** signature.
** ME 20211028 Added 'POLY' return data type for invoke.
** 004 ME 20230905 Implement remaining stubs methods.
** ME 20230918 Use function blocks for method that needs error handling, cache string representation.
** 005 CA 20231130 Do not use 'TypeFactory.object' in cases where the skeleton class is not instantiated via
** ObjectOps (like the reflection classes), as this will force pending ObjectOps scopeable
** registration, which never gets executed, as the legacy constructors are not used.
** 006 CA 20231106 Properly report DATASET/TABLE/-HANDLE parameters.
** 007 CA 20240314 'ablSignature' must be accessed only via its getter.
** 008 ES 20250523 Removed function call from the Legacy 4GL class.
*/
/*
** 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.oo.reflect;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.oo.lang.*;
import static com.goldencode.p2j.report.ReportConstants.*;
import static com.goldencode.p2j.util.InternalEntry.Type;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* Implementation of the Progress.Reflect.Method builtin class.
*/
@LegacyResource(resource = "Progress.Reflect.Method")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public class Legacy4GLMethod
extends BaseObject
{
private final LegacyClass originatingClass;
private final InternalEntry ie;
private LegacySignature ablSignature;
private String txtSig;
private object<? extends LegacyClass> declaringClass = new object<>(LegacyClass.class);
private logical isOverride = TypeFactory.logical(null);
private object<? extends DataType> returnType = new object<>(DataType.class);
private List<Parameter> _parameters = null;
/**
* Create a new legacy class object.
*
* @param cls
* The originating legacy class.
* @param javaMethod
* The method (reflection).
*
*/
public Legacy4GLMethod(LegacyClass cls, Method javaMethod)
{
this.originatingClass = cls;
this.ie = SourceNameMapper.buildInternalEntry(_getSignature(javaMethod), javaMethod);
}
/**
* Create a new legacy class object.
*
* @param cls
* The originating legacy class.
* @param internalEntry
* The InternalEntry.
*
*/
public Legacy4GLMethod(LegacyClass cls, InternalEntry internalEntry)
{
this.originatingClass = cls;
this.ie = internalEntry;
}
/**
* Check if the method has a valid LegacySignature (type is method).
* @param javaMethod the java method
* @return <code>true</code> if the method has a valid signature
*/
public static boolean is4GLMethod (Method javaMethod)
{
return _getSignature(javaMethod) != null;
}
/**
* Check if the method has a valid LegacySignature (type is method)
* and conforms to the specific flag selection criteria.
* @param javaMethod the java method
* @param flags the Flags enum to use as selection criteria
* @return <code>true</code> if the method has a valid signature for the input flags
*/
public static boolean is4GLMethod (Method javaMethod, Flags flags)
{
if (!is4GLMethod(javaMethod)
|| (Modifier.isStatic(javaMethod.getModifiers())
&& !flags.isFlagSet(Flags.static_).booleanValue())
|| (!Modifier.isStatic(javaMethod.getModifiers())
&& !flags.isFlagSet(Flags.instance).booleanValue()))
{
return false;
}
if (Modifier.isPublic(javaMethod.getModifiers()))
{
return flags.isFlagSet(Flags.public_).booleanValue();
}
else if (Modifier.isProtected(javaMethod.getModifiers()))
{
return flags.isFlagSet(Flags.protected_).booleanValue();
}
else if (Modifier.isPrivate(javaMethod.getModifiers()))
{
return flags.isFlagSet(Flags.private_).booleanValue();
}
// we might need to add a new attribute in LegacySignature if we need to differentiate between the two
return flags.isFlagSet(Flags.packageProtected).booleanValue()
|| flags.isFlagSet(Flags.packagePrivate).booleanValue();
}
@LegacySignature(returns = "OBJECT", qualified = "Progress.Reflect.AccessMode", type = Type.GETTER, name = "AccessMode")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public object<? extends AccessMode> getAccessMode()
{
return AccessMode.getEnum(ie.getMethod().getModifiers());
}
@LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Class", type = Type.GETTER, name = "DeclaringClass")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public object<? extends LegacyClass> getDeclaringClass()
{
if (declaringClass.isUnknown())
{
declaringClass.assign(ObjectOps.getLegacyClass((Class<? extends _BaseObject_>) ie.getMethod().getDeclaringClass()));
}
return declaringClass.duplicate();
}
@LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "IsAbstract")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public logical getIsAbstract()
{
return new logical(Modifier.isAbstract(ie.getMethod().getModifiers()));
}
@LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "IsFinal")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public logical getIsFinal()
{
return new logical(Modifier.isFinal(ie.getMethod().getModifiers()));
}
@LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "IsOverride")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public logical getIsOverride()
{
if (isOverride.isUnknown())
{
if (Modifier.isPrivate(ie.getMethod().getModifiers())
|| !originatingClass.getJavaClass().equals(ie.getMethod().getDeclaringClass())) {
isOverride.assign(false);
}
else
{
isOverride.assign(isOverride(ie.getMethod(), originatingClass.getJavaClass()));
}
}
return isOverride.duplicate();
}
@LegacySignature(returns = "LOGICAL", type = Type.GETTER, name = "IsStatic")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public logical getIsStatic()
{
return new logical(Modifier.isStatic(ie.getMethod().getModifiers()));
}
@LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Name")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public character getName()
{
return new character(_getSignature().name());
}
@LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "NumParameters")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public integer getNumParameters()
{
return new integer(_getSignature().parameters().length);
}
@LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetParameters", extent = -1, qualified = "progress.reflect.parameter")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public object<? extends Parameter>[] getParameters()
{
List<Parameter> params = _getParameters();
object<Parameter>[] parameters = TypeFactory.objectExtent(params.size(), Parameter.class, false);
for (int i = 0; i < params.size(); i++)
{
ArrayAssigner.assignSingle(parameters, i + 1, new object<> (params.get(i)));
}
return parameters;
}
@LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Class", type = Type.GETTER, name = "OriginatingClass")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public object<? extends LegacyClass> getOriginatingClass()
{
return new object<>(originatingClass);
}
@LegacySignature(returns = "INTEGER", type = Type.GETTER, name = "ReturnExtent")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public integer getReturnExtent()
{
int ext = _getSignature().extent();
switch (ext)
{
case SourceNameMapper.NO_EXTENT:
return new integer(0);
case SourceNameMapper.DYNAMIC_EXTENT:
return new integer();
default:
return new integer(ext);
}
}
@LegacySignature(returns = "OBJECT", qualified = "Progress.Reflect.DataType", type = Type.GETTER, name = "ReturnType")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public object<? extends DataType> getReturnType()
{
if (returnType.isUnknown())
{
returnType.assign(DataType.getEnum(new character(_getSignature().returns())));
}
return returnType.duplicate();
}
@LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "ReturnTypeName")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public character getReturnTypeName()
{
String qualified = _getSignature().qualified();
return new character(qualified != null && qualified.isEmpty() ? null : qualified);
}
@LegacySignature(type = Type.METHOD, name = "Invoke", returns = "BDT", parameters =
{
@LegacyParameter(name = "params", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
})
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public BaseDataType invoke(final object<? extends ParameterList> _params)
{
if (!getIsStatic().booleanValue())
{
String msg = String.format("Non-static method '%s' of class '%s' may not be invoked as if it were static",
getName().getValue(),
getDeclaringClass().ref().getTypeName().getValue());
ErrorManager.recordOrThrowError(15320, msg, false);
return BaseDataType.generateUnknown(returnType.getTypeName().getClass());
}
if (_params.isUnknown())
{
ErrorManager.recordOrThrowError(18353, "You did not supply a valid ParameterList to Invoke", false);
return BaseDataType.generateUnknown(returnType.getTypeName().getClass());
}
if (!_params.ref().isComplete())
{
ErrorManager.recordOrThrowError(15313, "All parameters in a ParameterList object must be intialized before using it in a NEW or Invoke method", false);
}
return _invoke(_params, originatingClass.getJavaClass());
}
@LegacySignature(type = Type.METHOD, name = "Invoke", returns = "BDT", parameters =
{
@LegacyParameter(name = "instance", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT"),
@LegacyParameter(name = "params", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
})
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public BaseDataType invoke(final object<? extends _BaseObject_> _instance, final object<? extends ParameterList> _params)
{
if (getIsStatic().booleanValue())
{
String msg = String.format("To invoke static method '%s' you should use the Invoke() overload that does not take and object instance parameter", getName().getValue());
ErrorManager.recordOrThrowError(15291, msg, false);
return BaseDataType.generateUnknown(returnType.getTypeName().getClass());
}
if (_params.isUnknown())
{
ErrorManager.recordOrThrowError(18353, "You did not supply a valid ParameterList to Invoke", false);
return BaseDataType.generateUnknown(returnType.getTypeName().getClass());
}
if (!_params.ref().isComplete())
{
ErrorManager.recordOrThrowError(15313, "All parameters in a ParameterList object must be intialized before using it in a NEW or Invoke method", false);
return BaseDataType.generateUnknown(returnType.getTypeName().getClass());
}
return _invoke(_params, _instance.ref());
}
@LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "ToString")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
public character toLegacyString()
{
return new character(toString());
}
/**
* Reports if the given instance is managed as part of the legacy 4GL object life cycle. This
* includes reference counting, linking in the SESSION object chain, managed construction and
* destruction.
*
* @return Always {@code false} for 4GL internals (class/reflection).
*/
@Override
public boolean isTracked()
{
return false;
}
@Override
public String toString()
{
if (txtSig != null)
{
return txtSig;
}
LegacySignature sig = _getSignature();
StringBuffer sb = new StringBuffer("METHOD ");
String ret = !sig.qualified().isEmpty() ? sig.qualified() : getReturnType().ref().toLegacyString().getValue().toUpperCase();
sb.append(getAccessMode().ref().toLegacyString().getValue().toUpperCase()).append(" ");
if (getIsOverride().booleanValue())
{
sb.append("OVERRIDE ");
}
if (!"VOID".equals(ret))
{
sb.append(ret).append(" ");
if (sig.extent() != SourceNameMapper.NO_EXTENT)
{
sb.append("EXTENT ");
if (sig.extent() != SourceNameMapper.DYNAMIC_EXTENT)
{
sb.append(sig.extent()).append(" ");
}
}
}
sb.append(sig.name()).append("(").append(
_getParameters().stream().map(Object::toString).collect(Collectors.joining(", ")))
.append(")");
txtSig = sb.toString();
return txtSig;
}
/**
* Build the list of method parameters (lazy load).
* @return The parameter list.
*/
private List<Parameter> _getParameters()
{
if (_parameters == null)
{
LegacyParameter[] params = _getSignature().parameters();
_parameters = new ArrayList<Parameter>(params.length);
for (int i = 0; i < params.length; i++)
{
_parameters.add(new Parameter(i, params[i], this.ie.getMethod()));
}
}
return _parameters;
}
/**
* Get the LegacySignature of current method.
*
* @return The LegacySignature of current method.
*/
private LegacySignature _getSignature() {
if (ablSignature == null)
{
ablSignature = _getSignature(ie.getMethod());
}
return ablSignature;
}
/**
* Get the LegacySignature annotation for the Java method.
*
* @param javaMethod
* The method.
* @return The annotation if present or null.
*/
private static LegacySignature _getSignature(Method javaMethod) {
if (javaMethod != null)
{
LegacySignature lsig = javaMethod.getAnnotation(LegacySignature.class);
if (lsig != null && lsig.type() == InternalEntry.Type.METHOD)
{
return lsig;
}
}
return null;
}
/**
* Check if the given method is an override for a method defined in it's super class(es).
*
* @param javaMethod
* The method.
* @param javaClass
* The super class to start searching from.
* @return True if the method is an override.
*/
private boolean isOverride (Method javaMethod, Class<?> javaClass) {
if (javaMethod != null && javaClass != null)
{
for (Method m : javaClass.getDeclaredMethods())
{
if (javaMethod.getName().equals(m.getName())
&& Modifier.isStatic(javaMethod.getModifiers()) == Modifier.isStatic(m.getModifiers())
&& !Modifier.isPrivate(m.getModifiers())
&& Arrays.equals(javaMethod.getParameters(), m.getParameters()))
{
return true;
}
}
return isOverride(javaMethod, javaClass.getSuperclass());
}
return false;
}
/**
* Invoke the current method.
*
* @param _params
* The parameter list.
* @param _instance
* The object instance or the originating class for static method.
* @return Any returned value, or unknown if the method is void.
*/
private BaseDataType _invoke(final object<? extends ParameterList> _params, final Object _instance)
{
ParameterList params = _params.ref();
Class<?> retType = ie.getMethod().getReturnType();
BaseDataType[] retVal = new BaseDataType[1];
Runnable task = () -> retVal[0] = (BaseDataType) ControlFlowOps.invokeLegacyMethod(ie,
retType == void.class || retType == Void.class,
_instance, originatingClass.getTypeName().getValue(), getName().getValue(), params.getModes(), params.getArgs());
// TODO: refactor so that the invoke is done in the method instead of the class itself
LegacyClass.invokeImpl(params, task);
return retVal[0];
}
}