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;
   }
}