# LegacyEnum.java

    /*
    ** Module   : LegacyEnum.java
    ** Abstract : Implementation of the Progress.Lang.Enum builtin class.
    **
    ** Copyright (c) 2020-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- -------------------------------Description--------------------------------
    ** 001 ME  20200312 First version.
    ** 002 ME  20200414 Add base implementation for getEnum methods for default error handling.
    ** 003 ME  20200421 Use annotations to build the list of enums.
    **     CA  20200429 Renamed from Enum to LegacyEnum.  Added APIs required by the generated,
    **                  converted code.
    **     GES 20200429 Early rework to new enums approach.
    **     GES 20200507 Completed first working version.
    **     GES 20200603 Added legacy class registration.
    **     GES 20200605 Fixed instance initialization and some lookup methods.
    ** 004 CA  20200927 Use IdentityHashMap instead of plain map when the key is a Class.
    ** 005 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy
    **                  signature.
    **     TJD 20220504 Java 11 compatibility minor changes
    **     VVT 20221221 Serialization support added (see #4658).
    ** 006 CA  20230321 Synchronized the 'registry' access.
    ** 007 CA  20230731 Legacy ENUM is stored in a EnumObject instance - this ensures that when they are created,
    **                  no context is saved for them, as they are JVM-wide constants.
    ** 008 ME  20230905 Remove prev/next sibling overrides, internal objects handled in BaseObject.
    */

    /*
     ** 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 static com.goldencode.p2j.report.ReportConstants.*;
    import static com.goldencode.p2j.util.InternalEntry.Type;

    import java.io.*;
    import java.util.*;
    import java.util.stream.*;
    import java.util.function.*;
    import com.goldencode.p2j.util.*;
    import com.goldencode.util.*;

    /**
     * Implementation of the Progress.Lang.Enum builtin class.
     */
    @LegacyResource(resource = "Progress.Lang.Enum")
    @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
    public class LegacyEnum
    extends BaseObject
    implements Comparable<LegacyEnum>,
               Externalizable
    {
       /**
        * Default constructor. Required for de-serialization.
        */
       public LegacyEnum()
       {
       }

       /** Maps enum classes to their control record. */
       private static final Map<Class<?>, EnumControlRecord> registry = new IdentityHashMap<>();

       /** ToObject error message specification. */
       private static final String TO_SPEC = "Invalid value specified for parameter '%s' " +
                                             "of method \"ToObject\"";

       /** The case-preserved legacy name for the enum. */
       protected String name;

       /** The 64-bit enum value. */
       protected long value;

       /** Override for the symbol name in cases where it is different from the enum name. */
       private String symname = null;

       static
       {
          ObjectOps.registerClass("Progress.Lang.Enum", LegacyEnum.class);
       }

       /**
        * Construct an immutable enum instance with the given state.
        *
        * @param    name
        *           The case-preserved legacy name for the enum.
        * @param    value
        *           The 64-bit enum value.
        */
       protected LegacyEnum(String name, long value)
       {
          this.name = name;
          this.value = value;
       }

       /**
        * Return the enum instance of the given type which is associated with the provided name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cname
        *           The name of the enum class to process.  Must not be {@code null} or unknown.
        * @param    name
        *           The name of the enum to return.  If the enum type is a flags enum, the value may
        *           be a comma-separated list of enum names to return as a multi-bit enum. Comma
        *           separated lists cannot be used with regular enums. Must not be {@code null} or
        *           unknown.
        *
        * @return   The enum instance with the provided name(s).
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Enum", type = Type.METHOD, name = "ToObject", parameters =
       {
          @LegacyParameter(name = "cname", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "name", type = "CHARACTER", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static <T extends LegacyEnum> object<T> toObject(character cname, character name)
       {
          Class<T> cls = findClass(cname,
                                   LegacyEnum::generateInvalidTypeTO,
                                   LegacyEnum::generateFailedToFindEnumType);

          if (cls == null)
          {
             return new object<T>();
          }

          return lookupWorker(cls, name, LegacyEnum::generateInvalidValueTO);
       }

       /**
        * Return the enum instance of the given type which is associated with the provided value.
        *
        * @param    <T>
        *           The enum type.
        * @param    cname
        *           The name of the enum class to process.  Must not be {@code null} or unknown.
        * @param    value
        *           The value of the enum to return.  Must not be {@code null} or unknown.
        *
        * @return   The enum instance with the provided name(s).
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       @LegacySignature(returns = "OBJECT", qualified = "Progress.Lang.Enum", type = Type.METHOD, name = "ToObject", parameters =
       {
          @LegacyParameter(name = "cname", type = "CHARACTER", mode = "INPUT"),
          @LegacyParameter(name = "value", type = "INT64", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public static <T extends LegacyEnum> object<T> toObject(character cname, int64 value)
       {
          Class<T> cls = findClass(cname,
                                   LegacyEnum::generateInvalidTypeTO,
                                   LegacyEnum::generateFailedToFindEnumType);

          if (cls == null)
          {
             return new object<T>();
          }

          return lookupWorker(cls, value, LegacyEnum::generateInvalidValueTO);
       }

       /**
        * Return the enum class associated with the provided 4GL OO class name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cname
        *           The 4GL name of the enum class to find.  Must not be {@code null} or unknown.
        * @param    unknown
        *           Error generator method for an unknown name.
        * @param    noFind
        *           Error generator method for an enum that was not found.
        *
        * @return   The enum class.
        */
       public static <T extends LegacyEnum> Class<T> findClass(character        cname,
                                                               Runnable         unknown,
                                                               Consumer<String> noFind)
       {
          if (cname == null || cname.isUnknown())
          {
             unknown.run();
             return null;
          }

          return findClass(cname.toStringMessage(), unknown, noFind);
       }

       /**
        * Return the enum class associated with the provided 4GL OO class name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cname
        *           The 4GL name of the enum class to find.  Must not be {@code null} or unknown.
        * @param    unknown
        *           Error generator method for an unknown name.
        * @param    noFind
        *           Error generator method for an enum that was not found.
        *
        * @return   The enum class.
        */
       public static <T extends LegacyEnum> Class<T> findClass(String           cname,
                                                               Runnable         unknown,
                                                               Consumer<String> noFind)
       {
          if (cname == null)
          {
             unknown.run();
             return null;
          }

          Class<?> cls = ObjectOps.resolveClass(cname);

          if (cls == null || !(LegacyEnum.class.isAssignableFrom(cls)))
          {
             noFind.accept(cname);
             return null;
          }

          return (Class<T>) cls;
       }

       /**
        * Return the enum instance of the given type which is associated with the provided name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    name
        *           The name of the enum to return.  If the enum type is a flags enum, the value may
        *           be a comma-separated list of enum names to return as a multi-bit enum. Comma
        *           separated lists cannot be used with regular enums. Must not be {@code null} or
        *           unknown.
        * @param    unk
        *           Error generator method for an unknown name.
        *
        * @return   The enum instance with the provided name(s).
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       public static <T extends LegacyEnum> object<T> lookupWorker(Class<T>           cls,
                                                                   character          name,
                                                                   Consumer<Class<?>> unk)
       {
          if (name == null || name.isUnknown())
          {
             unk.accept(cls);
             return null;
          }

          EnumControlRecord<T> ecr = getECR(cls);

          return new object<T>(ecr.lookupName.apply(ecr, name.toStringMessage()));
       }

       /**
        * Return the enum instance of the given type which is associated with the provided value.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    value
        *           The value of the enum to return.  Must not be {@code null} or unknown.
        * @param    unk
        *           Error generator method for an unknown name.
        *
        * @return   The enum instance with the provided value.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       public static <T extends LegacyEnum> object<T> lookupWorker(Class<T>           cls,
                                                                   int64              value,
                                                                   Consumer<Class<?>> unk)
       {
          if (value == null || value.isUnknown())
          {
             unk.accept(cls);
             return null;
          }

          EnumControlRecord<T> ecr = getECR(cls);

          return new object<T>(ecr.lookupValue.apply(ecr, value.longValue()));
       }

       /**
        * Generate the not found error.
        *
        * @param    type
        *           Enum type name which was not found.
        */
       public static void generateFailedToFindEnumType(String type)
       {
          String msg = String.format("Failed to find enum type '%s'", type);
          ErrorManager.recordOrThrowError(18109, msg);
       }

       /**
        * Generate an error 17971 if this is an attempt to instantiate an enum type.
        *
        * @param    cls
        *           The class which needs to be checked.
        *
        * @return   {@code true} if this was an attempt to instantiate an enum.
        */
       public static boolean failEnumInstantiation(Class<?> cls)
       {
          // enums cannot be instantiated
          if (LegacyEnum.class.isAssignableFrom(cls))
          {
             String cname = ObjectOps.getLegacyName((Class<? extends _BaseObject_>)cls);
             String msg = String.format("Cannot NEW class %s because it is an interface or enum",
                                        cname);

             ErrorManager.recordOrThrowError(17971, msg, false);

             return true;
          }

          return false;
       }

       /**
        * 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).
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        *
        * @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}.
        */
       public static <T extends LegacyEnum> character getEnumValues(Class<T> cls)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          if (ecr.values == null)
          {
             ecr.values = getEnumsValueSort(ecr).stream()
                                                .map(e -> Long.toString(e.value))
                                                .collect(Collectors.joining(","));
          }

          return new character(ecr.values);
       }

       /**
        * 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).
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        *
        * @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}.
        */
       public static <T extends LegacyEnum> character getEnumNames(Class<T> cls)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          Function<T, String> render = (T e) ->
          {
             LegacyEnum en = e;
             return en.symname == null ? en.name : en.symname;
          };

          if (ecr.names == null)
          {
             ecr.names = getEnumsValueSort(ecr).stream()
                                               .map(render)
                                               .collect(Collectors.joining(","));
          }

          return new character(ecr.names);
       }

       /**
        * 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 for flags enums. Strangely, for regular
        * enums the names cannot have leading/trailing whitespace.  This 4GL quirk is maintained here. If any
        * name is invalid, then unknown value is returned.  For regular enums, a comma-separated list is an
        * invalid name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @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.
        */
       public static <T extends LegacyEnum> int64 getEnumValue(Class<T> cls, character name)
       {
          if (name == null || name.isUnknown())
          {
             generateInvalidValue(cls, "GetEnumValue");
             return new int64();
          }

          EnumControlRecord<T> ecr = getECR(cls);

          LegacyEnum enm = ecr.lookupNameSafe.lookup(ecr, name.toStringMessage(), false);

          return (enm == null) ? new int64() : new int64(enm.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    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @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.
        */
       public static <T extends LegacyEnum> character getEnumName(Class<T> cls, int64 value)
       {
          if (value == null || value.isUnknown())
          {
             generateInvalidValue(cls, "GetEnumName");
             return new character();
          }

          EnumControlRecord<T> ecr = getECR(cls);

          LegacyEnum enm = ecr.lookupValueSafe.apply(ecr, value.longValue());

          return (enm == null) ? new character() : enm.toLegacyString();
       }

       /**
        * Returns the 64-bit value of the enum.
        *
        * @return   The 64-bit value.
        */
       @LegacySignature(returns = "INT64", type = Type.METHOD, name = "GetValue")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public int64 getValue()
       {
          return new int64(value);
       }

       /**
        * Returns the 64-bit value of the enum.
        *
        * @return   The 64-bit value.
        */
       public long _getValue()
       {
          return value;
       }

       /**
        * Compares this instance with the specified instance and returns -1 if this instance is less than the
        * instance specified, 0 if the two instances are equal and 1 if this instance is greater than the
        * specified instance.  This is the implementation of the <code>Comparable</code> interface.
        * <p>
        * The algorithm will fail to give meaningful results in the case where one tries to sort against other
        * objects that do not represent compatible values.  In the 4GL, only enum instances of the same exact
        * type are allowed.  This method enforces that rule.
        * <p>
        * This implementation is consistent with {@code equals} and {@code hashCode}.
        *
        * @param    enm
        *           The instance to compare against.
        *
        * @return   -1, 0, or 1 depending on if this instance is less than, equal to or greater (respectively)
        *           than the specified instance <code>enm</code>.
        */
       @Override
       public int compareTo(LegacyEnum enm)
       {
          if (!checkTypes(this, enm))
          {
             return -1;
          }

          return compareWorker(enm);
       }

       /**
        * Determines if this instance and the given instance are equivalent.  This implementation is consistent
        * with {@code compareTo} and {@code hashCode}, which means these instances are safe for usage as a key
        * in an associative collection.
        *
        * @param    obj
        *           The instance to compare against.
        *
        * @return   {@code true} if the objects compare equivalently; {@code false} if they do not, or if the
        *           parameter is not a {@code LegacyEnum} instance of the same subclass type.
        */
       @Override
       public boolean equals(Object obj)
       {
          if (!checkTypes(this, obj))
          {
             return false;
          }

          return compareWorker((LegacyEnum) obj) == 0;
       }

       /**
        * Calculates a hash code using the instance's value.  This implementation is consistent with
        * {@code equals}, so it is usable for Java's associative collections.  It ignores the name
        * of the enum in this calculation (which is how {@code equals} is implemented as well).
        *
        * @return   The hash code for this object instance.
        */
       @Override
       public int hashCode()
       {
          // this is the same as (value * 31)
          return (int) ((value << 5) - value);
       }

       /**
        * Compares this instance with the specified instance and returns -1 if this instance is less than the
        * given instance, 0 if the two instances are equal and 1 if this instance is greater than the given
        * instance.
        * <p>
        * This is the replacement for the 4GL {@code Progress.Lang.Enum:CompareTo} method.  It is NOT an
        * implementation of {@code Comparable}.
        *
        * @param    that
        *           The enum instance to compare against.
        *
        * @return   -1, 0, or 1 depending on if this instance is less than, equal to or
        *           greater than the given instance.
        */
       @LegacySignature(returns = "INTEGER", type = Type.METHOD, name = "CompareTo", parameters =
       {
          @LegacyParameter(name = "that", type = "OBJECT", qualified = "progress.lang.enum", mode = "INPUT")
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       public integer compareTo(final object<? extends LegacyEnum> that)
       {
          if (that == null || that.isUnknown())
          {
             return new integer();
          }

          return new integer(compareWorker(that.ref()));
       }

       /**
        * Check if this reference matches the given one.
        *
        * @param    other
        *           The other reference.
        *
        * @return   {@code true} if the other reference is not unknown value and if the reference
        *           is the same as this object's reference.
        */
       @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "Equals", parameters =
       {
          @LegacyParameter(name = "other", type = "OBJECT", mode = "INPUT", qualified="Progress.Lang.Object"),
       })
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL|RT_LVL_FULL)
       @Override
       public logical legacyEquals(object<? extends _BaseObject_> other)
       {
          if (other == null || other.isUnknown() || !checkTypes(this, other.ref()))
          {
             return new logical(false);
          }

          return new logical(compareWorker((LegacyEnum) other.ref()) == 0);
       }

       /**
        * Get the string representation for this instance.
        * <p>
        * Although the original method name is {@code toString()}, this version returns a
        * {@code character} type which would conflict with the {@code java.lang.Object.toString()}
        * version.  For this reason, the name is mapped differently during conversion.
        *
        * @return   The enum's case-preserved name as originally defined in the code (for
        *           predefined enums or as a comma separated list of names for dynamically
        *           instantiated multi-bit flag enums.
        */
       @LegacySignature(returns = "CHARACTER", type = Type.METHOD, name = "ToString")
       @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
       @Override
       public character toLegacyString()
       {
          return new character(name);
       }

       /**
        * Reports if the given instance is managed as part of the legacy 4GL object life cycle. This
        * includes reference counting, linking in the SESSION object chain, managed construction and
        * destruction.
        *
        * @return   Always {@code false} because enums exist outside of the OO 4GL life cycle
        *           mechanisms.
        */
       @Override
       public boolean isTracked()
       {
          return false;
       }

       /**
        * Compares this instance with the specified instance and returns -1 if this instance is less
        * than the given instance, 0 if the two instances are equal and 1 if this instance is greater
        * than the given instance.
        *
        * @param    that
        *           The enum instance to compare against.
        *
        * @return   -1, 0, or 1 depending on if this instance is less than, equal to or
        *           greater than the given instance.
        */
       public int compareWorker(LegacyEnum that)
       {
          long diff = this.value - that.value;

          return (diff < 0) ? -1 : ( (diff > 0) ? 1 : 0 );
       }

       /**
        * The object implements the writeExternal method to save its contents
        * by calling the methods of DataOutput for its primitive values or
        * calling the writeObject method of ObjectOutput for objects, strings,
        * and arrays.
        *
        * @serialData Overriding methods should use this tag to describe
        *             the data layout of this Externalizable object.
        *             List the sequence of element types and, if possible,
        *             relate the element to a public/protected field and/or
        *             method of this Externalizable class.
        *
        * @param out the stream to write the object to
        * @exception IOException Includes any I/O exceptions that may occur
        */
       @Override
       public void writeExternal(final ObjectOutput out)
       throws IOException
       {
          out.writeObject(name);
          out.writeLong(value);
          out.writeObject(symname);
       }

       /**
        * The object implements the readExternal method to restore its
        * contents by calling the methods of DataInput for primitive
        * types and readObject for objects, strings and arrays.  The
        * readExternal method must read the values in the same sequence
        * and with the same types as were written by writeExternal.
        *
        * @param in the stream to read data from in order to restore the object
        * @exception IOException if I/O errors occur
        * @exception ClassNotFoundException If the class for an object being
        *              restored cannot be found.
        */
       @Override
       public void readExternal(final ObjectInput in)
       throws IOException,
              ClassNotFoundException
       {
          name = (String) in.readObject();
          value = in.readLong();
          symname = (String) in.readObject();
       }

       /**
        * Return the enum instance of the given type which is associated with the provided value.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    name
        *           The name of the enum to return.  If the enum type is a flags enum, the value may
        *           be a comma-separated list of enum names to return as a multi-bit enum. Comma
        *           separated lists cannot be used with regular enums. Must not be {@code null} or
        *           unknown.
        *
        * @return   The enum instance with the provided name(s).
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> object<T> getEnum(Class<T> cls, character name)
       {
          return lookupWorker(cls, name, LegacyEnum::generateInvalidValueGE);
       }

       /**
        * Return the enum instance of the given type which is associated with the provided value.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    value
        *           The value of the enum to return.  Must not be {@code null} or unknown.
        *
        * @return   The enum instance with the provided value.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> object<T> getEnum(Class<T> cls, int64 value)
       {
          return lookupWorker(cls, value, LegacyEnum::generateInvalidValueGE);
       }

       /**
        * Return the enum instance of the given type which is associated with the provided name.
        *
        * @param    <E>
        *           The enum type.
        * @param    ecr
        *           The enum control record for the enum to be processed.  Must not be {@code null}.
        * @param    name
        *           The name of a single enum to return.  This will process an exact match only. This
        *           MUST NOT be a comma-separated list of enum names even if the enum to process is a
        *           flag enum. Must not be {@code null}.
        *
        * @return   The enum instance with the provided name(s).
        */
       protected static <E extends LegacyEnum> E lookupNameWorker(EnumControlRecord<E> ecr, String name)
       {
          E enm = lookupNameWorkerSafe(ecr, name, true);

          if (enm == null)
          {
             if (name.indexOf(',') != -1)
             {
                generateInvalidCommaSeparatedName(ecr.cls, name);
             }
             else
             {
                generateNotFoundName(ecr.cls, name);
             }

             return null;
          }

          return enm;
       }

       /**
        * Return the enum instance of the given type which is associated with the provided name. This will not
        * raise an error but it may return {@code null}.
        *
        * @param    <E>
        *           The enum type.
        * @param    ecr
        *           The enum control record for the enum to be processed.  Must not be {@code null}.
        * @param    name
        *           The name of a single enum to return.  This will process an exact match only. This
        *           MUST NOT be a comma-separated list of enum names even if the enum to process is a
        *           flag enum. Must not be {@code null}.
        * @param    trim
        *           {@code true} to trim leading/trailing whitespace in the name before searching.
        *
        * @return   The enum instance with the provided name(s) or {@code null} if there is no match.
        */
       protected static <E extends LegacyEnum> E lookupNameWorkerSafe(EnumControlRecord<E> ecr,
                                                                      String               name,
                                                                      boolean              trim)
       {
          name = trim ? StringHelper.safeTrim(name) : name;

          return ecr.byName.get(name.toLowerCase());
       }

       /**
        * Return the enum instance of the given type which is associated with an exact match to the provided
        * value.
        *
        * @param    <E>
        *           The enum type.
        * @param    ecr
        *           The enum control record for the enum to process.  Must not be {@code null}.
        * @param    value
        *           The value of the enum to return.  Only exact matches are considered.
        *
        * @return   The enum instance with the provided value.
        */
       protected static <E extends LegacyEnum> E lookupValueWorker(EnumControlRecord<E> ecr, long value)
       {
          E enm = lookupValueWorkerSafe(ecr, value);

          if (enm == null)
          {
             generateNotFoundValue(ecr.cls, value);
             return null;
          }

          return enm;
       }

       /**
        * Return the enum instance of the given type which is associated with an exact match to the provided
        * value.
        *
        * @param    <E>
        *           The enum type.
        * @param    ecr
        *           The enum control record for the enum to process.  Must not be {@code null}.
        * @param    value
        *           The value of the enum to return.  Only exact matches are considered.
        *
        * @return   The enum instance with the provided value or {@code null} if there is no match.
        */
       protected static <E extends LegacyEnum> E lookupValueWorkerSafe(EnumControlRecord<E> ecr, long value)
       {
          return ecr.byValue.get(value);
       }

       /**
        * Return an existing instance if it exists with the exact value provided, otherwise create a new
        * instance and return it.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    value
        *           The value of the enum to return.
        *
        * @return   The enum instance with the provided value.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> T getExisting(Class<T> cls, long value)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          return ecr.byValue.get(value);
       }

       /**
        * Create a new instance and return it.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.  Must not be {@code null}.
        * @param    name
        *           The name of the enum.
        * @param    value
        *           The value of the enum.
        *
        * @return   A new enum instance with the provided name and value.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> T createWorker(Class<T> cls, String name, long value)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          return ecr.create.apply(name, value);
       }

       /**
        * Obtains a list of all predefined enums for the given enum type, 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).
        *
        * @param    <T>
        *           The enum type.
        * @param    ecr
        *           The enum control record.
        *
        * @return   The list of predefined enums.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> List<T> getEnumsValueSort(EnumControlRecord<T> ecr)
       {
          ArrayList list = new ArrayList(ecr.enums);
          list.sort(ecr.valueSort);

          return list;
       }

       /**
        * Obtains a list of all predefined enums for the given enum type, in the order of their definition.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class being processed.
        *
        * @return   The list of predefined enums.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> List<T> getEnumsDefineOrder(Class<T> cls)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          return getEnumsDefineOrder(ecr);
       }

       /**
        * Obtains a list of all predefined enums for the given enum type, in the order of their definition.
        *
        * @param    <T>
        *           The enum type.
        * @param    ecr
        *           The enum control record.
        *
        * @return   The list of predefined enums.
        */
       protected static <T extends LegacyEnum> List<T> getEnumsDefineOrder(EnumControlRecord<T> ecr)
       {
          return new ArrayList(ecr.enums);
       }

       /**
        * Obtains a maximal bitfield for the given enum type.  The maximal bitfield is the bitwise OR of all
        * predefined enum values for this type.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class to process.
        *
        * @return   The maximal bitfield.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> long maximalBitfield(Class<T> cls)
       {
          return getECR(cls).maximal;
       }

       /**
        * Implicitly create the enum instance by incrementing the value from the most recently created enum.
        * If this is the first instance, set the value to 1.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class.
        * @param    name
        *           The enum legacy name.
        *
        * @return   The created object instance for this enum.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> object<T> createEnum(Class<T> cls, String name)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          long value = 1;

          if (!ecr.enums.isEmpty())
          {
             T ori = ecr.enums.getLast();

             value = ecr.flags ? nextPowerOf2(name, ori.value) : ori.value + 1;
          }

          // we don't need to pass the original enum as a pattern in this case
          return createEnumWorker(ecr, name, value, null);
       }

       /**
        * Explicitly create the enum instance with the given name but with the value from the specified alias.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class.
        * @param    name
        *           The enum legacy name.
        * @param    alias
        *           The source of the enum's value.  Must be previously defined.
        *
        * @return   The created object instance for this enum.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered, the class is not a valid enum or the alias is
        *           unknown.
        */
       protected static <T extends LegacyEnum> object<T> createEnum(Class<T> cls, String name, String alias)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          // by using the enum-type-specific helper here, we get the right behavior for either regular
          // or flags enums; if the alias is a comma-separated list, then the resulting enum is a new enum
          // which represents multuple predefined enums and has the multibit flag set
          T ori = ecr.lookupName.apply(ecr, alias);

          if (ori == null)
          {
             throw new IllegalStateException("Unknown alias cannot be used to initialize an enum.");
          }

          // the pattern enum muust be passed here
          return createEnumWorker(ecr, name, ori.value, ori);
       }

       /**
        * Explicitly create an enum instance with the specified value and legacy name.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class.
        * @param    name
        *           The enum legacy name.
        * @param    value
        *           The enum value.
        *
        * @return   The created object instance for this enum.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       protected static <T extends LegacyEnum> object<T> createEnum(Class<T> cls, String name, long value)
       {
          EnumControlRecord<T> ecr = getECR(cls);

          return createEnumWorker(ecr, name, value, null);
       }

       /**
        * Register this enum.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class.
        * @param    create
        *           Lambda for creation of new instances.
        * @param    legacyName
        *           The legacy, fully qualified, name.
        */
       protected static <T extends LegacyEnum> void registerEnum(Class<T>                    cls,
                                                                 BiFunction<String, Long, T> create,
                                                                 String                      legacyName)
       {
          registerEnum(cls,
                       create,
                       LegacyEnum::lookupNameWorker,
                       LegacyEnum::lookupNameWorkerSafe,
                       LegacyEnum::lookupValueWorker,
                       LegacyEnum::lookupValueWorkerSafe,
                       null,
                       LegacyEnum::compareValues,
                       legacyName);
       }

       /**
        * Register this enum.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           The enum class.
        * @param    create
        *           Lambda for creation of new instances.
        * @param    lookupName
        *           Lambda for lookup of enums by name.
        * @param    lookupNameSafe
        *           Lambda for lookup of enums by name, without raising an error.
        * @param    lookupValue
        *           Lambda for lookup of enums by value.
        * @param    lookupValueSafe
        *           Lambda for lookup of enums by value, without raising an error.
        * @param    initHelper
        *           Initializer helper.
        * @param    valueSort
        *           Sorts enums by value.
        * @param    legacyName
        *           The legacy, fully qualified, name.
        */
       protected static
          <T extends LegacyEnum> void registerEnum(Class<T>                                    cls,
                                                   BiFunction<String, Long, T>                 create,
                                                   BiFunction<EnumControlRecord<T>, String, T> lookupName,
                                                   NameLookupHelper<T>                         lookupNameSafe,
                                                   BiFunction<EnumControlRecord<T>, Long, T>   lookupValue,
                                                   BiFunction<EnumControlRecord<T>, Long, T>   lookupValueSafe,
                                                   EnumInitializer<T>                          initHelper,
                                                   Comparator<LegacyEnum>                      valueSort,
                                                   String                                      legacyName)
       {
          synchronized (registry)
          {
             registry.put(cls, new EnumControlRecord<T>(cls,
                                                        create,
                                                        lookupName,
                                                        lookupNameSafe,
                                                        lookupValue,
                                                        lookupValueSafe,
                                                        initHelper,
                                                        valueSort,
                                                        legacyName));
          }

          ObjectOps.registerClass(legacyName, cls);
       }

       /**
        * Generate the not found error for a value lookup.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        * @param    value
        *           Enum instance value which cannot be found.
        */
       protected static void generateNotFoundValue(Class<?> cls, long value)
       {
          String cname = ObjectOps.getLegacyName((Class<? extends _BaseObject_>)cls);
          String msg   = String.format("Could not get enum of type '%s' for value %d", cname, value);

          ErrorManager.recordOrThrowError(18207, msg);
       }

       /**
        * Generate the invalid value error (15246) for an enum method call.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        * @param    meth
        *           Method name which had the failure.
        */
       protected static void generateInvalidValue(Class<?> cls, String meth)
       {
          generateInvalidValue(ObjectOps.getLegacyName((Class<? extends _BaseObject_>)cls), meth);
       }

       /**
        * Generate the invalid value error (15246) for an enum method call.
        *
        * @param    cname
        *           The legacy class name for which the value is invalid.
        * @param    meth
        *           Method name which had the failure.
        */
       protected static void generateInvalidValue(String cname, String meth)
       {
          String msg = String.format("Invalid value specified for %s:%s", cname, meth);

          ErrorManager.recordOrThrowError(15246, msg);
       }

       /**
        * Render a list of enums (name and value) into a string.
        *
        * @param    list
        *           The list of enums to dump.  Must NOT be {@code null}.
        *
        * @return   The rendered text.
        */
       protected static String dumpEnums(Collection<LegacyEnum> list)
       {
          return list.stream().map(LegacyEnum::dumpEnum).collect(Collectors.joining(", "));
       }

       /**
        * Render an enum (name and value) into a simple string.
        *
        * @param    en
        *           The enum to dump.  Must NOT be {@code null}.
        *
        * @return   The rendered text.
        */
       protected static String dumpEnum(LegacyEnum en)
       {
          return "(" + en.name + ":" + Long.toString(en.value) + ")";
       }

       /**
        * Obtain the enum control record for the given enum class.
        *
        * @param    <T>
        *           The enum type.
        * @param    cls
        *           Enum class.
        *
        * @return   The control record.
        *
        * @throws   IllegalStateException
        *           If the enum is not yet registered or the class is not a valid enum.
        */
       private static <T extends LegacyEnum> EnumControlRecord getECR(Class<T> cls)
       {
          synchronized (registry)
          {
             EnumControlRecord<T> ecr = registry.get(cls);

             if (ecr == null)
             {
                throw new IllegalStateException("Attempt to create enum instance before registration.");
             }

             return ecr;
          }
       }

       /**
        * Initialize the enum instance with the specified value and legacy name.
        *
        * @param    <T>
        *           The enum type.
        * @param    ecr
        *           The enum control record.
        * @param    name
        *           The enum legacy name.
        * @param    value
        *           The enum value.
        * @param    pattern
        *           An enum from which some state is patterned.
        *
        * @return   The created object instance for this enum.
        */
       private static <T extends LegacyEnum> object<T> createEnumWorker(EnumControlRecord<T> ecr,
                                                                        String               name,
                                                                        long                 value,
                                                                        T                    pattern)
       {
          String text = name;

          if (pattern != null)
          {
             // multibit flags enum patterns created from comma-separated alias lists are not predefined and
             // have blank names; these must be avoided
             if (pattern.name != null && !pattern.name.isEmpty())
             {
                text = pattern.name;
             }
          }
          else
          {
             // we cannot use the helper here (because for flags enums it will create a new instance on the fly)
             // so we use the simple lookup only
             T ori = LegacyEnum.lookupValueWorkerSafe(ecr, value);

             if (ori != null)
             {
                text = ori.name;
             }
          }

          T instance = ecr.create.apply(text, value);

          if (text != name)
          {
             LegacyEnum en = instance;
             en.symname = name;
          }

          ecr.byName.put(StringHelper.safeTrim(name).toLowerCase(), instance);
          ecr.byValue.putIfAbsent(Long.valueOf(value), instance);  // only the first enum of a given value is stored
          ecr.enums.add(instance);

          if (ecr.initHelper != null)
          {
             ecr.initHelper.init(ecr, instance, pattern);
          }

          return new EnumObject(instance);
       }

       /**
        * Calculate the next higher power of 2 over the given value.
        *
        * @param    name
        *           The name of the enum member being created.
        * @param    value
        *           The number to process.
        *
        * @return   The next higher power of 2.
        */
       private static long nextPowerOf2(String name, long value)
       {
          if (value == 0)
          {
             return 0x01;
          }

          int bit = highestSetBit(value);

          if (bit > 62)
          {
             // in the 4GL, this is a compiler error so this really should NOT happen unless the
             // enum is incorrectly defined
             String msg   = String.format("Invalid value specified for enum member '%s'", name);
             ErrorManager.recordOrThrowError(18215, msg);
          }

          return 1L << (bit + 1);
       }

       /**
        * Detect which is the highest bit set in the given value (the most significant bit is
        * on the left and is bit number 63, while the least significant bit is on the right and
        * is bit 0).  Do NOT call this with a 0 value, since that would yield nonsense.
        *
        * @param    value
        *           The bitfield to process.
        *
        * @return   The bit number of the highest bit that is set.
        *
        * @throws   IllegalArgumentException
        *           If the given value is zero.
        */
       private static int highestSetBit(long value)
       {
          if (value == 0)
             throw new IllegalArgumentException("Value 0 is not allowed.");

          // quick out, negative numbers have the sign bit set
          if (value < 0)
             return 63;

          int i;

          // check the rest of the bits
          for (i = 0; i <= 62; i++)
          {
             value >>>= 1;

             if (value == 0)
                break;
          }

          return i;
       }

       /**
        * Regular enum comparator.  Sorts using the natural sorting of the numeric values of the enums.
        *
        * @param    e1
        *           First enum.
        * @param    e2
        *           Second enum.
        *
        * @return   -1, 0, or 1 depending on if e1 is less than, equal to or greater than e2.
        */
       private static int compareValues(LegacyEnum e1, LegacyEnum e2)
       {
          return e1.compareWorker(e2);
       }

       /**
        * Check if the data types represent the same exact enum class, raise error 223 if they are different.
        *
        * @param    o1
        *           Object instance 1.
        * @param    o2
        *           Object instance 2.
        *
        * @return   {@code true} if the classes of each object are identical.
        *
        * @throws   ErrorConditionException
        *           If the types are different.
        */
       private static boolean checkTypes(Object o1, Object o2)
       {
          if (o1.getClass() != o2.getClass())
          {
             ErrorManager.recordOrThrowError(223, "Incompatible data types in expression or assignment");
             return false;
          }

          return true;
       }

       /**
        * Generate the invalid type error for ToObject.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       private static void generateInvalidTypeTO()
       {
          ErrorManager.recordOrThrowError(18193, String.format(TO_SPEC, "enumTypeName"));
       }

       /**
        * Generate the invalid value error for ToObject.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       private static void generateInvalidValueTO(Class<?> cls)
       {
          ErrorManager.recordOrThrowError(18193, String.format(TO_SPEC, "value"));
       }

       /**
        * Generate the invalid value error for getEnum.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       private static void generateInvalidValueGE(Class<?> cls)
       {
          generateInvalidValue(cls, "GetEnum");
       }

       /**
        * Generate the not found error for a name lookup.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        * @param    ename
        *           Enum instance name which cannot be found.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       private static void generateNotFoundName(Class<?> cls, String ename)
       {
          String cname = ObjectOps.getLegacyName((Class<? extends _BaseObject_>)cls);
          String msg   = String.format("Could not find '%s' in enum '%s'", ename, cname);

          ErrorManager.recordOrThrowError(18008, msg);
       }

       /**
        * Generate the comma-separated name list error for non-flags enums.
        *
        * @param    cls
        *           The enum for which the value is invalid.
        * @param    ename
        *           Comma-separated name list which was invalid.
        *
        * @throws   ErrorConditionException
        *           Always.
        */
       private static void generateInvalidCommaSeparatedName(Class<?> cls, String ename)
       {
          String cname = ObjectOps.getLegacyName((Class<? extends _BaseObject_>)cls);
          String msg   = String.format("Comma separated list of names '%s' not valid for non-flag " +
                                       "enum '%s'",
                                       ename,
                                       cname);

          ErrorManager.recordOrThrowError(18017, msg);
       }

       /**
        * Post-process a new enum to set state.
        *
        * @param    <E>
        *           The enum type associated with the control record.
        */
       @FunctionalInterface
       protected interface EnumInitializer<E extends LegacyEnum>
       {
          /**
           * Post-process a new enum to set state.
           *
           * @param    ecr
           *           The control record.
           * @param    created
           *           Newly created enum needing initialization.
           * @param    pattern
           *           Enum from which some state can be patterned.
           */
          void init(EnumControlRecord<E> ecr, E created, E pattern);
       }

       /**
        * Helper to lookup a predefined enum by its name.
        *
        * @param    <E>
        *           The enum type associated with the control record.
        */
       @FunctionalInterface
       protected interface NameLookupHelper<E extends LegacyEnum>
       {
          /**
           * Lookup a predefined enum by its name.
           *
           * @param    ecr
           *           The control record.
           * @param    name
           *           Enum name.
           * @param    trim
           *           {@code true} to force trimming the name before searching.
           *
           * @return   The enum or {@code null} if no match was found.
           */
          E lookup(EnumControlRecord<E> ecr, String name, boolean trim);
       }

       /**
        * Stores the static data associated with a given enum subclass.
        *
        * @param    <E>
        *           The enum type associated with the control record.
        */
       protected static class EnumControlRecord<E extends LegacyEnum>
       {
          /** Fully qualified legacy enum name. */
          public final String legacyName;

          /** num class for which this record is defined. */
          public final Class<E> cls;

          /** Map of values to enum instances. */
          public final HashMap<Long, E> byValue = new HashMap<>();

          /** Map of names to enum instances. */
          public final HashMap<String, E> byName = new HashMap<>();

          /** All enum instances in definition order. */
          public final LinkedList<E> enums = new LinkedList<>();

          /** Lambda for creation of new instances. */
          public final BiFunction<String, Long, E> create;

          /** Lambda for lookup of enums by name. */
          public final BiFunction<EnumControlRecord<E>, String, E> lookupName;

          /** Lambda for lookup of enums by name, without raising an error. */
          public final NameLookupHelper<E> lookupNameSafe;

          /** Lambda for lookup of enums by value. */
          public final BiFunction<EnumControlRecord<E>, Long, E> lookupValue;

          /** Lambda for lookup of enums by value, without raising an error. */
          public final BiFunction<EnumControlRecord<E>, Long, E> lookupValueSafe;

          /** Initialization helper. */
          public final EnumInitializer<E> initHelper;

          /** Sorts enums by value. */
          public final Comparator<LegacyEnum> valueSort;

          /** Marks the enum if it is a flags enum. */
          public boolean flags = false;

          /** For flags enums, this is the maximal bitfield (all set bits in defined enums). */
          public long maximal = 0;

          /** {@code true} if any enum value is a negative number. */
          public boolean negatives = false;

          /** {@code true} if any enum value is between -1 and -32768 inclusive. */
          public boolean highNegs = false;

          /** Cached instance of the sorted values string. */
          public String values = null;

          /** Cached instance of the sorted names string. */
          public String names = null;

          /**
           * Construct an instance.
           *
           * @param    cls
           *           Enum class for which this record is defined.
           * @param    create
           *           Lambda for creation of new instances.
           * @param    lookupName
           *           Lambda for lookup of enums by name.
           * @param    lookupNameSafe
           *           Lambda for lookup of enums by name, without raising an error.
           * @param    lookupValue
           *           Lambda for lookup of enums by value.
           * @param    lookupValueSafe
           *           Lambda for lookup of enums by value, without raising an error.
           * @param    initHelper
           *           Initialization helper.
           * @param    valueSort
           *           Sorts enums by value.
           * @param    legacyName
           *           The legacy, fully qualified, name.
           */
          protected EnumControlRecord(Class<E>                                    cls,
                                      BiFunction<String, Long, E>                 create,
                                      BiFunction<EnumControlRecord<E>, String, E> lookupName,
                                      NameLookupHelper<E>                         lookupNameSafe,
                                      BiFunction<EnumControlRecord<E>, Long, E>   lookupValue,
                                      BiFunction<EnumControlRecord<E>, Long, E>   lookupValueSafe,
                                      EnumInitializer<E>                          initHelper,
                                      Comparator<LegacyEnum>                      valueSort,
                                      String                                      legacyName)
          {
             this.cls             = cls;
             this.create          = create;
             this.lookupName      = lookupName;
             this.lookupNameSafe  = lookupNameSafe;
             this.lookupValue     = lookupValue;
             this.lookupValueSafe = lookupValueSafe;
             this.initHelper      = initHelper;
             this.legacyName      = legacyName;
             this.valueSort       = valueSort;
          }
       }

    }