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