# LegacyClass.java

    /*
    ** Module   : LegacyClass.java
    ** Abstract : Implementation of the Progress.Lang.Class builtin class.
    **
    ** Copyright (c) 2018-2025, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --------------------------------------Description---------------------------------------
    ** 001 CA  20181213 First version.
    ** 002 CA  20190219 Implemented the class methods and properties.
    ** 003 CA  20190509 Always use <? extends _BaseObject_>.
    ** 004 CA  20190812 First pass at invoke and new via 4GL reflection.
    ** 005 CA  20191023 Fixed the LegacySignature.name for isA method.
    ** 006 CA  20191024 Added method support levels and updated the class support level.
    ** 007 CA  20191124 Added stubs for GetPropertyValue.  Changed class conversion level to PARTIAL.
    **     CA  20191211 Instances of this class must exist as legacy 4GL objects (with ctor and execute methods).
    ** 008 CA  20200503 Added stubs for GetEnumNames, GetEnumValues, GetEnumValue, GetEnumName, IsEnum
    **                  and IsFlagsEnum.
    **     GES 20200519 Added protection against instantiation of legacy enum classes.  Added full implementation
    **                  for all enum methods.
    **     ME  20200423 Add missing methods, mainly reflection related.
    **     GES 20200528 Fixed enum error handling.
    **     GES 20200605 Fixed isValidEnum() processing.
    ** 009 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy signature.
    **     OM  20210225 Dropped duplicated error code.
    **     CA  20220304 An ERROR condition being raised from calling a legacy method mustbe morphed into a legacy
    **                  SysError exception, and not raise an ERROR condition.
    **     ME  20211028 Use 'POLY' data type for return/parameter on invoke/getPropertyValue/setPropertyValue.
    **     CA  20220513 Switched invoke() to use BaseDataType as return type, because FWD does not yet support
    **                  'extent' POLY returned values, and this conflicts with BlockManager.returnNormal()
    **                  parameter.
    **     CA  20220526 Added default legacy constructor method.
    **     CA  20220828 Implemented 'getInterfaces()' method.
    ** 010 CA  20230601 'getInterfaces' in OE does not guarantee the returned interface order, but experimenting
    **                  shows that it does a BFS walk of the hierarchy.  Also, fixed a bug in
    **                  'Utils.collectInterfaces', which returned only the directly implemented interfaces instead
    **                  from the full hierarchy.
    ** 011 CA  20230801 'invoke' must re-throw a LegacyErrorException thrown by the target.
    ** 012 ME  20230503 Reconvert skeleton and update method signatures, invoke returns Object (extent) (#6410).
    **     ME  20230905 Implement getMethod/getMethods, some clean-up on invokeImpl (unused arguments).
    **     ME  20230918 Use function block for `getMethod` method (error handling).
    ** 013 CA  20230802 Reset the OutputParameterAssigner after invoke finishes, as the parameters are copied
    **                  explicitly, without the OutputParameterAssigner support.
    ** 014 CA  20231208 POLY OO method invocations must use the var's declared type and not the runtime type.
    ** 015 SB  20240116 Implemented functionality for GetPropertyValue.
    ** 016 CA  20240708 Allow RETURN ERROR to propagate to the caller.
    ** 017 ICP 20250120 Used logical constants to leverage cached instances.
    ** 018 ES  20250515 Added runtime support for hasWidgetPool method.
    **     ES  20250521 Added runtime support for getVariables, getVariable, getProperties and getProperty
    **                  methods.
    **     ES  20250527 Cache class level properties and variables.
    */

    /*
    ** 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.lang;

    import java.io.*;
    import java.lang.reflect.*;
    import java.util.*;
    import java.util.logging.Level;

    import com.goldencode.p2j.oo.reflect.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.p2j.util.InternalEntry.Type;
    import com.goldencode.p2j.util.logging.CentralLogger;
    import com.goldencode.util.CaseInsensitiveHashMap;

    import static com.goldencode.p2j.report.ReportConstants.*;
    import static com.goldencode.p2j.util.BlockManager.*;

    /**
     * Implementation of the Progress.Lang.Class builtin class.
     */
    @LegacyResource(resource = "Progress.Lang.Class")
    @LegacyResourceSupport(supportLvl = CVT_LVL_PARTIAL|RT_LVL_PARTIAL)
    public class LegacyClass
    extends BaseObject
    {
       /** The legacy class name. */
       private final String typeName;

       /** The converted Java class. */
       private final Class<? extends _BaseObject_> cls;

       /**
        * Flag (when resolved) indicating if this or any super-class has static members or methods.
        */
       private Boolean hasStatics = null;

       /** Flag indicating that a class is annotated with LegacyWidgetPool annotation */
       private boolean hasWidgetPool = false;

       /** Map that caches the variables of the defined class */
       private Map<String, Variable> classVariables = new CaseInsensitiveHashMap<>();

       /** Map that caches the properties of the defined class */
       private Map<String, Property> classProperties = new CaseInsensitiveHashMap<>();

       /** Map that caches the methods of the defined class */
       private Map<Method, Legacy4GLMethod> classMethods = new CaseInsensitiveHashMap<>();

       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(LegacyClass.class.getName());

       /**
        * Create a new legacy class object.
        *
        * @param    typeName
        *           The qualified legacy class name.
        * @param    cls
        *           The converted type.
        */
       public LegacyClass(String typeName, Class<? extends _BaseObject_> cls)
       {
          this.typeName = typeName;
          this.cls = cls;
          this.hasWidgetPool = cls.isAnnotationPresent(LegacyWidgetPool.class);
       }

       /**
        * Resolve the static property with the given name.
        *
        * @param    propName
        *           The property name.
        *
        * @return   See above.
        */
       @LegacySignature(type = Type.METHOD, name = "GetPropertyValue", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "propName", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public BaseDataType getPropertyValue(character propName)
       {
          BaseDataType ret = null;
          ret = getPropertyValueImpl(null, propName, null);
          return ret;
       }

       /**
        * Resolve the static array property value, with the given index and name.
        * TODO: the index can be any given data type.
        *
        * @param    propName
        *           The property name.
        * @param    idx
        *           The index.
        *
        * @return   See above.
        */
       @LegacySignature(type = Type.METHOD, name = "GetPropertyValue", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "propName", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "idx", type = "BDT", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_PARTIAL|RT_LVL_STUB)
       public BaseDataType getPropertyValue(character propName, Object idx)
       {
          BaseDataType ret = null;
          ret = getPropertyValueImpl(null, propName, idx);
          return ret;
       }

       /**
        * Resolve the instance property value, with the given name.
        *
        * @param    instance
        *           The instance.
        * @param    propName
        *           The property name.
        *
        * @return   See above.
        */
       @LegacySignature(type = Type.METHOD, name = "GetPropertyValue", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "instance", type = "OBJECT", qualified = "Progress.Lang.Object", mode = "INPUT"),
          @LegacyParameter(name = "propName", type = "CHARACTER", mode = "INPUT"),
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public BaseDataType getPropertyValue(object<? extends _BaseObject_> instance, character propName)
       {
          BaseDataType ret = null;
          ret = getPropertyValueImpl(instance, propName, null);
          return ret;
       }

       /**
        * Resolve the instance array property value, with the given index and name.
        * TODO: the index can be any given data type.
        *
        * @param    instance
        *           The instance.
        * @param    propName
        *           The property name.
        * @param    idx
        *           The index.
        *
        * @return   See above.
        */
       @LegacySignature(type = Type.METHOD, name = "GetPropertyValue", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "instance", type = "OBJECT", qualified = "Progress.Lang.Object", mode = "INPUT"),
          @LegacyParameter(name = "propName", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "idx", type = "BDT", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_PARTIAL|RT_LVL_STUB)
       public BaseDataType getPropertyValue(object<? extends _BaseObject_> instance,
                                      character propName,
                                      Object idx)
       {
          BaseDataType ret = null;
          ret = getPropertyValueImpl(instance, propName, idx);
          return ret;
       }

       /**
        * Resolve the legacy class from the given name.
        *
        * @param    typeName
        *           The qualified legacy class name.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Class", type = Type.METHOD, name = "getClass", parameters =
       {
          @LegacyParameter(name = "type", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public static object<LegacyClass> getLegacyClass(character typeName)
       {
          return getLegacyClass(typeName.isUnknown() ? "" : typeName.toStringMessage());
       }

       public static object<? extends LegacyClass> getLegacyClass(object<? extends com.goldencode.p2j.oo.lang._BaseObject_> obj)
       {
          if (obj == null || !obj._isValid())
             return null;

          return LegacyClass.class.equals(obj.ref().getClass()) ? (object<LegacyClass>) obj : obj.ref().getLegacyClass();
       }

       /**
        * Resolve the legacy class from the given name.
        *
        * @param    typeName
        *           The qualified legacy class name.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Class", type = Type.METHOD, name = "getClass", parameters =
       {
          @LegacyParameter(name = "type", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public static object<LegacyClass> getLegacyClass(String typeName)
       {
          return new object(ObjectOps.getLegacyClass(typeName).ref());
       }

       /**
        * Obtains the package name of the object.
        *
        * @return   The package name.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "Package")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getPackage()
       {
          int dotIdx = typeName.indexOf('.');
          return character.of(dotIdx < 0 ? "" : typeName.substring(0, dotIdx));
       }

       /**
        * Obtains the parent class of the object.
        *
        * @return   The parent class.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Class", type = Type.GETTER, name = "SuperClass")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<LegacyClass> getSuperClass()
       {
          if (cls == BaseObject.class)
          {
             return new object<>();
          }

          return getLegacyClass(ObjectOps.getLegacyName((Class) cls.getSuperclass()));
       }

       /**
        * Obtains the fully qualified name (package and class) of the object.
        *
        * @return   The fully qualified type name.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.GETTER, name = "TypeName")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getTypeName()
       {
          return character.of(typeName);
       }

       /**
        * Returns a comma-separated text list of the names of all predefined enums in this legacy class, in
        * the order of their values.  For regular enums, this is a simple ascending sort.  For flag enums, it
        * is ascending order starting from zero through the positive values and then followed by any negative
        * values (also in ascending order).
        *
        * @return   The comma-separated text list of the predefined enum names.
        *
        * @throws   ErrorConditionException
        *           When called for a class that is not a subclass of {@code LegacyEnum} or {@code FlagsEnum}.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetEnumNames")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getEnumNames()
       {
          if (!isValidEnum())
          {
             return new character();
          }

          return LegacyEnum.getEnumNames((Class<? extends LegacyEnum>) cls);
       }

       /**
        * Returns a comma-separated text list of the values of all predefined enums in this legacy class, in
        * the order of their values.  For regular enums, this is a simple ascending sort.  For flag enums, it
        * is ascending order starting from zero through the positive values and then followed by any negative
        * values (also in ascending order).
        *
        * @return   The comma-separated text list of the predefined enum values.
        *
        * @throws   ErrorConditionException
        *           When called for a class that is not a subclass of {@code LegacyEnum} or {@code FlagsEnum}.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetEnumValues")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getEnumValues()
       {
          if (!isValidEnum())
          {
             return new character();
          }

          return LegacyEnum.getEnumValues((Class<? extends LegacyEnum>) cls);
       }

       /**
        * Obtain the value of the named enum.  For regular enums, the given name must be a single name that
        * matches exactly to a predefined enum name.  For flags enums, it can one name or a comma-separated
        * list of valid names, whose corresponding values are bitwise OR'd together.  Matching is
        * case-insensitive and leading/trailing whitespace is ignored. If any name is invalid, then unknown
        * value is returned.  For regular enums, a comma-separated list is an invalid name.
        *
        * @param    name
        *           The enum name to lookup.
        *
        * @return   The value found or unknown value if there is an invalid name.
        *
        * @throws   ErrorConditionException
        *           If the given name is unknown value.
        */
       @LegacySignature(returns = "INT64", type = Type.METHOD, name = "GetEnumValue", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public int64 getEnumValue(character name)
       {
          if (!isValidEnum())
          {
             return new int64();
          }

          return LegacyEnum.getEnumValue((Class<? extends LegacyEnum>) cls, name);
       }

       /**
        * Obtain the value of the named enum.  For regular enums, the given name must be a single name that
        * matches exactly to a predefined enum name.  For flags enums, it can one name or a comma-separated
        * list of valid names, whose corresponding values are bitwise OR'd together.  Matching is
        * case-insensitive and leading/trailing whitespace is ignored. If any name is invalid, then unknown
        * value is returned.  For regular enums, a comma-separated list is an invalid name.
        *
        * @param    name
        *           The enum name to lookup.
        *
        * @return   The value found or unknown value if there is an invalid name.
        *
        * @throws   ErrorConditionException
        *           If the given name is unknown value.
        */
       @LegacySignature(returns = "INT64", type = Type.METHOD, name = "GetEnumValue", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public int64 getEnumValue(String name)
       {
          return getEnumValue(new character(name));
       }

       /**
        * Obtain the name of the enum(s) specified by this value.  For regular enums, the given value must be
        * an exact match to a single predefined enum.  For flags enums, it can be a single enum's value or it
        * can be a bitset that matches more than one enum.  In the multiple flag enum case, it will only match
        * enum values which are full subsets of the given value (see {@code isFlagSet}).  If there is no exact
        * match or for flag enums there is no single enum that is a subset, then unknown value is returned.
        * <p>
        * If multiple enums match (flag enums case), then each of the names will be returned in a comma-separated
        * list.
        *
        * @param    value
        *           The enum value to lookup.
        *
        * @return   The name found or unknown value if there is an invalid value.
        *
        * @throws   ErrorConditionException
        *           If the given input value is the unknown value.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetEnumName", parameters =
       {
          @LegacyParameter(name = "value", type = "INT64", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getEnumName(int64 value)
       {
          if (!isValidEnum())
          {
             return new character();
          }

          return LegacyEnum.getEnumName((Class<? extends LegacyEnum>) cls, value);
       }

       /**
        * Obtain the name of the enum(s) specified by this value.  For regular enums, the given value must be
        * an exact match to a single predefined enum.  For flags enums, it can be a single enum's value or it
        * can be a bitset that matches more than one enum.  In the multiple flag enum case, it will only match
        * enum values which are full subsets of the given value (see {@code isFlagSet}).  If there is no exact
        * match or for flag enums there is no single enum that is a subset, then unknown value is returned.
        * <p>
        * If multiple enums match (flag enums case), then each of the names will be returned in a comma-separated
        * list.
        *
        * @param    value
        *           The enum value to lookup.
        *
        * @return   The name found or unknown value if there is an invalid value.
        *
        * @throws   ErrorConditionException
        *           If the given input value is the unknown value.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "GetEnumName", parameters =
       {
          @LegacyParameter(name = "value", type = "INT64", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public character getEnumName(long value)
       {
          return getEnumName(new int64(value));
       }

       /**
        * Report if this class is a subclass of {@code LegacyEnum}.
        *
        * @return   {@code true} if this is a subclass of {@code LegacyEnum}.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsEnum")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public logical isEnum()
       {
          return logical.of(LegacyEnum.class.isAssignableFrom(cls));
       }

       /**
        * Report if this class is a subclass of {@code FlagsEnum}.
        *
        * @return   {@code true} if this is a subclass of {@code FlagsEnum}.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsFlagsEnum")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public logical isFlagsEnum()
       {
          return logical.of(FlagsEnum.class.isAssignableFrom(cls));
       }

       /**
        * Get the converted {@link #cls type}.
        *
        * @return   See above.
        */
       public Class<? extends _BaseObject_> getType()
       {
          return cls;
       }

       /**
        * Check if this is an interface.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "isInterface")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical isInterface()
       {
          return logical.of(cls.isInterface());
       }

       /**
        * Check if this is a final class.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "isFinal")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical isFinal()
       {
          // SysError can be extended in application although is the super class of other 4GL errors
          return logical.of(SysError.class.equals(cls) || Modifier.isFinal(cls.getModifiers()));
       }

       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsGeneric")
       public logical isGeneric()
       {
          // only for .net objects
          return logical.of(false);
       }

       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsIndexed")
       public logical isIndexed()
       {
          // only for .net objects
          return logical.of(false);
       }

       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsSerializable")
       public logical isSerializable()
       {
          // TODO: 4GL serialization does not use an interface, some annotation maybe
          // a class can be non-serializable even if it's base class is
          return logical.of(cls.isAssignableFrom(Serializable.class));
       }

       /**
        * Check if this is an abstract class.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "isAbstract")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical isAbstract()
       {
          return logical.of(Modifier.isAbstract(cls.getModifiers()));
       }

       /**
        * Check if this or any of the super-classes has static members.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "hasStatics")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical hasStatics()
       {
          return logical.of(hasStaticsInt() || getSuperClass().ref().hasStaticsInt());
       }

       /**
        * Check if this class has a USE-WIDGET-POOL option.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "hasWidgetPool")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical hasWidgetPool()
       {
          return logical.of(hasWidgetPool);
       }

       /**
        * Check if this type is the same as or a sub-class of the given type.
        *
        * @param    type
        *           The type to check.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "isA", parameters =
       {
          @LegacyParameter(name = "type", type = "CHARACTER")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical isA(character type)
       {
          return isA(getLegacyClass(type));
       }

       public boolean isA(String type) {
          return _isA(getLegacyClass(type));
       }

       /**
        * Check if this type is the same as or a sub-class of the given type.
        *
        * @param    ref
        *           The type to check.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "isA", parameters =
       {
          @LegacyParameter(name = "ref", type = "OBJECT", mode = "INPUT", qualified = "Progress.Lang.Class")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public logical isA(object<? extends LegacyClass> ref)
       {
          return logical.of(_isA(ref));
       }

       public boolean _isA(object<? extends LegacyClass> ref)
       {
          if (ref.isUnknown())
          {
             return false;
          }
          LegacyClass obj = ref.ref();

          return obj.cls.isAssignableFrom(this.cls);
       }
       /**
        * Create a new instance from this class, using the implicit constructor.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Object", type = Type.METHOD, name = "new")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends _BaseObject_> new_()
       {
          // enums cannot be instantiated
          if (LegacyEnum.failEnumInstantiation(cls))
          {
             return null;
          }

          return ObjectOps.newInstanceInternal(cls, typeName, null);
       }

       /**
        * Create a new instance from this class, using the specified constructor.
        *
        * @return   See above.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Object", type = Type.METHOD, name = "new", parameters =
       {
          @LegacyParameter(name = "parms", type = "OBJECT", mode = "INPUT", qualified = "Progress.Lang.ParameterList")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_BASIC)
       public object<? extends _BaseObject_> new_(object<? extends ParameterList> parms)
       {
          // enums cannot be instantiated
          if (LegacyEnum.failEnumInstantiation(cls))
          {
             return null;
          }

          ParameterList opl = parms.ref();
          // TODO: validate params?
          String modes = opl.getModes();
          Object[] args = opl.getArgs();

          object<?>[] retVal = new object[1];
          Runnable task = () -> retVal[0] = ObjectOps.newInstanceInternal(cls, typeName, modes, args);

          invokeImpl(opl, task);

          return retVal[0];
       }

       /**
        * Invoke the specified static method in this class.
        *
        * @param     method
        *            The static method name.
        */
    //   @LegacySignature(type = Type.METHOD, name = "invoke", parameters =
    //   {
    //      @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT")
    //   })
       // DO NOT ANNOTATE THIS!
       public void invokeStandalone(character method)
       {
          ObjectOps.invokeStandalone(typeName, method);
       }

       /**
        * Invoke the specified static method in this class.
        *
        * @param     method
        *            The static method name.
        * @param     parms
        *            The parameter list.
        */
    //   @LegacySignature(type = Type.METHOD, name = "invoke", parameters =
    //   {
    //      @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
    //      @LegacyParameter(name = "parms", type = "OBJECT", mode = "INPUT", qualified = "Progress.Lang.ParameterList")
    //   })
       // DO NOT ANNOTATE THIS!
       public void invokeStandalone(character method, object<? extends ParameterList> parms)
       {
          ParameterList opl = parms.ref();
          // TODO: validate params object?

          String modes = opl.getModes();
          Object[] args = opl.getArgs();

          Runnable task = () -> ObjectOps.invokeStandalone(typeName, method, modes, args);

          invokeImpl(opl, task);
       }

       /**
        * Invoke the specified static method in this class.
        *
        * @param     method
        *            The static method name.
        *
        * @return    Any returned value, or unknown if the method is void.
        */
       @LegacySignature(type = Type.METHOD, name = "invoke", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public BaseDataType invoke(character method)
       {
          return ObjectOps.invoke(typeName, method);
       }

       /**
        * Invoke the specified instance method in the specified object.
        *
        * @param     ref
        *            The legacy object reference.
        * @param     method
        *            The method name.
        */
    //   @LegacySignature(type = Type.METHOD, name = "invoke", parameters =
    //   {
    //      @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
    //      @LegacyParameter(name = "parms", type = "OBJECT", mode = "INPUT", qualified = "Progress.Lang.Object")
    //   })
       // DO NOT ANNOTATE THIS!
       public void invokeStandalone(object<? extends _BaseObject_> ref, character method)
       {
          ObjectOps.invokeStandalone(ref, method);
       }

       /**
        * Invoke the specified instance method in the specified object.
        *
        * @param     ref
        *            The legacy object reference.
        * @param     method
        *            The method name.
        * @param     parms
        *            The parameter list.
        */
    //   @LegacySignature(type = Type.METHOD, name = "invoke", parameters =
    //   {
    //      @LegacyParameter(name = "ref",    type = "OBJECT",    mode = "INPUT", qualified = "Progress.Lang.Object"),
    //      @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
    //      @LegacyParameter(name = "parms",  type = "OBJECT"   , mode = "INPUT", qualified = "Progress.Lang.ParameterList")
    //   })
       // DO NOT ANNOTATE THIS!
       public void invokeStandalone(object<? extends _BaseObject_>  ref,
                                    character                       method,
                                    object<? extends ParameterList> parms)
       {
          ParameterList opl = parms.ref();

          // TODO: validate params object?

          String modes = opl.getModes();
          Object[] args = opl.getArgs();

          Runnable task = () -> ObjectOps.invokeStandalone(ref, method, modes, args);

          invokeImpl(opl, task);
       }

       /**
        * Invoke the specified static method in this class.
        *
        * @param     method
        *            The static method name.
        * @param     parms
        *            The parameter list.
        *
        * @return    Any returned value, or unknown if the method is void.
        */
       @LegacySignature(type = Type.METHOD, name = "invoke", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "parms",  type = "OBJECT", mode = "INPUT", qualified = "Progress.Lang.ParameterList")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_BASIC)
       public BaseDataType invoke(character method, object<? extends ParameterList> parms)
       {
          ParameterList opl = parms.ref();
          // TODO: validate params?
          String modes = opl.getModes();
          Object[] args = opl.getArgs();

          BaseDataType[] retVal = new BaseDataType[1];
          Runnable task = () -> retVal[0] = ObjectOps.invoke(typeName, method, modes, args);

          invokeImpl(opl, task);

          return retVal[0];
       }

       /**
        * Invoke the specified instance method in the specified object.
        *
        * @param     ref
        *            The legacy object reference.
        * @param     method
        *            The method name.
        *
        * @return    Any returned value, or unknown if the method is void.
        */
       @LegacySignature(type = Type.METHOD, name = "invoke", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "ref",    type = "OBJECT",    mode = "INPUT", qualified = "Progress.Lang.Object"),
          @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public BaseDataType invoke(object<? extends _BaseObject_> ref, character method)
       {
          return ObjectOps.invoke(ref, method);
       }

       /**
        * Invoke the specified instance method in the specified object.
        *
        * @param     ref
        *            The legacy object reference.
        * @param     method
        *            The method name.
        * @param     parms
        *            The parameter list.
        *
        * @return    Any returned value, or unknown if the method is void.
        */
       @LegacySignature(type = Type.METHOD, name = "invoke", returns = "BDT", parameters =
       {
          @LegacyParameter(name = "ref",    type = "OBJECT",    mode = "INPUT", qualified = "Progress.Lang.Object"),
          @LegacyParameter(name = "method", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "parms",  type = "OBJECT"   , mode = "INPUT", qualified = "Progress.Lang.ParameterList")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_BASIC)
       public BaseDataType invoke(object<? extends _BaseObject_>    ref,
                            character                       method,
                            object<? extends ParameterList> parms)
       {
          ParameterList opl = parms.ref();
          // TODO: validate params?
          String modes = opl.getModes();
          Object[] args = opl.getArgs();

          BaseDataType[] retVal = new BaseDataType[1];
          Runnable task = () -> retVal[0] = ObjectOps.invoke(ref, method, modes, args);

          invokeImpl(opl, task);

          return retVal[0];
       }

       /**
        * Perform the actual invocation.  This can be a void or non-void method call, or an object instantiation
        * via {@link #new_}.
        *
        * @param    opl
        *           The {@link ParameterList} reference.
        * @param    task
        *           The task to execute, to perform the actual invoke.
        */
       public static void invokeImpl(ParameterList opl, Runnable task)
       {
          boolean silent = ErrorManager.isSilent();
          if (!silent)
          {
             // assume a silent mode, so that errors can be captured and re-raised
             ErrorManager.setSilent(true);
          }

          boolean ran = false;
          try
          {
             CallParameter[] parameters = opl.getParameters();
             if (parameters != null)
             {
                for (CallParameter cparam : parameters)
                {
                   Object arg = cparam.getArgument();
                   if (arg instanceof AbstractExtentParameter)
                   {
                      AbstractParameter.getCurrentScope()
                                        .addExtentParameter((AbstractExtentParameter<?>) arg);
                   }
                   else if (arg instanceof AbstractSimpleParameter)
                   {
                      AbstractParameter.getCurrentScope()
                                       .addSimpleParameter((AbstractSimpleParameter) arg);
                   }
                }
             }

             task.run();
             ran = true;

             if (!silent && ErrorManager.isPendingError())
             {
                // raise a condition, this is from a RETURN ERROR - will be re-thrown in catch
                throw new ErrorConditionException("return error");
             }

             // now, we need to copy back the output and input-output arguments
             if (parameters != null)
             {
                for (CallParameter cparam : parameters)
                {
                   if (cparam.mode.output || cparam.mode.inputOutput)
                   {
                      if (!cparam.copyOutput())
                      {
                         // if a parameter copy failed, stop processing
                         break;
                      }
                   }
                }
             }
          }
          catch (LegacyErrorException lex)
          {
             throw lex;
          }
          catch (ErrorConditionException ece)
          {
             ErrorManager.setSilent(silent);

             boolean pendingError = ErrorManager.isPendingError();
             ErrorManager.clearPending();

             if (!silent && ErrorManager.mustThrowLegacyError())
             {
                // check if it must be morphed into a legacy error instance
                object<? extends SysError> err = SysError.newInstance(ece.getMessage(),
                                                                      ece.getProgressErrorCode());
                throw new LegacyErrorException(err);
             }

             if (ran || ControlFlowOps.hadInvalidArguments())
             {
                int[] nums =
                {
                   ece.getProgressErrorCode(),
                   5729,
                   5678
                };
                String[] texts =
                {
                   ece.getMessage(),
                   "Incompatible datatypes found during runtime conversion",
                   "Unable to do run-time conversion of datatypes"
                };

                ErrorManager.recordOrThrowError(nums, texts, false, true);
                return;
             }

             int[] nums =
             {
                ece.getProgressErrorCode(),
                5729,
                10089
             };
             String[] texts =
             {
                ece.getMessage(),
                "Incompatible datatypes found during runtime conversion",
                "Unable to update output parameter for dynamic INVOKE"
             };

             if (!pendingError)
             {
                ErrorManager.recordOrShowError(nums, texts, false, false, false, true);
             }
             else
             {
                ErrorManager.recordOrThrowError(nums, texts, false, true);
             }
          }
          finally
          {
             ErrorManager.setSilent(silent);

             OutputParameterAssigner opa = TransactionManager.deregisterOutputParameterAssigner();
             if (opa != null)
             {
                opa.abort();
             }
          }
       }

       /**
        * Check if this type has any static members or methods.
        *
        * @return   See above.
        */
       private boolean hasStaticsInt()
       {
          if (hasStatics != null)
          {
             return hasStatics;
          }

          hasStatics = false;

          // check if this has static members
          Field[] fields = cls.getDeclaredFields();
          for (Field f : fields)
          {
             if (Modifier.isStatic(f.getModifiers()))
             {
                hasStatics = true;
                return true;
             }
          }

          Method[] methods = cls.getDeclaredMethods();
          for (Method m : methods)
          {
             if (Modifier.isStatic(m.getModifiers()))
             {
                hasStatics = true;
                return true;
             }
          }

          return hasStatics;
       }

       /**
        * Check if the class of this instance is an enum and raise an error 18110 if the class is not an enum.
        *
        * @return   {@code true} if this is a valid enum class.
        *
        * @throws   ErrorConditionException
        *           When called for a class that is not a subclass of {@code LegacyEnum} or {@code FlagsEnum}.
        */
       private boolean isValidEnum()
       {
          // only the subclasses of LegacyEnum and FlagsEnum are allowed
          if (!LegacyEnum.class.isAssignableFrom(cls) || cls == LegacyEnum.class || cls == FlagsEnum.class)
          {
             String cname = ObjectOps.getLegacyName(cls);
             String msg   = String.format("Class '%s' is not an enum type", cname);

             ErrorManager.recordOrThrowError(18110, msg, false);
             return false;
          }

          return true;
       }

       @Override
       public logical legacyEquals(object<? extends _BaseObject_> other)
       {
          if (!other.isUnknown() && other.ref() instanceof LegacyClass)
             return new logical(typeName.equals(((LegacyClass) other.ref()).typeName));

          return super.legacyEquals(other);
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetConstructor", qualified = "progress.reflect.constructor", parameters =
       {
          @LegacyParameter(name = "parms", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends LegacyConstructor> getConstructor(final object<? extends com.goldencode.p2j.oo.lang.ParameterList> _parms)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getConstructor(ParameterList)");
          return new object<>();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetConstructor", qualified = "progress.reflect.constructor", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT"),
          @LegacyParameter(name = "parms", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends LegacyConstructor> getConstructor(final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags, final object<? extends com.goldencode.p2j.oo.lang.ParameterList> _parms)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getConstructor(Flags, ParameterList)");
          return new object<>();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetConstructors", extent = -1, qualified = "progress.reflect.constructor")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends LegacyConstructor>[] getConstructors()
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getConstructor()");
          return new object[0];
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetConstructors", extent = -1, qualified = "progress.reflect.constructor", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends LegacyConstructor>[] getConstructors(final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getConstructor(Flags)");
          return new object[0];
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetEvent", qualified = "progress.reflect.event", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends com.goldencode.p2j.oo.reflect.Event> getEvent(final character _name)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:GetEvent(character)");
          return new object();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetEvent", qualified = "progress.reflect.event", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends com.goldencode.p2j.oo.reflect.Event> getEvent(final character _name, final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:GetEvent(character, Flags)");
          return new object();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetEvents", extent = -1, qualified = "progress.reflect.event")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends com.goldencode.p2j.oo.reflect.Event>[] getEvents()
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getEvents()");
          return new object[0];
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetEvents", extent = -1, qualified = "progress.reflect.event", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public object<? extends com.goldencode.p2j.oo.reflect.Event>[] getEvents(final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:getEvents(Flags)");
          return new object[0];
       }

       /**
        * Resolve the interfaces implemented by this class or any super class or super-interface.
        * If no interface is implemented, it returns a zero-length array.
        * The order of the returned interfaces is undetermined.
        *
        * @return    The array of {@link LegacyClass} instances for each interface of this class.
        */
       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetInterfaces", extent = -1, qualified = "progress.lang.class")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends LegacyClass>[] getInterfaces()
       {
          Set<Class<?>> all = new LinkedHashSet<>();
          Utils.collectInterfaces(cls, all);
          // collect all legacy interfaces
          Iterator<Class<?>> iter = all.iterator();
          List<object<? extends LegacyClass>> ifaces = new ArrayList<>(all.size());
          while (iter.hasNext())
          {
             Class<?> type = iter.next();
             if (!_BaseObject_.class.isAssignableFrom(type) || type == _BaseObject_.class)
             {
                continue;
             }

             ifaces.add(ObjectOps.getLegacyClass((Class) type));
          }

          object<? extends LegacyClass>[] res = new object[ifaces.size()];
          if (!ifaces.isEmpty())
          {
             for (int i = 0; i < ifaces.size(); i++)
             {
                res[i] = ifaces.get(i);
             }
          }

          return res;
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetMethod", qualified = "progress.reflect.method", parameters =
       {
          @LegacyParameter(name = "method-name", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "parms", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends Legacy4GLMethod> getMethod(final character _methodName, final object<? extends com.goldencode.p2j.oo.lang.ParameterList> _parms)
       {
          return getMethod(_methodName, (object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_), _parms);
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetMethod", qualified = "progress.reflect.method", parameters =
       {
          @LegacyParameter(name = "method-name", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT"),
          @LegacyParameter(name = "params", type = "OBJECT", qualified = "progress.lang.parameterlist", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends Legacy4GLMethod> getMethod(final character _methodName, final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags, final object<? extends com.goldencode.p2j.oo.lang.ParameterList> _params)
       {
          // if flags is unknown - the invalid handle exception is thrown in 4GL
          Flags flags = _flags.ref();

          if (_params.isUnknown() || !_params.ref().isComplete())
          {
             ErrorManager.recordOrThrowError(15303, "An invalid or incomplete parameter list was specified when using reflection", false);
             return new object<>();
          }
          ParameterList params = _params.ref();
          String modes = params.getModes();
          Object[] args = params.getArgs();

          InternalEntry ie = ControlFlowOps.resolveLegacyEntry(false, cls, typeName, _methodName.getValue(),
                   flags.isFlagSet(Flags.static_).booleanValue(), modes, args);

          if (ie != null && Legacy4GLMethod.is4GLMethod(ie.getMethod(), flags))
          {
             Legacy4GLMethod legacy4GLMethod = classMethods.get(ie.getMethod());

             if (legacy4GLMethod == null)
             {
                legacy4GLMethod = new Legacy4GLMethod(this, ie);
                classMethods.put(ie.getMethod(), legacy4GLMethod);
             }

             return new object<>(legacy4GLMethod);
          }

          return new object<>();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetMethods", extent = -1, qualified = "progress.reflect.method")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends Legacy4GLMethod>[] getMethods()
       {
          return getMethods((object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetMethods", extent = -1, qualified = "progress.reflect.method", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends Legacy4GLMethod>[] getMethods(final object<? extends com.goldencode.p2j.oo.reflect.Flags> flags)
       {
          object<? extends Legacy4GLMethod>[] res = null;

          object<? extends LegacyClass> legacyClass = LegacyClass.getLegacyClass(typeName);
          // if flags is unknown - the invalid handle exception is thrown in 4GL
          Set<Method> methods = _getMethods(flags.ref(), legacyClass);
          res = new object[methods.size()];

          if (!methods.isEmpty())
          {
             int i = 0;
             for (Method method: methods)
             {
                Legacy4GLMethod legacy4GLMethod = classMethods.get(method);

                if (legacy4GLMethod == null)
                {
                   legacy4GLMethod = new Legacy4GLMethod(this, method);
                   classMethods.put(method, legacy4GLMethod);
                }

                res[i] = new object<> (legacy4GLMethod);
                i ++;
             }
          }

          return res;
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetProperties", extent = -1, qualified = "progress.reflect.property")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Property>[] getProperties()
       {
          return getProperties((object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetProperties", extent = -1, qualified = "progress.reflect.property", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Property>[] getProperties(final object<? extends com.goldencode.p2j.oo.reflect.Flags> flags)
       {
          object<? extends com.goldencode.p2j.oo.reflect.Property>[] res = null;

          object<? extends LegacyClass> legacyClass = LegacyClass.getLegacyClass(typeName);
          // if flags is unknown - the invalid handle exception is thrown in 4GL
          Set<Property> properties = _getProperties(flags.ref(), legacyClass);
          res = new object[properties.size()];

          if (!properties.isEmpty())
          {
             int i = 0;
             for (Property property: properties)
             {
                res[i] = new object<> (property);
                i ++;
             }
          }

          return res;
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetProperty", qualified = "progress.reflect.property", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Property> getProperty(final character _name)
       {
          return getProperty(_name, (object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetProperty", qualified = "progress.reflect.property", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Property> getProperty(final character name, final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          // if flags is unknown - the invalid handle exception is thrown in 4GL
          Flags flags = _flags.ref();

          Property property = classProperties.get(name.getValue());

          if (property != null)
          {
             Method method = getBasePropertyMethod(property.getter(), property.setter());
             if (checkMethodFlags(method, flags))
             {
                return new object<>(property);
             }
          }

          object<? extends LegacyClass> legacyClass = LegacyClass.getLegacyClass(typeName);
          property = _getProperty(name, flags, legacyClass);

          if (property != null)
          {
             classProperties.put(name.getValue(), property);
             return new object<>(property);
          }

          return new object<>();
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetVariables", extent = -1, qualified = "progress.reflect.variable")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Variable>[] getVariables()
       {
          return getVariables((object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetVariables", extent = -1, qualified = "progress.reflect.variable", parameters =
       {
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Variable>[] getVariables(final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          object<? extends Variable>[] res = null;

          object<? extends LegacyClass> legacyClass = LegacyClass.getLegacyClass(typeName);
          // if flags is unknown - the invalid handle exception is thrown in 4GL
          Set<Variable> variables = _getVariables(_flags.ref(), legacyClass);

          res = new object[variables.size()];

          if (!variables.isEmpty())
          {
             int i = 0;
             for (Variable variable: variables)
             {
                res[i] = new object<> (variable);
                i ++;
             }
          }

          return res;
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetVariable", qualified = "progress.reflect.variable", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Variable> getVariable(final character _name)
       {
          return getVariable(_name, (object<? extends Flags>) FlagsEnum.or(Flags.instance, Flags.public_));
       }

       @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "GetVariable", qualified = "progress.reflect.variable", parameters =
       {
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "flags", type = "OBJECT", qualified = "progress.reflect.flags", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public object<? extends com.goldencode.p2j.oo.reflect.Variable> getVariable(final character name, final object<? extends com.goldencode.p2j.oo.reflect.Flags> _flags)
       {
          Flags flags = _flags.ref();
          Variable variable = classVariables.get(name);

          if (variable != null)
          {
             Field field = variable.getField();
             if (checkFieldFlags(field, flags))
             {
                return new object<>(variable);
             }
          }

          object<? extends LegacyClass> legacyClass = LegacyClass.getLegacyClass(typeName);
          variable = _getVariable(name, flags, legacyClass);

          if (variable != null)
          {
             classVariables.put(name.getValue(), variable);
             return new object<>(variable);
          }

          return new object<>();
       }

       @LegacySignature(type = Type.METHOD, name = "SetPropertyValue", parameters =
       {
          @LegacyParameter(name = "pname", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "value", type = "BDT", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public void setPropertyValue(final character _pname, final Object _value)
       {
          object<? extends com.goldencode.p2j.oo.reflect.Flags> flags = new object<com.goldencode.p2j.oo.reflect.Flags>();

          flags.assign(com.goldencode.p2j.oo.reflect.Flags.public_);
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.static_));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.instance));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.private_));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.protected_));

          object<? extends com.goldencode.p2j.oo.reflect.Property> prop = getProperty(_pname, flags);
          Property property = prop.ref();

          if (property == null)
          {
             String msg = "Could not find property " + "'" + _pname.getValue() + "'" + " in class '" + typeName + "'";
             ErrorManager.recordOrThrowError(16548, msg, false);
             return;
          }

          if (!property.getIsStatic().booleanValue())
          {
             String msg = "Non-static member " + "'" + _pname.getValue() + "'" + " must be accessed via an object instance, "
                          + "not via a class name";
             ErrorManager.recordOrThrowError(14633, msg, false);
             return;
          }

          if (property.getAccessMode().ref().equals(AccessMode.private_.ref()))
          {
             String msg = "Property " + _pname.getValue() + " has a PRIVATE SET accessor so can be set only from "
                          + "within the class where it's defined";
             ErrorManager.recordOrThrowError(13855, msg, false);
             return;
          }

          property.set(_value);
       }

       @LegacySignature(type = Type.METHOD, name = "SetPropertyValue", parameters = {
                @LegacyParameter(name = "pname", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "index", type = "INTEGER", mode = "INPUT"),
                @LegacyParameter(name = "value", type = "BDT", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public void setPropertyValue(final character _pname, final integer _index,
                final Object _value)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:setPropertyValue(character,integer,bdt)");
       }

       @LegacySignature(type = Type.METHOD, name = "SetPropertyValue", parameters =
       {
          @LegacyParameter(name = "obj-ref", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT"),
          @LegacyParameter(name = "pname", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "value", type = "BDT", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       public void setPropertyValue(final object<? extends com.goldencode.p2j.oo.lang._BaseObject_> _objRef, final character _pname, final Object _value)
       {
          object<? extends com.goldencode.p2j.oo.reflect.Flags> flags = new object<com.goldencode.p2j.oo.reflect.Flags>();

          flags.assign(com.goldencode.p2j.oo.reflect.Flags.public_);
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.static_));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.instance));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.private_));
          flags.assign(flags.ref().setFlag(com.goldencode.p2j.oo.reflect.Flags.protected_));

          object<? extends com.goldencode.p2j.oo.reflect.Property> prop = getProperty(_pname, flags);
          Property property = prop.ref();

          if (property == null)
          {
             String msg = "Could not find property " + "'" + _pname.getValue() + "'" + " in class '" + typeName + "'";
             ErrorManager.recordOrThrowError(16548, msg, false);
             return;
          }

          if (property.getIsStatic().booleanValue())
          {
             String msg = "Static member '" + _pname.getValue() + " must be accessed via a class name, "
                           + "not via an object instance";
             ErrorManager.recordOrThrowError(14634, msg, false);
             return;
          }

          if (property.getAccessMode().ref().equals(AccessMode.private_.ref()))
          {
             String msg = "Property " + _pname.getValue() + " has a PRIVATE SET accessor so can be set only from "
                          + "within the class where it's defined";
             ErrorManager.recordOrThrowError(13855, msg, false);
             return;
          }

          property.set(_objRef, _value);
       }

       @LegacySignature(type = Type.METHOD, name = "SetPropertyValue", parameters = {
                @LegacyParameter(name = "obj-ref", type = "OBJECT", qualified = "progress.lang.object", mode = "INPUT"),
                @LegacyParameter(name = "pname", type = "CHARACTER", mode = "INPUT"),
                @LegacyParameter(name = "index", type = "INTEGER", mode = "INPUT"),
                @LegacyParameter(name = "value", type = "BDT", mode = "INPUT") })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_STUB)
       public void setPropertyValue(
                final object<? extends com.goldencode.p2j.oo.lang._BaseObject_> _objRef,
                final character _pname, final integer _index,
                final Object _value)
       {
          UnimplementedFeature.missing("Progress.Lang.Class:setPropertyValue(progress.lang.object,character,integer,bdt)");
       }

       public Class<? extends _BaseObject_> getJavaClass()
       {
          return cls;
       }

       /**
        * Get the list of 4gl methods(annotated) filtered as per given flags.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        * @param   clazz
        *          The Java class to collect methods from.
        * @return  The list of 4GL methods.
        */
       private Set<Method> _getMethods (Flags flags, object <? extends LegacyClass> legacyClass)
       {
          Set<Method> methods = new LinkedHashSet<>();
          if (legacyClass.isUnknown())
          {
             return methods;
          }
          Class<? extends _BaseObject_> cls = legacyClass.ref().getType();

          for (Method method : cls.getDeclaredMethods())
          {
             if (Legacy4GLMethod.is4GLMethod(method, flags))
             {
                methods.add(method);
             }
          }

          if (cls.getSuperclass() != null && !flags.isFlagSet(Flags.declaredOnly).booleanValue())
          {
             for (Method method : _getMethods(flags, legacyClass.ref().getSuperClass()))
             {
                if (!methods.stream().anyMatch(m -> m.getName().equals(method.getName()) && Arrays.equals(m.getParameters(), method.getParameters())))
                {
                   methods.add(method);
                }
             }
          }

          return methods;
       }

       /**
        * Get the list of 4gl variable(annotated) filtered as per given flags.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The list of 4GL variable.
        */
       private Set<Variable> _getVariables (Flags flags, object <? extends LegacyClass> legacyClass)
       {
          Set<Variable> variables = new LinkedHashSet<>();
          if (legacyClass.isUnknown())
          {
             return variables;
          }

          Class<? extends _BaseObject_> cls = legacyClass.ref().getType();

          for (Field field : cls.getDeclaredFields())
          {
             if (Variable.isVariable(field)  && checkFieldFlags(field, flags))
             {
                String name = field.getName();
                Variable variable = classVariables.get(name);

                if (variable == null)
                {
                   variable = new Variable(legacyClass.ref(), field);
                   classVariables.put(name, variable);
                }

                variables.add(variable);
             }
          }

          if (cls.getSuperclass() != null && !flags.isFlagSet(Flags.declaredOnly).booleanValue())
          {
             for (Variable varibale : _getVariables(flags, legacyClass.ref().getSuperClass()))
             {
                if (!variables.contains(varibale))
                {
                   variables.add(varibale);
                }
             }
          }

          return variables;
       }

       /**
        * Get the 4gl variable(annotated) with the given name filtered as per given flags.
        *
        * @param   name
        *          The name of the variable.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The 4GL variable.
        */
       private Variable _getVariable (character name, Flags flags, object <? extends LegacyClass> legacyClass)
       {
          if (legacyClass.isUnknown())
          {
             return null;
          }

          Class<? extends _BaseObject_> cls = legacyClass.ref().getType();

          for (Field field : cls.getDeclaredFields())
          {
             if (Variable.isVariable(field))
             {
                LegacySignature lsig = field.getDeclaredAnnotation(LegacySignature.class);
                if (lsig == null || !lsig.name().equalsIgnoreCase(name.getValue()))
                {
                   continue;
                }

                if (checkFieldFlags(field, flags))
                {
                   Variable variable = classVariables.get(lsig.name());

                   if (variable == null)
                   {
                      variable = new Variable(legacyClass.ref(), field);
                      classVariables.put(lsig.name(), variable);
                   }

                   return variable;
                }
             }
          }

          if (cls.getSuperclass() != null &&
              cls.getSuperclass() != Object.class &&
              !flags.isFlagSet(Flags.declaredOnly).booleanValue())
          {
             return _getVariable(name, flags, legacyClass.ref().getSuperClass());
          }

          return null;
       }

       /**
        * Get the list of 4gl properties(annotated) filtered as per given flags.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The list of 4GL variable.
        */
       private Set<Property> _getProperties (Flags flags, object <? extends LegacyClass> legacyClass)
       {
          Set<Property> properties = new LinkedHashSet<>();
          if (legacyClass.isUnknown())
          {
             return properties;
          }

          Class<? extends _BaseObject_> cls = legacyClass.ref().getType();

          Map<String, Method> getters = getters(cls);
          Map<String, Method> setters = setters(cls);

          for (Field field : cls.getDeclaredFields())
          {
             if (Property.isProperty(field))
             {
                LegacySignature lsig = field.getDeclaredAnnotation(LegacySignature.class);

                String propertyName = lsig.name();
                Method getter = getters.remove(propertyName);
                Method setter = setters.remove(propertyName);

                Method inferedMethod = getBasePropertyMethod(getter, setter);
                if (checkMethodFlags (inferedMethod, flags))
                {
                   Property property = classProperties.get(propertyName);

                   if (property == null)
                   {
                      property = new Property(legacyClass.ref(), field, getter, setter);
                      classProperties.put(propertyName, property);
                   }

                   properties.add(property);
                }
             }
          }

          for (String property: getters.keySet())
          {
             Method getter = getters.get(property);
             Method setter = setters.remove(property);

             Method inferedMethod = getBasePropertyMethod(getter, setter);
             LegacySignature lsig = inferedMethod.getDeclaredAnnotation(LegacySignature.class);
             if (checkMethodFlags (inferedMethod, flags))
             {
                Property existingProperty = classProperties.get(lsig.name());

                if (existingProperty == null)
                {
                   existingProperty = new Property(legacyClass.ref(), null, getter, setter);
                   classProperties.put(lsig.name(), existingProperty);
                }

                properties.add(existingProperty);
             }
          }

          for (String property: setters.keySet())
          {
             Method setter = setters.get(property);
             Method getter = getters.remove(property);

             Method inferedMethod = getBasePropertyMethod(getter, setter);
             LegacySignature lsig = inferedMethod.getDeclaredAnnotation(LegacySignature.class);
             if (checkMethodFlags (inferedMethod, flags))
             {
                Property existingProperty = classProperties.get(lsig.name());

                if (existingProperty == null)
                {
                   existingProperty = new Property(legacyClass.ref(), null, getter, setter);
                   classProperties.put(lsig.name(), existingProperty);
                }

                properties.add(new Property(this, null, getter, setter));
             }
          }

          if (cls.getSuperclass() != null && !flags.isFlagSet(Flags.declaredOnly).booleanValue())
          {
             for (Property property : _getProperties(flags, legacyClass.ref().getSuperClass()))
             {
                if (!properties.contains(property))
                {
                   properties.add(property);
                }
             }
          }

          return properties;
       }

       /**
        * Get the 4gl variable(annotated) with the given name filtered as per given flags.
        *
        * @param   name
        *          The name of the variable.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The 4GL variable.
        */
       Property _getProperty (character name, Flags flags, object <? extends LegacyClass> legacyClass)
       {
          if (legacyClass.isUnknown())
          {
             return null;
          }

          Class<? extends _BaseObject_> cls = legacyClass.ref().getType();

          Map<String, Method> getters = getters(cls);
          Map<String, Method> setters = setters(cls);

          for (Field field : cls.getDeclaredFields())
          {
             if (Property.isProperty(field))
             {
                LegacySignature lsig = field.getDeclaredAnnotation(LegacySignature.class);
                if (lsig == null || !lsig.name().equalsIgnoreCase(name.getValue()))
                {
                   continue;
                }

                String propertyName = lsig.name();
                Method getter = getters.remove(propertyName);
                Method setter = setters.remove(propertyName);

                Method inferedMethod = getBasePropertyMethod(getter, setter);
                if (checkMethodFlags (inferedMethod, flags))
                {
                   Property property = classProperties.get(lsig.name());

                   if (property == null)
                   {
                      property = new Property(legacyClass.ref(), field, getter, setter);
                      classProperties.put(lsig.name(), property);
                   }

                   return property;
                }
             }
          }

          for (String property: getters.keySet())
          {
             Method getter = getters.get(property);
             Method setter = setters.remove(property);

             LegacySignature lsig = getter.getDeclaredAnnotation(LegacySignature.class);
             if (lsig == null || !lsig.name().equals(name.getValue()))
             {
                continue;
             }

             Method inferedMethod = getBasePropertyMethod(getter, setter);
             if (checkMethodFlags (inferedMethod, flags))
             {
                Property existingProperty = classProperties.get(lsig.name());

                if (existingProperty == null)
                {
                   existingProperty = new Property(legacyClass.ref(), null, getter, setter);
                   classProperties.put(lsig.name(), existingProperty);
                }

                return existingProperty;
             }
          }

          for (String property: setters.keySet())
          {
             Method setter = setters.get(property);
             Method getter = getters.remove(property);

             LegacySignature lsig = setter.getDeclaredAnnotation(LegacySignature.class);
             if (lsig == null || !lsig.name().equals(name.getValue()))
             {
                continue;
             }

             Method inferedMethod = getBasePropertyMethod(getter, setter);
             if (checkMethodFlags (inferedMethod, flags))
             {
                Property existingProperty = classProperties.get(lsig.name());

                if (existingProperty == null)
                {
                   existingProperty = new Property(legacyClass.ref(), null, getter, setter);
                   classProperties.put(lsig.name(), existingProperty);
                }

                return existingProperty;
             }
          }

          if (cls.getSuperclass() != null &&
                   cls.getSuperclass() != Object.class &&
              !flags.isFlagSet(Flags.declaredOnly).booleanValue())
          {
             return _getProperty(name, flags, legacyClass.ref().getSuperClass());
          }

          return null;
       }

       /**
        * Get the map of 4gl getters (annotated) with name of the property as key.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The list of 4GL variable.
        */
       private Map<String, Method> getters(Class<? extends _BaseObject_> clazz)
       {
          Map<String, Method> getters = new CaseInsensitiveHashMap<>();
          Map<String, List<InternalEntry>> clsGetters = SourceNameMapper.getGetters(clazz);

          for (String name: clsGetters.keySet())
          {
             List<InternalEntry> internalEntries = clsGetters.get(name);

             InternalEntry ie = null;
             int internalEntriesSize = internalEntries.size();

             switch (internalEntriesSize)
             {
                case (1):
                   ie = internalEntries.get(0);
                   break;
                case (2):
                   ie = internalEntries.get(0);
                   break;
                default:
                   LOG.log(Level.SEVERE, "More than 2 GETTERS received, expected max 2.");
                   ie = internalEntries.get(0);
                   break;
             }

             Method getter = ie.getMethod();

             getters.put(name, getter);
          }

          return getters;
       }

       /**
        * Get the map of 4gl setters (annotated) with name of the property as key.
        *
        * @param   flags
        *          The flags enum to filter the returned methods.
        *
        * @param   clazz
        *          The Java class to collect methods from.
        *
        * @return  The list of 4GL variable.
        */
       private Map<String, Method> setters(Class<? extends _BaseObject_> clazz)
       {
          Map<String, Method> setters = new CaseInsensitiveHashMap<>();
          Map<String, List<InternalEntry>> clsSetters = SourceNameMapper.getSetters(clazz);

          for (String name: clsSetters.keySet())
          {
             List<InternalEntry> internalEntries = clsSetters.get(name);

             InternalEntry ie = null;
             int internalEntriesSize = internalEntries.size();

             switch (internalEntriesSize)
             {
                case (1):
                   ie = internalEntries.get(0);
                   break;
                case (2):
                   ie = internalEntries.get(0);
                   break;
                default:
                   LOG.log(Level.SEVERE, "More than 2 GETTERS received, expected max 2.");
                   ie = internalEntries.get(0);
                   break;
             }

             Method setter = ie.getMethod();

             setters.put(name, setter);
          }

          return setters;
       }

       /**
        * Get the method with lower access mode between getter / setter.
        *
        * @param   getter
        *          The getter java method.
        *
        * @param   setter
        *          The setter java method.
        *
        * @return  The method with lower access mode.
        */
       public Method getBasePropertyMethod(Method getter, Method setter)
       {
          object<? extends Flags> getterModeFlag = getter != null ? Property.getAccesMode(getter) :
                                                                    new object<>(Flags.private_);
          object<? extends Flags> setterModeFlag = setter != null ? Property.getAccesMode(setter) :
                                                                    new object<>(Flags.private_);

          Method inferedMethod = null;

          if (getterModeFlag.compareTo(setterModeFlag) <= 0)
          {
             inferedMethod = getter != null ? getter : setter;
          }
          else
          {
             inferedMethod = setter != null ? setter : getter;
          }

          return inferedMethod;
       }

       /**
        * Check if the java method 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 boolean checkMethodFlags (Method method, Flags flags)
       {
          if ((Modifier.isStatic(method.getModifiers()) && !flags.isFlagSet(Flags.static_).booleanValue()) ||
              (!Modifier.isStatic(method.getModifiers()) && !flags.isFlagSet(Flags.instance).booleanValue()))
          {
             return false;
          }

          if (Modifier.isPublic(method.getModifiers()))
          {
             return flags.isFlagSet(Flags.public_).booleanValue();
          }
          else if (Modifier.isProtected(method.getModifiers()))
          {
             return flags.isFlagSet(Flags.protected_).booleanValue();
          }
          else if (Modifier.isPrivate(method.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();
       }

       /**
        * Check if the java field conforms to the specific flag selection criteria.
        *
        * @param javaField
        *        the java field.
        * @param flags
        *        the Flags enum to use as selection criteria.
        *
        * @return <code>true</code> if the field has a valid signature for the input flags
        */
       public static boolean checkFieldFlags (Field field, Flags flags)
       {
          if ((Modifier.isStatic(field.getModifiers()) && !flags.isFlagSet(Flags.static_).booleanValue()) ||
              (!Modifier.isStatic(field.getModifiers()) && !flags.isFlagSet(Flags.instance).booleanValue()))
          {
             return false;
          }

          if (Modifier.isPublic(field.getModifiers()))
          {
             return flags.isFlagSet(Flags.public_).booleanValue();
          }
          else if (Modifier.isProtected(field.getModifiers()))
          {
             return flags.isFlagSet(Flags.protected_).booleanValue();
          }
          else if (Modifier.isPrivate(field.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();
       }

       /**
        * Helper function that gets the internal entry which contains the GET method for a given
        * property name of a class instance. All GET methods are also retrieved from the super-classes of
        * the instance, in order to retrieve inherited properties, including Next-Sibling and Prev-Sibling.
        * For EXTENT type properties the indexed getter is returned.
        *
        * @param   instance
        *          The instance.
        * @param   propName
        *          The property name.
        * @param   idx
        *          The index.
        * @return  The internal entry which contains the GET method.
        */
       private InternalEntry getGetterInternalEntry(object<? extends _BaseObject_> instance, character propName, Object idx)
       {
          Map<String, List<InternalEntry>> clsGetters = SourceNameMapper.getGetters(cls);
          Map<String, List<InternalEntry>> clsSetters = SourceNameMapper.getSetters(cls);
          Class<?> superClass = cls.getSuperclass();
          while (superClass != Object.class)
          {
             Class<? extends _BaseObject_> BOSuperClass = (Class<? extends _BaseObject_>) superClass;

             Map<String, List<InternalEntry>> clsSuperGetters = SourceNameMapper.getGetters(BOSuperClass);
             clsGetters.putAll(clsSuperGetters);

             Map<String, List<InternalEntry>> clsSuperSetters = SourceNameMapper.getSetters(BOSuperClass);
             clsSetters.putAll(clsSuperSetters);

             superClass = superClass.getSuperclass();
          }

          String propNameLC = propName.toJavaType().toLowerCase();
          List<InternalEntry> internalEntries = clsGetters.get(propNameLC);
          InternalEntry ie;
          if (internalEntries == null || internalEntries.isEmpty())
          {
             List<InternalEntry> ieSetter = clsSetters.get(propNameLC);
             StringBuilder sb = new StringBuilder();
             if (ieSetter != null && !ieSetter.isEmpty())
             {
                sb.append("Property ").append("'").append(propName.toJavaType()).append("'")
                  .append(" in Class ").append("'").append(typeName).append("'")
                  .append(" does not allow read access");
                ErrorManager.recordOrThrowError(13822, sb.toString());
             }
             else
             {
                sb.append("Could not find property ").append("'").append(propName.toJavaType()).append("'")
                  .append(" in class ").append("'").append(typeName).append("'");
                ErrorManager.recordOrThrowError(16548, sb.toString());
             }

             // this shouldn't be reached as an error is thrown already
             return null;
          }

          int internalEntriesSize = internalEntries.size();
          int extentSize = -1;
          //assumption is that in case of an EXTENT property, the first getter is the indexed getter
          //and the second one is the bulk getter
          switch (internalEntriesSize)
          {
             case (1):
                ie = internalEntries.get(0);
                break;
             case (2):
                InternalEntry bulkGetter = internalEntries.get(1);
                if (idx == null)
                {
                   ie = bulkGetter;
                }
                else
                {
                   ie = internalEntries.get(0);
                }
                extentSize = bulkGetter.getExtent();
                break;
             default:
                LOG.log(Level.SEVERE, "More than 2 GETTERS received, expected max 2.");
                ie = internalEntries.get(0);
                break;
          }

          int index = -1;
          if (idx != null)
          {
             //idx is not null for non-extent type, invalid
             if (extentSize == -1)
             {
                StringBuilder sb = new StringBuilder();
                sb.append("Invalid runtime use of indeterminate extent. ")
                  .append("It must be set to a fixed dimension");
                ErrorManager.recordOrThrowError(11387, sb.toString());
             }
             if (!(idx instanceof NumberType))
             {
                //TODO: Conversion to integer value of given data type
                LOG.log(Level.SEVERE, "Index data type for non-numerical type is not implemented.");
             }

             NumberType indexNT = (NumberType) idx;
             index = indexNT.intValue();

             if (index < 1)
             {
                StringBuilder sb = new StringBuilder();
                sb.append("Array subscript ").append(index).append("is out of range");
                ErrorManager.recordOrThrowError(26, sb.toString());
             }

             if (index > extentSize)
             {
                StringBuilder sb = new StringBuilder();
                sb.append("Array subscript ").append(index)
                  .append(" is out of range. The indeterminate extent is fixed to a ")
                  .append("dimension of ").append(extentSize);
                ErrorManager.recordOrThrowError(11388, sb.toString());
             }
          }

          Method getMethod = ie.getMethod();
          int getModifier = getMethod.getModifiers();

          if (Modifier.isPrivate(getModifier))
          {
             /* TODO: deny access from "outside" for private entries
             StringBuilder sb = new StringBuilder();
             sb.append("Property ")
               .append(propName.toJavaType())
               .append(" has a PRIVATE GET accessor so can be read ")
               .append("only from within the class where it's defined");
             ErrorManager.recordOrShowError(13833, sb.toString(), true);
              */

             if (LOG.isLoggable(Level.WARNING))
             {
                StringBuilder sb = new StringBuilder();
                sb.append("The GET method for ").append("'").append(propName.toJavaType()).append("' is PRIVATE")
                  .append("but the property was still retrieved ").append("without checking call place. TODO:")
                  .append(" implement call place checking.");
                LOG.log(Level.WARNING, sb.toString());
             }

             getMethod.setAccessible(true);
          }

          if (Modifier.isStatic(getModifier) && instance != null)
          {
             StringBuilder sb = new StringBuilder();
             sb.append("Static member ").append("'").append(propName.toJavaType()).append("'")
               .append(" must be accessed via a class name, not via")
               .append(" an object instance");
             ErrorManager.recordOrThrowError(14634, sb.toString(), true);
          }
          else if (!Modifier.isStatic(getModifier) && instance == null)
          {
             StringBuilder sb = new StringBuilder();
             sb.append("Non-static member ").append("'").append(propName.toJavaType()).append("'")
               .append(" must be accessed via an object instance, not via ")
               .append("a class name");
             ErrorManager.recordOrShowError(14633, sb.toString(), true);
          }

          return ie;
       }

       /**
        * Helper function used to handle GetPropertyValue, which returns a class property
        * based on the property's name. The function gets the InternalEntry which
        * holds the GET method of the converted 4GL class. The GET method is then invoked in order to obtain
        * the property.
        *
        * @param   instance
        *          The instance. It is passed as null when called by the static GetPropertyValue overloads.
        * @param   propName
        *          The name of the property.
        * @param   idx
        *          The index parameter. It is always null when called with the non indexed overload of
        *          GetPropertyValue.
        * @return  The value of the property.
        */
       private BaseDataType getPropertyValueImpl(object<? extends _BaseObject_> instance,
                                                 character propName,
                                                 Object idx)
       {
          BaseDataType ret = null;
          try
          {
             propName = (character) propName.val();
             if (instance != null)
             {
                if (!instance._isValid())
                {
                   String errorMsg = "Invalid handle. Not initialized or points to a deleted object";
                   ErrorManager.recordOrThrowError(3135, errorMsg, true);
                }

                if (instance.isUnknown())
                {
                   String errorMsg = "Invalid object reference passed to PLC:Get/SetPropertyValue";
                   ErrorManager.recordOrThrowError(16580, errorMsg, true);
                }
             }

             if (propName.isUnknown())
             {
                String errorMsg = "Invalid property name passed to PLC:Get/SetPropertyValue";
                ErrorManager.recordOrThrowError(16581, errorMsg, true);
             }

             InternalEntry ie = getGetterInternalEntry(instance, propName, idx);
             if (ie == null)
             {
                throw new IllegalStateException("Internal entry was not found for : " + propName);
             }

             Method getMethod = ie.getMethod();

             Object invokeRet;

             NumberType indexNT = (NumberType) idx;

             if (instance != null)
             {
                //GET is for a non-EXTENT type. assumption is only the indexed getter requires a parameter
                if (getMethod.getParameterCount() == 0)
                {
                   invokeRet =  getMethod.invoke(instance.ref());
                }
                else
                {
                   invokeRet = getMethod.invoke(instance.ref(), new integer(indexNT.intValue()));
                }
             }
             else
             {
                if (getMethod.getParameterCount() == 0)
                {
                   invokeRet = getMethod.invoke(null);
                }
                else
                {
                   invokeRet = getMethod.invoke(null, new integer(indexNT.intValue()));
                }
             }

             if (invokeRet instanceof BaseDataType)
             {
                ret = (BaseDataType) invokeRet;
             }
             else
             //invoked the bulk getter
             if (invokeRet instanceof BaseDataType[])
             {
                ret = new unknown();
                ErrorManager.recordOrThrowError(-567, "Return is of type EXTENT for GetPropertyValue which is not implemented.");
             }
             else
             {
                LOG.log(Level.SEVERE, "Unrecognized property data type for GetPropertyValue.");
             }

          }
          catch (InvocationTargetException exc)
          {
             if (LOG.isLoggable(Level.SEVERE))
             {
                StringBuilder sb = new StringBuilder();
                sb.append("Exception while attempting to get the property value: ").append(propName);
                LOG.log(Level.SEVERE, sb.toString(), exc);
             }
          }
          catch (IllegalAccessException exc)
          {
             if (LOG.isLoggable(Level.SEVERE))
             {
                StringBuilder sb = new StringBuilder();
                sb.append("Unexpected IllegalAccessException while trying to invoke GET method for property ")
                  .append(propName).append(": ").append(exc.toString());
                LOG.log(Level.SEVERE, sb.toString(), exc);
             }
          }

          return ret;
       }
    }