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

}