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