FlagsEnum.java

/*
** Module   : FlagsEnum.java
** Abstract : Implementation of the Progress.Lang.FlagsEnum builtin class.
**
** Copyright (c) 2020-2021, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description---------------------------------------
** 001 ME  20200422 First version.
** 002 ME  20200423 Fix set/unset flag methods.
**     GES 20200429 Early rework to new enums approach.
**     GES 20200507 Completed first working version.
**     GES 20200603 Fixed ToString and initialize methods and added legacy class registration.
**     GES 20200605 Bitwise operators must return predefined instances if they are an exact match.
** 003 CA  20210221 Fixed 'qualified', 'extent' and 'returns' annotations at the legacy
**                  signature.
*/

/*
 ** 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 com.goldencode.p2j.util.*;

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

import java.util.function.*;
import java.util.stream.*;

/**
 * Implementation of the Progress.Lang.FlagsEnum builtin class.
 */
@LegacyResource(resource = "Progress.Lang.FlagsEnum")
@LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
public class FlagsEnum
extends LegacyEnum
{
   /** Error message for unknown value input in IsFlagSet. */
   private static final String ISSET_UNKNOWN = "Could not check flags because the specified enum " +
                                               "instance is invalid";
                                               
   /** Error message for zero value input in IsFlagSet. */
   private static final String ISSET_ZERO = "Cannot check flags if the value for the specified enum " +
                                            "instance is zero";
                                            
   /** Error message for mismatched enum class as a parameter. */ 
   private static final String MISMATCHED_INPUT = "Parameter 1 for METHOD %s is not type compatible with " +
                                                  "its definition";
                                                  
   /** Negative values greater than or equal to this will trigger the maximal bitfield matching quirk. */
   private static final int HIGH_NEGATIVE_BOUNDARY = -32768;
                                               
   /** Identifies dynamically created multibit flags enums. */
   private boolean multibit = false;
   
   /** Identifies an enum which is an alias for one or more other flag enum values. */
   private boolean aliased = false;
   
   /** Identifies an enum which was predefined. */
   private boolean predefined = false;
   
   static
   {
      ObjectOps.registerClass("Progress.Lang.FlagsEnum", FlagsEnum.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 FlagsEnum(String name, long value)
   {
      super(name, value);
   }
   
   /**
    * Create a new enum instance that has all bits turned ON which are ON in both operands and return the
    * resulting enum.  This implements the bitwise OR operator and is the equivalent of the {@code SetFlag}
    * method with only a minor difference in the unknown value handling.
    *
    * @param    op1
    *           The input enum as the left operand.
    * @param    op2
    *           The input enum as the right operand.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   public static object<? extends FlagsEnum> or(object<? extends FlagsEnum> op1, object<? extends FlagsEnum> op2)
   {
      return operator((object<FlagsEnum>) op1,
                      (object<FlagsEnum>) op2,
                      (long o1, long o2) -> { return o1 | o2; });
   }

   /**
    * Create a new enum instance that has all bits turned ON which are DIFFERENT states in both operands 
    * return the resulting enum.  This implements the bitwise XOR operator and is the equivalent of the
    * {@code ToggleFlag} method with only a minor difference in the unknown value handling.
    *
    * @param    op1
    *           The input enum as the left operand.
    * @param    op2
    *           The input enum as the right operand.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   public static object<? extends FlagsEnum> xor(object<? extends FlagsEnum> op1, object<? extends FlagsEnum> op2)
   {
      return operator((object<FlagsEnum>) op1,
                      (object<FlagsEnum>)op2,
                      (long o1, long o2) -> { return o1 ^ o2; });
   }

   /**
    * Create a new enum instance that has all bits turned ON which are ON in both operands and return the  
    * resulting enum.  This implements the bitwise AND operator.
    *
    * @param    op1
    *           The input enum as the left operand.
    * @param    op2
    *           The input enum as the right operand.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   public static object<? extends FlagsEnum> and(object<? extends FlagsEnum> op1, object<? extends FlagsEnum> op2)
   {
      return operator((object<FlagsEnum>) op1,
                      (object<FlagsEnum>) op2,
                      (long o1, long o2) -> { return o1 & o2; });
   }
   
   /**
    * Invert the bits of this enum and return the result as a new enum instance.  This is the bitwise NOT
    * 4GL operator but the behavior is not the traditional bitwise inversion (one's complement).  Instead
    * the algorithm is {@code ((INVERT enum_member) AND maximal_bitfield)} where INVERT is traditional 
    * bitwise NOT (full inversion of bit state or one's complement), AND is traditional "bitwise AND" and
    * maximal_bitfield is the value created by bitwise OR'ing all pre-defined enums of this type. This
    * means that any bits which are not set in one of the predefined enums will not be set in the result.
    *
    * @param    flag
    *           The input enum.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value.
    */
   public static object<? extends FlagsEnum> not(object<? extends FlagsEnum> flag)
   {
      if (flag == null || flag.isUnknown())
      {
         unknownBitwiseOperation();
         return new object<>();
      }
      
      FlagsEnum        input = flag.ref();
      Class<FlagsEnum> cls   = (Class<FlagsEnum>) input.getClass();
      
      long result = ~(input.value) & maximalBitfield(cls);
      
      return new object<>((FlagsEnum) getExistingOrCreate(cls, result));
   }

   /**
    * 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()
   {
      // predefined enums always have names so the super method will work; predefined enums made from a
      // comma-separated alias list will have the multibit flag set but the name is still valid
      if (predefined)
      {
         return super.toLegacyString();
      }
      
      // at this point we must be dealing with a multibit enum that was dynamically created
      
      // our output name must only include non-multibit, non-aliased enums whose bits are fully present in
      // the current enum instance's value; also any 0 value is excluded
      Predicate<LegacyEnum> include = (LegacyEnum e) ->
      {
         FlagsEnum fe = (FlagsEnum) e;
         
         // boolean rc = !fe.multibit && !fe.aliased && fe.value != 0 && ((this.value & fe.value) == fe.value);
         // String msg = String.format("name %s; value %d; multibit %b; aliased %b; result %b", fe.name, fe.value, fe.multibit, fe.aliased, rc);
         // com.goldencode.p2j.ui.LogicalTerminal.messageBox(msg);
         
         return !fe.multibit && !fe.aliased && fe.value != 0 && ((this.value & fe.value) == fe.value);
      };
      
      // iterate through all predefined enums matching the include filter and create a comma-separated
      // output string
      String result = getEnumsDefineOrder(this.getClass()).stream()
                                                          .filter(include)
                                                          .map((e) -> e.name)
                                                          .collect(Collectors.joining(","));
                                                          
      return new character((result == null) ? "" : result);
   }

   /**
    * Report if all bits which are set in the input enum are also set in this current enum
    * instance. This means that if the current instance is the same as or a superset of the
    * input instance, then this method will return {@code true}.
    *
    * @param    that
    *           The input enum with the bits to check for.
    *
    * @return   {@code true} if ALL bits which are ON in {@code that} are also ON in the
    *           current enum.
    *
    * @throws   ErrorConditionException
    *           If {@code that} is unknown value or has a value of zero.
    */
   @LegacySignature(returns = "LOGICAL", type = Type.METHOD, name = "IsFlagSet", parameters =
   {
      @LegacyParameter(name = "that", type = "OBJECT", qualified = "progress.lang.flagsenum", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   public logical isFlagSet(object<? extends FlagsEnum> that)
   {
      if (that.isUnknown())
      {
         ErrorManager.recordOrThrowError(18205, ISSET_UNKNOWN);
         return new logical(false);
      }
      
      long thatVal = that.ref().value;

      if (thatVal == 0)
      {
         ErrorManager.recordOrThrowError(18113, ISSET_ZERO);
         return new logical(false);
      }
         
      return new logical((value & thatVal) == thatVal);
   }

   /**
    * Create a new enum instance that has all bits turned ON which are ON in both {@code this} and 
    * {@code flag} and return the resulting enum.  This is the equivalent of the bitwise OR operator with
    * only a minor difference in the unknown value handling.
    *
    * @param    <T>
    *           The enum type.
    * @param    flag
    *           The input enum.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "SetFlag", qualified = "progress.lang.flagsenum", parameters =
   {
      @LegacyParameter(name = "flag", type = "OBJECT", qualified = "progress.lang.flagsenum", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   public <T extends FlagsEnum> object<T> setFlag(final object<T> flag)
   {
      return bitwiseWorker(flag, (long op1, long op2) -> { return op1 | op2; }, "SetFlag"); 
   }

   /**
    * Create a new enum instance that has all bits turned ON which are DIFFERENT states in both {@code this} 
    * and {@code flag} and return the resulting enum.  This is the equivalent of the bitwise XOR operator
    * with only a minor difference in the unknown value handling.
    *
    * @param    <T>
    *           The enum type.
    * @param    flag
    *           The input enum.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "ToggleFlag", qualified = "progress.lang.flagsenum", parameters =
   {
      @LegacyParameter(name = "flag", type = "OBJECT", qualified = "progress.lang.flagsenum", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   public <T extends FlagsEnum> object<T> toggleFlag(object<T> flag)
   {
      return bitwiseWorker(flag, (long op1, long op2) -> { return op1 ^ op2; }, "ToggleFlag"); 
   }

   /**
    * Create a new enum instance which is a copy of the {@code this} but where all bits are turned OFF which
    * are ON in {@code flag} and return the resulting enum.  This is the equivalent of the operation
    * {@code op1 AND (NOT op2)}.
    *
    * @param    <T>
    *           The enum type.
    * @param    flag
    *           The input enum.
    *
    * @return   The enum with the calculated value.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   @LegacySignature(returns = "OBJECT", type = Type.METHOD, name = "UnsetFlag", qualified = "progress.lang.flagsenum", parameters =
   {
      @LegacyParameter(name = "flag", type = "OBJECT", qualified = "progress.lang.flagsenum", mode = "INPUT")
   })
   @LegacyResourceSupport(supportLvl = CVT_LVL_FULL | RT_LVL_FULL)
   public <T extends FlagsEnum> object<T> unsetFlag(object<T> flag)
   {
      return bitwiseWorker(flag, (long op1, long op2) -> { return op1 & ~op2; }, "UnsetFlag"); 
   }
   
   /**
    * 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,
                   FlagsEnum::lookupNameWorker,
                   FlagsEnum::lookupNameWorkerSafe,
                   FlagsEnum::lookupValueWorker,
                   FlagsEnum::lookupValueWorkerSafe,
                   FlagsEnum::initialize,
                   FlagsEnum::compareValues,
                   legacyName);
   }
   
   /**
    * 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 OR a comma-separated list of enums to return.  If there
    *           is just one name then this will process an exact match only. For a comma-separated
    *           list of enum names, all results will be bitwise OR'd together.  Must not be
    *           {@code null}.
    *
    * @return   The enum instance with the value referenced by the provided name(s).
    */
   protected static <E extends LegacyEnum> E lookupNameWorker(EnumControlRecord<E> ecr, String name)
   {
      return nameHelper(ecr, LegacyEnum::lookupNameWorker, name);
   }
   
   /**
    * 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 OR a comma-separated list of enums to return.  If there
    *           is just one name then this will process an exact match only. For a comma-separated
    *           list of enum names, all results will be bitwise OR'd together.  Must not be
    *           {@code null}.
    * @param    trim
    *           Ignored because for flags enums trimming always occurs.
    *
    * @return   The enum instance with the value referenced by the provided name(s).
    */
   protected static <E extends LegacyEnum> E lookupNameWorkerSafe(EnumControlRecord<E> ecr,
                                                                  String               name,
                                                                  boolean              trim)
   {
      return nameHelper(ecr, FlagsEnum::lookupNameSafeAdapter, name);
   }
   
   /**
    * Return the enum instance of the given type which is associated with the provided value. This may be
    * an exact match or it can be a bitset match.  In bitset matching mode, an enum with the given value
    * will be returned so long as all bits in the value are also present in the maximal bitset of all enums.
    * This can generate an enum that has bits which don't match any existing enum (thus yielding a empty
    * string for the name).
    * <p>
    * This method also implements a 4GL quirk which excludes some negative values from being matched in
    * bitset mode.
    *
    * @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.
    *
    * @return   The enum instance with the provided value.
    */
   protected static <E extends LegacyEnum> E lookupValueWorker(EnumControlRecord<E> ecr, long value)
   {
      E enm = LegacyEnum.lookupValueWorkerSafe(ecr, value);
      
      if (enm != null)
      {
         return enm;
      }
      
      // the 4GL has a quirk for negative numbers processing which bypasses bitset matching
      if (!(ecr.negatives && !ecr.highNegs && value < 0))
      {
         // bitset matching mode
         if ((value & ecr.maximal) == value)
         {
            // all bits of our input value are also set in the maximal bitset
            FlagsEnum fe = (FlagsEnum) ecr.create.apply("", value);
            fe.multibit = true;
            
            enm = (E) fe;
         }
      }
      
      // now we can generate the error if needed
      if (enm == null)
      {
         generateNotFoundValue(ecr.cls, value);
      }
      
      return enm;
   }
   
   /**
    * Return the enum instance of the given type which is associated with the provided value. This may be
    * an exact match or it can be a bitset match of all enums which are a full subset of the given value.
    * In the bitset match it will be the bitwise OR of all matching enums.  Such a result will always have
    * at least one name in the list of names.
    * <p>
    * This is a very different algorithm from {@code lookupValueWorker} which can generate an enum that
    * has bits which don't match any existing enum (thus yielding a empty string for the name).
    *
    * @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.
    *
    * @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)
   {
      E enm = LegacyEnum.lookupValueWorkerSafe(ecr, value);
      
      if (enm != null)
      {
         return enm;
      }
      
      Predicate<E> include = (e) -> !((FlagsEnum)e).multibit && ((value & e.value) == e.value);
      
      // bitset matching mode; predefined multibit enums are excluded in the 4GL (such enums occur when
      // a comma-separated list of aliases is used)
      long result = getEnumsDefineOrder(ecr).stream()
                                            .filter(include)
                                            .mapToLong((e) -> e.value)
                                            .reduce(0L, (long o1, long o2) -> o1 | o2);
      
      if (result != 0)
      {
         FlagsEnum fe = (FlagsEnum) ecr.create.apply("", result);
         fe.multibit = true;
         
         enm = (E) fe;
      }
         
      return enm;
   }
   
   /**
    * 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    hlp
    *           The routine for per-name lookups.
    * @param    name
    *           The name of a single enum OR a comma-separated list of enums to return.  If there
    *           is just one name then this will process an exact match only. For a comma-separated
    *           list of enum names, all results will be bitwise OR'd together.  Must not be
    *           {@code null}.
    *
    * @return   The enum instance with the value referenced by the provided name(s).
    */
   protected static <E extends LegacyEnum> E nameHelper(EnumControlRecord<E>                        ecr,
                                                        BiFunction<EnumControlRecord<E>, String, E> hlp,
                                                        String                                      name)
   {
      String[] members = name.split(",");
      
      long value = 0L;
      
      for (int i = 0; i < members.length; i++)
      {
         E enm = hlp.apply(ecr, members[i]);
         
         if (enm == null)
         {
            // something went wrong
            return null;
         }
         
         // is there only one name instead of a list
         if (members.length == 1)
         {
            // exact match case
            return enm;
         }
         
         value |= enm.value;
      }
      
      FlagsEnum fe = (FlagsEnum) ecr.create.apply("", value);
      fe.multibit = true;
      
      return (E) fe;
   }
   
   /**
    * Render the instance as text.
    *
    * @return   The state of the instance as text.
    */
   private String dump()
   {
      return String.format("name %s; value %d (%016X); multibit %b; aliased %b; predefined %b",
                           name,
                           value,
                           value,
                           multibit,
                           aliased,
                           predefined);
   }
   
   /**
    * 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 OR a comma-separated list of enums to return.  If there
    *           is just one name then this will process an exact match only. For a comma-separated
    *           list of enum names, all results will be bitwise OR'd together.  Must not be
    *           {@code null}.
    *
    * @return   The enum instance with the value referenced by the provided name(s).
    */
   private static <E extends LegacyEnum> E lookupNameSafeAdapter(EnumControlRecord<E> ecr, String name)
   {
      return LegacyEnum.lookupNameWorkerSafe(ecr, name, true);
   }
   
   /**
    * 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 must be patterned. This only occurs when creating from an alias.
    */
   private static <E extends LegacyEnum> void initialize(EnumControlRecord<E> ecr, E created, E pattern)
   {
      ecr.flags = true;
      ecr.maximal |= created.value;
      
      if (created.value < 0)
      {
         ecr.negatives = true;
                                                                         
         // a single value that is in the HIGH_NEGATIVE_BOUNDARY range OR a set of values that combine there
         // will cause the quirk to be present
         if (created.value >= HIGH_NEGATIVE_BOUNDARY || ecr.maximal >= HIGH_NEGATIVE_BOUNDARY)
         {
            ecr.highNegs = true;
         }
      }
      
      FlagsEnum fe  = (FlagsEnum) created;
      
      fe.predefined = true;
      
      if (pattern != null)
      {
         FlagsEnum pat = (FlagsEnum) pattern;
         
         // alias mode copies some state
         fe.multibit = pat.multibit;
         fe.aliased  = true;
      }
      else
      {
         // non-alias mode may be multibit, we must detect that
         fe.multibit = isMultibit(created.value);
      }
   }
   
   /**
    * Detect if more than one bit is set in the given value.
    *
    * @param    value
    *           The bitfield to process.
    *
    * @return   {@code true} if more than one bit is set.
    */
   private static boolean isMultibit(long value)
   {
      if (value == 0)
         return false;
      
      int num  = 0;
      
      for (int i = 0; i < 64; i++)
      {
         long mask = 1L << i;
         
         if ((value & mask) == mask)
         {
            num++;
         }
         
         if (num > 1)
            return true;
      }
      
      return false;
   }
   
   /**
    * Flags enum comparator.  Sorts ascending from zero through the positive values and then followed by any
    * negative values in ascending order.
    *
    * @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)
   {
      if (e1.value < 0 && e2.value >= 0)
      {
         return 1;
      }
      else if (e1.value >= 0 && e2.value < 0)
      {
         return -1;
      }
      
      long diff = e1.value - e2.value;
      
      return (diff < 0) ? -1 : ( (diff > 0) ? 1 : 0 );
   }
   
   /**
    * Process the given binary bitwise operation with the given operands.  The resulting value will be
    * returned as an enum instance.  If the resulting value is an exact match to one of the operands or to
    * one of the predefined enums, that enum will be returned.  Otherwise the result will be dynamically
    * created as a new multibit enum.
    *
    * @param    <T>
    *           The enum type.
    * @param    op1
    *           The first operand.  It MUST be the same exact enum class as the current instance.
    * @param    op2
    *           The second operand.  It MUST be the same exact enum class as the current instance.
    * @param    bits
    *           The bitwise operation to perform.
    *
    * @return   The resulting enum.
    *
    * @throws   ErrorConditionException
    *           If an operand is unknown value or the operand classes don't match.
    */
   private static <T extends FlagsEnum> object<T> operator(object<T> op1, object<T> op2, BitwiseOperation bits)
   {
      if (op1 == null || op1.isUnknown())
      {
         unknownBitwiseOperation();
         
         return new object<>();
      }
      
      FlagsEnum enm = (FlagsEnum) op1.ref();
      
      return enm.bitwiseWorker(op2, bits, null); 
   }
   
   /**
    * Process the given binary bitwise operation with the current instance ({@code this}) as the first
    * operand and the {@code op2} as the second operand.  The resulting value will be returned as an
    * enum instance.  If the resulting value is an exact match to one of the operands or to one of the
    * predefined enums, that enum will be returned.  Otherwise the result will be dynamically created
    * as a new multibit enum.
    *
    * @param    <T>
    *           The enum type.
    * @param    op2
    *           The second operand.  It MUST be the same exact enum class as the current instance.
    * @param    bits
    *           The bitwise operation to perform.
    * @param    meth
    *           Method name for error messages or {@code null} if this call is for a bitwise operator.
    *
    * @return   The resulting enum.
    *
    * @throws   ErrorConditionException
    *           If {@code op2} is unknown value.
    */
   private <T extends FlagsEnum> object<T> bitwiseWorker(object<T> op2, BitwiseOperation bits, String meth)
   {
      if (op2 == null || op2.isUnknown())
      {
         if (meth != null)
         {
            generateInvalidValue(this.getClass(), meth);
         }
         else
         {
            unknownBitwiseOperation();
         }
         
         return new object<>();
      }
      
      T        input = op2.ref();
      Class<T> myCls = (Class<T>) this.getClass();
      
      // although the original code generates a compile failure, we must do this at runtime
      if (!myCls.equals(input.getClass()))
      {
         if (meth != null)
         {
            mismatchedInput(meth);
         }
         else
         {
            incompatibleDataTypes();
         }
      }

      long flagVal = bits.munge(value, input.value);
      
      // quick out if the resut is the same as op2
      if (flagVal == input.value)
      {
         return op2;
      }
      
      FlagsEnum enm = (value == flagVal) ? this : getExistingOrCreate((Class<FlagsEnum>) myCls, flagVal);

      return new object<T>((T) enm);
   }
   
   /**
    * For the given enum class and value, return any predefined instance which is an exact match or if no
    * match exists then create a new multibit instance for the given value (with no name).
    *
    * @param    cls
    *           The enum class to process.
    * @param    value
    *           The value to lookup and/or use in any create.
    *
    * @return   The matching or created enum instance.
    */
   private static FlagsEnum getExistingOrCreate(Class<FlagsEnum> cls, long value)
   {
      FlagsEnum enm = getExisting(cls, value);
      
      if (enm == null)
      {
         enm = (FlagsEnum) createWorker(cls, "", value);
         enm.multibit = true;
      }
      
      return enm;
   }
   
   /**
    * Generate an error 12905 (normally a compile error) to indicate that the method inputs were invalid.
    *
    * @param    meth
    *           The method name being processed.
    *
    * @throws   ErrorConditionException
    *           Always throws this.
    */
   private static void mismatchedInput(String meth)
   {
      ErrorManager.recordOrThrowError(12905, String.format(MISMATCHED_INPUT, meth));
   }
   
   /**
    * Generate an error 18210 (normally a compile error) to indicate that the operator inputs were invalid.
    *
    * @throws   ErrorConditionException
    *           Always throws this.
    */
   private static void unknownBitwiseOperation()
   {
      ErrorManager.recordOrThrowError(18210, "Cannot perform bitwise operation with the unknown value");
   }
   
   /**
    * Generate an error 223 (normally a compile error) when the operator inputs are the wrong type
    * (different enum classes when they must be the same exact enum type).
    *
    * @throws   ErrorConditionException
    *           Always throws this.
    */
   private static void incompatibleDataTypes()
   {
      ErrorManager.recordOrThrowError(223, "Incompatible data types in expression or assignment");
   }
   
   /**
    * Process two long values using some kind of bitwise operation and return the calculated value.
    */
   @FunctionalInterface
   private interface BitwiseOperation 
   {
      /**
       * Process operands and return the calculated value.
       *
       * @param    op1
       *           First operand.
       * @param    op2
       *           Second operand.
       *
       * @return   The calculated result.
       */
      long munge(long op1, long op2); 
   }   
}