Bit Flag Enums
Java enums are nice, but how can you use them as them as bit flags? In many other programming languages this is perfectly simple.
The Problem
As already told in this post about the values()
flaw in Java enums
I’m a big fan of the rich enums Java provides while other languages just
have enums which are basically constant named integers.
Why use enums for integer constants at all?
In my viewer project I use a lot of enums to represent internally used integer values to make the life of programmers who use my code a bit easier. Basically I could just add integer constants for given values instead of enums, but then there is always the problem for a user which constants she should use for a given setter method. This could be documented (who reads that?). Having enums introduces type safety and the possible values are inherently documented. Of course the enum should be documented, but this only happens once and not in each case it is used as a parameter or return value.
Think about
/** Unspecified line join style. */
public static final int JOIN_STYLE_NONE = 0;
/** Round line join style. */
public static final int JOIN_STYLE_ROUND = 1;
/** Angle line join style. */
public static final int JOIN_STYLE_ANGLE = 2;
/** Flat join style. */
public static final int JOIN_STYLE_FLAT = 3;
// [...]
/**
* Set the line join style.
* @param style the join style for lines, one of
* <ul>
* <li>{@link #JOIN_STYLE_NONE}</li>
* <li>{@link #JOIN_STYLE_ROUND}</li>
* <li>{@link #JOIN_STYLE_ANGLE}</li>
* <li>{@link #JOIN_STYLE_FLAT}</li>
* </ul>
*/
public void setJoinStyle(int style)
{
switch (style) {
case JOIN_STYLE_NONE:
case JOIN_STYLE_ROUND:
case JOIN_STYLE_ANGLE:
case JOIN_STYLE_FLAT:
this.joinStyle = style;
break;
default:
throw new IllegalArgumentException("Incorrect join style: "+style);
}
}
/**
* Get the line join style.
* @preturn the join style for lines, one of
* <ul>
* <li>{@link #JOIN_STYLE_NONE}</li>
* <li>{@link #JOIN_STYLE_ROUND}</li>
* <li>{@link #JOIN_STYLE_ANGLE}</li>
* <li>{@link #JOIN_STYLE_FLAT}</li>
* </ul>
*/
public int getJoinStyle()
{
return joinStyle;
}
versus
/** Line join style. */
public enum JoinStyle
{
/** No specific style. */
None,
/** Round join. */
Round,
/** Angle join. */
Angle,
/** Flat join. */
Flat;
}
// [...]
/**
* Set the line join style.
* @param style the join style for lines
*/
public void setJoinStyle(@NotNull JoinStyle style)
{
this.joinStyle = style;
}
/**
* Get the line join style.
* @return the join style for lines
*/
@NotNull
public JoinStyle getJoinStyle()
{
return joinStyle;
}
I hope you agree that the second solution is much preferable for several reasons.
Bit Flags enter the game
But coming from ancient times of programming the formats displayed in
my viewer also use a lot of bit flags. For languages using the constant
named integer enums I frowned upon above this is no problem. Mostly they
can directly mix integers and enums using all operators (especially
binary or
, and
, not
and xor
), or they need a bit of casting or
annotating (like the [Flags]
annotation in C#).
Java has nothing in that direction, so bad luck. Or a lot of work.
My Solution
Prepare for a long journey, this is not getting easy.
Design Goals
Immutablility
Nowadays I usually start my designs with immutable classes. Obviously this includes interface which just provide no methods for change. Immutable classes have several advantages:
- They are inherently thread-safe.
- You can easily define constants.
- You can cache values.
Of course there is one obvious disadvantage: you cannot change an object of an immutable class. So if you want a change, you’ll have to create a changed copy. So for larger objects which often change immutability can lead to performance problems because of the heavy copying involved.
Performance
Integers bit flags operations are very fast, as they usually can be performed directly on the processor. Anything based on enums will obviously be much slower, but I wanted the costs to be low.
Same is true for memory. Any bit flags field in my classes will use a
pointer, and an object it is pointing to. Compare that to the byte
which can already handle 8 bit flags.
Ease of Use
This includes that like in the enums code example above each method should make clear what it accepts without additional documentation (although the user has to grok the bit flag enum concept once).
Slow Start
Although it isn’t obvious when using integers the bit flag concept combines two different kind of objects: a bit flag representing a single bit and bit mask representing an array of these flags.
Bit masks are used as arrays of boolean values which usually have
the length of 8 (a byte
bit mask), 16 (a short
bit mask), 32
(an int
bit mask), or 64 (a long
bit mask). Of course usually not
all possible entries are used, and using
BitSet
much larger arrays could be created.
A bit flag is just the index of one bit in a mask.
At first we’ll keep to the low level of bit flags and masks, and marry them with the enums later.
BitFlag
Let’s start with the bit flag. This has to be an interface
, because
each enum bit flag object will have to implement it as we cannot
introduce a base class for enum
s. For the very same reason this interface
should be as simple as possible. We’ll just move the real work to the
mask for which we currently just know the type BitMask
as a placeholder.
/**
* A single bit flag.
*/
public interface BitFlag
{
/**
* Is this flag set in the given mask?
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
boolean isSetIn(@NotNull BitMask mask);
/**
* Set this flag in the given bit mask.
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
BitMask setIn(@NotNull BitMask mask);
/**
* Clear this flag in the given bit mask.
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
BitMask clearIn(@NotNull BitMask mask);
}
That’s nearly as easy as could be:
- check whether the bit flag is set
- set the bit flag
- clear the bit flag
The last two methods could be joined into one setter with an additional boolean parameter telling whether to set or to clear the flag, but I found the above slightly preferable.
SimpleBitFlag
A basic implementation of this interface is done in class SimpleBitFlag
:
/**
* A simple bit flag implementation.
*
* For efficiency reasons this class is not constructable,
* as the {@link SimpleBitFlag#get(int)} factory methods instead.
*
* This class is immutable.
*
* @author <a href="mailto:rammi@caff.de">Rammi</a>
*/
public final class SimpleBitFlag
implements BitFlag
{
private static final SimpleBitFlag[] STANDARD_FLAGS = new SimpleBitFlag[64];
static {
for (int p = 0; p < STANDARD_FLAGS.length; ++p) {
STANDARD_FLAGS[p] = new SimpleBitFlag(p);
}
}
private final int bitPosition;
/**
* Constructor.
* @param bitPosition bit flag position, non-negative
*/
private SimpleBitFlag(int bitPosition)
{
if (bitPosition < 0) {
throw new IllegalArgumentException("pos has to be non-negative!");
}
this.bitPosition = bitPosition;
}
/**
* Is this flag set in the given mask?
*
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
@Override
public boolean isSetIn(@NotNull BitMask mask)
{
return mask.isSet(bitPosition);
}
/**
* Set this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
@Override
public BitMask setIn(@NotNull BitMask mask)
{
return mask.set(bitPosition);
}
/**
* Clear this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
@Override
public BitMask clearIn(@NotNull BitMask mask)
{
return mask.clear(bitPosition);
}
/**
* Convert this flag into a bit mask.
*
* @return bit mask
*/
@NotNull
public BitMask toBitMask()
{
if (bitPosition < BitMask16.BIT_COUNT) {
return setIn(BitMask16.ZERO);
}
if (bitPosition < BitMask32.BIT_COUNT) {
return setIn(BitMask32.ZERO);
}
if (bitPosition < BitMask64.BIT_COUNT) {
return setIn(BitMask64.ZERO);
}
return setIn(new BitSetBitMask(bitPosition + 1));
}
/**
* Convert this flog into an integer bit mask.
* @return integer bit mask, possibly {@code 0} if this flag is outside of 32 bit range range
*/
public int toInt()
{
if (bitPosition < 32) {
return 0x01 << bitPosition;
}
return 0;
}
/**
* Convert this flog into a long bit mask.
* @return long bit mask, possibly {@code 0} if this flag is outside of 64 bit range
*/
public long toLong()
{
if (bitPosition < 64) {
return 0x01L << bitPosition;
}
return 0;
}
/**
* Get the bit position associated with this flag.
* @return bit position
*/
public int getBitPosition()
{
return bitPosition;
}
/**
* Get the simple bit flag for which the given index is set.
* @param index bit flag index
* @return simple bit flag which has the given bit index set
*/
@NotNull
public static SimpleBitFlag get(int index)
{
if (index < 0 || index >= STANDARD_FLAGS.length) {
return new SimpleBitFlag(index);
}
return STANDARD_FLAGS[index];
}
}
This already uses some of methods of the yet unknown BitMask
interface,
and is based on the fact that a bit flag is just a position in a bit mask,
This position is stored in field bitPosition
. Because we know that the
bit masks we’ll handle have restricted length, we can construct all
BitFlag
objects we are expecting directly as constants. Being constants,
the class has to be final
and immutable (it cannot be changed which we
enforced by making the only field final
, too).
As everything is constant, there is no constructor but a static factory
method get(int)
, which usually returns the appropriate constant
BitFlag
from the STANDARD_FLAGS
array. So the only expected memory
necessary to keep all bit flags in standard situation is just this 64
objects constructed once during class load. That is nice and efficient.
BitMask
As mentioned before the bit mask interface is the work horse, so it is a bit more complex. I decided for the following, which I’ll introduce step by step:
/**
* A set of bit flags.
*
* @author <a href="mailto:rammi@caff.de">Rammi</a>
*/
public interface BitMask
{
/**
* Is the flag at the given position set?
* @param pos position (non-negative)
* @return {@code true}: the flag is set<br/>
* {@code false}: the flag is not set
*/
boolean isSet(int pos);
/**
* Set the flag at the given position.
* @param pos position
* @return bit mask where the flag at the given position is set
*/
@NotNull
BitMask set(int pos);
/**
* Clear the flag at the given position.
* @param pos position
* @return bit mask where the flag at the given position is cleared
*/
@NotNull
BitMask clear(int pos);
These 3 methods correspond directly to the methods in the BitFlag
interface. Immutability of classes implementing this interface is kept in
mind, so the changing methods return a new BitMask
instead of setting
or clearing the bit directly.
/**
* Get the number of possible bits used in this flag.
* @return bit count
*/
int getBitCount();
In some cases the size of the bit mask is of interest, and that’s
getBitCount()
good for.
/**
* Get the number of bits set in this flag.
* @return set bit count
*/
int getCardinality();
/**
* Is no flag set?
* @return {@code true}: if no flag in this bit mask is set<br/>
* {@code false}: if any flag in this bit mask is set
*/
boolean isEmpty();
The numbers of set bits can come in handy some times, e.g. when we want
to convert a BitMask
into an array of BitFlag
s. isEmpty()
is the same
as a cardinatity of 0
, but can be faster calculated. Consider a bitmask
of 137
: you can easily tell that this is non-empty, but how many bits are
set is a bit more complex to tell.
/**
* Flip the flag at the given position.
* @param pos position
* @return bit mask where the flag at the given position is flipped
*/
@NotNull
BitMask flip(int pos);
The method flip()
is a convenience method which in my code is not used.
/**
* Get the inverse of this bit mask.
* @return inverse bit mask
*/
@NotNull
BitMask not();
/**
* Get the result of a logical <b>and</b> of this bit mask and another.
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
BitMask and(@NotNull BitMask other);
/**
* Get the result of a logical <b>and</b> of this bit mask and the inverse of another.
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
BitMask andNot(@NotNull BitMask other);
/**
* Get the result of a logical <b>or</b> of this bit mask and another.
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
BitMask or(@NotNull BitMask other);
/**
* Get the result of a logical <b>xor</b> (exclusive or) of this bit mask and another.
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
BitMask xor(@NotNull BitMask other);
The above methods allow to do the operations which are expected to be possible
with bit masks. Although some of them just seem to be for convenience as they could
be expressed by combining others, they are for efficiency to avoid calculations
of intermediate BitMask
objects.
/**
* Convert this bit mask into a bit set.
* @return bit set
*/
@NotNull
BitSet toBitSet();
/**
* Get the lower 32 bits of this mask.
* @return lower 32 bits as an int
*/
int low32();
/**
* Get the lower 64 bits of this mask.
* @return lower 64 bits
*/
long low64();
}
Finally we provide some cast methods allowing to handle the bit mask
as a standard BitSet
(this should always work), as a long
(only
keeping all information when the length of the mask is less than 64) or
as an int
(good for less than 32 bits). There is no comparable method
for short
or byte
, as these methods allow lost of information we can
easily use (short)mask.low32()
to get a short
if required.
BitMask Implementations
Basically all we need for implementing the BitMask
interface
would be a class which wraps or extends a standard BitSet
. But that would
be inefficient as we expect to have lots of BitMask
s. When each has its
individual BitSet
this would use a lot of memory. This is one of the
rare cases where we’ll sacrifice the
DRY Principle
and allow for some code duplication.
For fallback we’ll indeed implement a BitMask
based on BitSet
.
But we’ll also add BitMask
s based on the primitive types short
(BitMask16
), int
(BitMask32
) and long
(BitMask64
). These 3 will
have a lot of code duplication, so there’s no byte
based BitMask
to
avoid another duplication without much expected effect. Bit masks with
8 bits are not used by my formats, but if you need one it’s added easily
enough, which is the result of designing with interfaces.
BitSetBitMask
This is the fallback if someone really decides to use more than 64 bits
in her mask. We have to keep in mind that BitSet
is mutable, so it never
might appear on the outside of this class. As planned BitSetBitMask
itself is made immutable.
/**
* A bit set based bit mask.
* This allows for arbitrary sized bit counts.
* This class is immutable.
*
* @author <a href="mailto:rammi@caff.de">Rammi</a>
*/
public final class BitSetBitMask
implements BitMask
{
/** Bit set bit mask with no bits set. */
public static final BitSetBitMask ZERO = new BitSetBitMask(new BitSet());
Being immutable allows us to provide useful constants.
@NotNull
private final BitSet bitSet;
/**
* Optimized internal constructor.
* @param doClone clone parameter?
* @param bitSet flags to use
*/
private BitSetBitMask(boolean doClone,
@NotNull BitSet bitSet)
{
this.bitSet = doClone
? (BitSet)bitSet.clone()
: bitSet;
}
In some cases the bitSet
comes from the outside and needs cloning (or
someone could keep a reference to it, allowing changes), in other cases
it’s from inside and can be kept.
/**
* Constructor.
* @param bitSet flags on which this mask is based
*/
public BitSetBitMask(@NotNull BitSet bitSet)
{
this(true, bitSet);
}
/**
* Constructor.
* This creates a bit mask with a capacity for the given number of flags.
* @param flagCount flag count
*/
public BitSetBitMask(int flagCount)
{
this(false, new BitSet(flagCount));
}
Construction is either done from a BitSet
, or just from the maximum number
of bits. As we don’t expect to use this class at all, there are not other
convenience constructors.
/**
* Is the flag at the given position set?
*
* @param pos position (non-negative)
* @return {@code true}: the flag is set<br/>
* {@code false}: the flag is not set
*/
@Override
public boolean isSet(int pos)
{
return bitSet.get(pos);
}
/**
* Set the flag at the given position.
*
* @param pos position
* @return bit mask where the flag at the given position is set
*/
@NotNull
@Override
public BitMask set(int pos)
{
if (isSet(pos)) {
return this;
}
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.set(pos);
return new BitSetBitMask(false, newFlags);
}
/**
* Clear the flag at the given position.
*
* @param pos position
* @return bit mask where the flag at the given position is cleared
*/
@NotNull
@Override
public BitMask clear(int pos)
{
if (!isSet(pos)) {
return this;
}
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.set(pos);
return new BitSetBitMask(false, newFlags);
}
The set()
and clear()
methods hurt a bit, because a (possibly large)
BitSet
is cloned. But being immutable allows to return this
when nothing
is changed.
/**
* Get the number of possible bits used in this flag.
*
* @return bit count
*/
@Override
public int getBitCount()
{
return bitSet.size();
}
/**
* Get the number of bits set in this flag.
*
* @return set bit count
*/
@Override
public int getCardinality()
{
return bitSet.cardinality();
}
/**
* Is no flag set?
*
* @return {@code true}: if no flag in this bit mask is set<br/>
* {@code false}: if any flag in this bit mask is set
*/
@Override
public boolean isEmpty()
{
return bitSet.isEmpty();
}
/**
* Flip the flag at the given position.
*
* @param pos position
* @return bit mask where the flag at the given position is flipped
*/
@NotNull
@Override
public BitMask flip(int pos)
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.flip(pos);
return new BitSetBitMask(false, newFlags);
}
/**
* Get the inverse of this bit mask.
*
* @return inverse bit mask
*/
@NotNull
@Override
public BitMask not()
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.flip(0, newFlags.length());
return new BitSetBitMask(false, newFlags);
}
/**
* Get the result of a logical <b>and</b> of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask and(@NotNull BitMask other)
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.and(other.toBitSet());
return new BitSetBitMask(false, newFlags);
}
/**
* Get the result of a logical <b>and</b> of this bit mask and the inverse of another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask andNot(@NotNull BitMask other)
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.andNot(other.toBitSet());
return new BitSetBitMask(false, newFlags);
}
/**
* Get the result of a logical <b>or</b> of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask or(@NotNull BitMask other)
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.or(other.toBitSet());
return new BitSetBitMask(false, newFlags);
}
/**
* Get the result of a logical <b>xor</b> (exclusive or) of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask xor(@NotNull BitMask other)
{
BitSet newFlags = (BitSet)bitSet.clone();
newFlags.xor(other.toBitSet());
return new BitSetBitMask(false, newFlags);
}
/**
* Convert this bit mask into a bit set.
*
* @return bit set
*/
@NotNull
@Override
public BitSet toBitSet()
{
return (BitSet)bitSet.clone();
}
/**
* Get the lower 32 bits of this mask.
*
* @return lower 32 bits as an int
*/
@Override
public int low32()
{
return (int)low64();
}
/**
* Get the lower 64 bits of this mask.
*
* @return lower 64 bits
*/
@Override
public long low64()
{
return getBitCount() > 0
? bitSet.toLongArray()[0]
: 0L;
}
The above is straight forward and should be clear without comments.
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || !(o instanceof BitMask)) {
return false;
}
return BitMaskUtil.areEqual(this, (BitMask)o);
}
@Override
public int hashCode()
{
int hash = 0;
for (long v : bitSet.toLongArray()) {
hash ^= BitMaskUtil.getHash64(v);
}
return hash;
}
We want to be able to check bit masks in general, so comparing and
hashing is externalized in order to allow for handling all BitMasks
the same and not duplicate code.
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
* <p/>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
@Override
public String toString()
{
return String.format("<%s>", bitSet);
}
}
BitMask16
BitMask16
, BitMask32
and BitMask64
are quite similar, so we
just show the first one here.
/**
* A bit mask for 16 bit flags.
*
* Please note that this class will not handle more than
* 16 bits. Setting any higher bits will have no result.
*
* This class is immutable.
*
* @author <a href="mailto:rammi@caff.de">Rammi</a>
*/
public final class BitMask16
implements BitMask
{
/** The number of bits in this class. */
public static final int BIT_COUNT = 16;
/** The zero bit mask. This has all 16 flags unset. */
public static final BitMask16 ZERO = new BitMask16(0);
/** Mask for the bits in this mask. */
private static final int MASK = 0xFFFF;
/** The bit mask where all 16 bits are set. */
public static final BitMask16 ALL_SET = new BitMask16(MASK);
Again we can provide some useful constants.
BIT_COUNT
and ZERO
indeed were already used in method toBitMask()
of SimpleBitFlag
.
private final short flags;
/**
* Constructor.
* @param flags bit mask, only the 16 low order bits are used
*/
public BitMask16(int flags)
{
this.flags = (short)(flags & MASK);
}
int
is used for convenience, otherwise each constructor call with a constant value would
need a caset.
/**
* Is the flag at the given position set?
*
* @param pos position (non-negative)
* @return {@code true}: the flag is set<br/>
* {@code false}: the flag is not set
*/
@Override
public boolean isSet(int pos)
{
if (pos < 0) {
throw new IllegalArgumentException("pos needs to be non-negative!");
}
return pos < BIT_COUNT && (flags >>> pos & 0x01) != 0;
}
/**
* Set the flag at the given position.
*
* @param pos position (non-negative)
* @return bit mask where the flag at the given position is set
*/
@NotNull
@Override
public BitMask set(int pos)
{
if (pos < 0) {
throw new IllegalArgumentException("pos needs to be non-negative!");
}
if (isSet(pos) || pos >= BIT_COUNT) {
return this;
}
return new BitMask16(flags | (0x0001 << pos));
}
/**
* Clear the flag at the given position.
*
* @param pos position
* @return bit mask where the flag at the given position is cleared
*/
@NotNull
@Override
public BitMask clear(int pos)
{
if (pos < 0) {
throw new IllegalArgumentException("pos needs to be non-negative!");
}
if (!isSet(pos) || pos >= BIT_COUNT) {
return this;
}
return new BitMask16(flags & ~(0x0001 << pos));
}
The class is expected to be included in a library used by others, so we
explicitly raise an exception for ‘pos < 0’. In local code an assert
would be more efficient, but only if assertions are enabled.
/**
* Get the number of possible bits used in this flag.
*
* @return bit count
*/
@Override
public int getBitCount()
{
return BIT_COUNT;
}
/**
* Get the number of bits set in this flag.
*
* @return set bit count
*/
@Override
public int getCardinality()
{
return Integer.bitCount(flags & MASK);
}
/**
* Is no flag set?
*
* @return {@code true}: if no flag in this bit mask is set<br/>
* {@code false}: if any flag in this bit mask is set
*/
@Override
public boolean isEmpty()
{
return (flags & MASK) == 0;
}
/**
* Flip the flag at the given position.
*
* @param pos position
* @return bit mask where the flag at the given position is flipped
*/
@NotNull
@Override
public BitMask flip(int pos)
{
if (pos < 0) {
throw new IllegalArgumentException("pos needs to be non-negative!");
}
if (pos >= BIT_COUNT) {
return this;
}
return new BitMask16(flags ^ ~(0x0001 << pos));
}
/**
* Get the inverse of this bit mask.
*
* @return inverse bit mask
*/
@NotNull
@Override
public BitMask not()
{
return new BitMask16(~flags);
}
/**
* Get the result of a logical <b>and</b> of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask and(@NotNull BitMask other)
{
if (other.getBitCount() > BIT_COUNT) {
return other.and(this);
}
int combined = flags & other.low32();
if (combined == flags) {
return this;
}
return new BitMask16(combined);
}
/**
* Get the result of a logical <b>and</b> of this bit mask and the inverse of another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask andNot(@NotNull BitMask other)
{
if (other.getBitCount() > BIT_COUNT) {
return other.not().and(this);
}
int combined = flags | ~other.low32();
if (combined == flags) {
return this;
}
return new BitMask16(combined);
}
/**
* Get the result of a logical <b>or</b> of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask or(@NotNull BitMask other)
{
if (other.getBitCount() > BIT_COUNT) {
return other.or(this);
}
int combined = flags | other.low32();
if (combined == flags) {
return this;
}
return new BitMask16(combined);
}
/**
* Get the result of a logical <b>xor</b> (exclusive or) of this bit mask and another.
*
* @param other other bit mask
* @return resulting bit mask
*/
@NotNull
@Override
public BitMask xor(@NotNull BitMask other)
{
if (other.getBitCount() > BIT_COUNT) {
return other.xor(this);
}
int combined = flags ^ other.low32();
if (combined == flags) {
return this;
}
return new BitMask16(combined);
}
All bit mask combination methods possibly forward the operation to the other operand if it is larger (has more bits). Thus no information gets lost.
/**
* Convert this bit mask into a bit set.
*
* @return bit set
*/
@NotNull
@Override
public BitSet toBitSet()
{
return BitSet.valueOf(new long[] { low64() });
}
/**
* Get the lower 32 bits of this mask.
*
* @return lower 32 bits as an int
*/
@Override
public int low32()
{
return flags & MASK;
}
/**
* Get the lower 64 bits of this mask.
*
* @return lower 64 bits
*/
@Override
public long low64()
{
return flags & MASK;
}
@Override
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || !(o instanceof BitMask)) {
return false;
}
return BitMaskUtil.areEqual(this, (BitMask)o);
}
@Override
public int hashCode()
{
return BitMaskUtil.getHash64(low64());
}
/**
* Returns a string representation of the object. In general, the
* {@code toString} method returns a string that
* "textually represents" this object. The result should
* be a concise but informative representation that is easy for a
* person to read.
* It is recommended that all subclasses override this method.
* <p/>
* The {@code toString} method for class {@code Object}
* returns a string consisting of the name of the class of which the
* object is an instance, the at-sign character `{@code @}', and
* the unsigned hexadecimal representation of the hash code of the
* object. In other words, this method returns a string equal to the
* value of:
* <blockquote>
* <pre>
* getClass().getName() + '@' + Integer.toHexString(hashCode())
* </pre></blockquote>
*
* @return a string representation of the object.
*/
@Override
public String toString()
{
return String.format("<%02x>", flags);
}
}
Marrying Enums
After some work we can now handle bit flags and bit masks and combine them.
We have obeyed our design goal of immutability. The performance
goal is not missed that much, as in typical situations all we have are
64 SimpleBitFlag
s which are passed around, and BitMask16
or BitMask32
which are just immutable wrappers around short
or int
like java.lang.Short
and java.lang.Integer
.
We can even make an enum
implement BitFlag
, forward that internally
to a SimpleBitFlag
.
Here’s an example:
/** Associativity flags. */
public enum AssociativityFlags
implements BitFlag
{
/** First point is referenced. */
FirstPointReference(0),
/** Second point is referenced. */
SecondPointReference(1),
/** Third point is referenced. */
ThirdPointReference(2),
/** Fourth point is referenced. */
FourthPointReference(3);
@NotNull
private final BitFlag flag;
/**
* Constructor.
* @param bit represented bit id
*/
AssociativityFlags(int bit)
{
flag = SimpleBitFlag.get(bit);
}
/**
* Is this flag set in the given mask?
*
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
@Override
public boolean isSetIn(@NotNull BitMask mask)
{
return flag.isSetIn(mask);
}
/**
* Set this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
@Override
public BitMask setIn(@NotNull BitMask mask)
{
return flag.setIn(mask);
}
/**
* Clear this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
@Override
public BitMask clearIn(@NotNull BitMask mask)
{
return flag.clearIn(mask);
}
For a given reason this is not yet the whole implementation.
The reason is ease of use. We have our enums, but we use BitMask16
or
BitMask32
for the bit mask values. There is no way to differ from
a bitmask using the above AssociativityFlags
to a bit mask using something
else. So if a method returns a BitMask
we have no way to know which kind
of flags it contains or allows. So we have the same situation which we had
with using simple integers, but with much more effort. Time for a final step:
we need a BitMask which knows the kind of enum bit flag it is handling.
We’ll call that one EnumBitMask
, but before we introduce it we’ll finish
the AssociativityFlags
.
/** Cached values. */
private static final AssociativityFlags[] VALUES = values();
/** Bit mask where not flag is set. */
public static final EnumBitMask<AssociativityFlags> NONE = EnumBitMask.NONE32.cast(AssociativityFlags.class);
/**
* Get the enum bit mask associated with the given internal value.
* @param internalValue internal value
* @return associated bit mask
*/
@NotNull
public static EnumBitMask<AssociativityFlags> fromInternal(int internalValue)
{
return EnumBitMask.get32(internalValue, AssociativityFlags.class);
}
/**
* Get the flags which are set in the given mask.
* @param mask mask to check
* @return set flags
*/
@NotNull
public static List<AssociativityFlags> getAllSetFrom(@NotNull EnumBitMask<AssociativityFlags> mask)
{
return mask.getAllSet(VALUES);
}
}
(Check here why the VALUES
constant appears.)
Obviously the EnumBitMask has to be a generic class, otherwise we’d have
to implenent a specialized class for each enum. For the understanding it
is good to know that the flags represented by AssociativityFlags
are stored
with 32 bit internally. That’s the reason for the 32
appearing twice.
The NONE
constant represents the special value where no flag is set.
We’ll learn later that the cast()
is just used to fool the compiler,
it will always return the same object.
The method fromInternal()
is used to convert an internal integer value
into our typed `EnumBitMask
Of course adding all this stuff to each bit flag enum is tedious error-prone work. Luckily modern IDEs like IntelliJ IDEA which I am using allow to create templates. So I added templates for Enum BitFlag 16 and Enum BitFlag 32. The first looks like
#parse("File Header.java")
#if (${PACKAGE_NAME} && ${PACKAGE_NAME} != "")package ${PACKAGE_NAME};#end
import de.caff.annotation.NotNull;
import de.caff.util.*;
#parse("Class Comment.java")
public enum ${NAME}
implements BitFlag
{
;
/** Enum bit of this flags where no flag is set. */
@NotNull
public static final EnumBitMask<${NAME}> NONE = EnumBitMask.NONE16.cast(${NAME}.class);
/** Wrapped real bit flag. */
@NotNull
private final BitFlag flag;
/**
* Constructor.
* @param flagIndex index of flag
*/
${NAME}(int flagIndex)
{
flag = SimpleBitFlag.get(flagIndex);
}
/**
* Is this flag set in the given mask?
*
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
@Override
public boolean isSetIn(@NotNull BitMask mask)
{
return flag.isSetIn(mask);
}
/**
* Set this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
@Override
public BitMask setIn(@NotNull BitMask mask)
{
return flag.setIn(mask);
}
/**
* Clear this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
@Override
public BitMask clearIn(@NotNull BitMask mask)
{
return flag.clearIn(mask);
}
/**
* Get the enum bit mask associated with a given internal value.
* @param internalValue internal value (only the lower 16 bits are used)
* @return associated bit mask
*/
@NotNull
public static EnumBitMask<${NAME}> fromInternal(int internalValue)
{
return EnumBitMask.get16(internalValue, ${NAME}.class);
}
}
This keeps care of most of the boilerplate code. The parts included
by #parse
are already shown here.
The EnumBitMask class
The EnumBitMask
will forward all oerations to a simple BitMask
, which allows
it to be ignorant of the width (16bit, 32bit etc.).
/**
* Type safe bit flags.
* This class is immutable.
*
* @author <a href="mailto:rammi@caff.de">Rammi</a>
* @param <E> the enum bit flag type for which this mask is valid
*/
public final class EnumBitMask<E extends Enum<E> & BitFlag>
{
/** Key mask for excluded keys in 32bit cache. */
private static final int EXCLUDE_MASK32 = 0xFFFFFFFF << Utility.getIntParameter("ebm.mask32.shift",
16);
/** The underlying simple bit mask. */
@NotNull
private final BitMask bitMask;
/**
* Constructor.
* @param bitMask initial flags
*/
public EnumBitMask(@NotNull BitMask bitMask)
{
this.bitMask = bitMask;
}
/**
* Get the underlying bit mask,
* @return bit mask
*/
@NotNull
public BitMask getBitMask()
{
return bitMask;
}
The basic methods (checking a flag, setting it or clearing it) are just forwarded to
the wrapped BitMask
. Because we have just this one class, it’s also useful to add a
bunch of convenience methods. Because of the immutablility changes will always create
a new EnumBitMask
, so we check whether changes really do happen and return this
otherwise.
/**
* Is the given flag set?
* @param flag flag to check
* @return {@code true}: the flag is set<br/>
* {@code false}: the flag is not set
*/
public boolean isSet(@NotNull E flag)
{
return flag.isSetIn(bitMask);
}
/**
* Is any flag in both this and the other bit mask set?
* @param other other bit mask
* @return {@code true}: if any bit is set in both masks<br/>
* {@code false}: if no bit is set in both masks
*/
public boolean isSetAny(@NotNull EnumBitMask<E> other)
{
return !other.getBitMask().and(getBitMask()).isEmpty();
}
/**
* Are all flags from the other bit mask set?
* @param other other bit mask
* @return {@code true}: if any bit is set in both masks<br/>
* {@code false}: if no bit is set in both masks
*/
public boolean isSetAll(@NotNull EnumBitMask<E> other)
{
final BitMask combined = other.getBitMask().and(getBitMask());
return BitMaskUtil.areEqual(combined, bitMask);
}
/**
* Is any flag from the given sequence set?
* @param flags flags to check
* @return {@code true}: if any bit flag is set in this masks<br/>
* {@code false}: if no bit flag is set in this masks
*/
@SafeVarargs
public final boolean isSetAny(E... flags)
{
for (E flag : flags) {
if (isSet(flag)) {
return true;
}
}
return false;
}
/**
* Is any flag from the given collection set?
* @param flags flags to check
* @return {@code true}: if any bit flag is set in this masks<br/>
* {@code false}: if no bit flag is set in this masks
*/
public boolean isSetAny(@NotNull Collection<E> flags)
{
for (E flag : flags) {
if (isSet(flag)) {
return true;
}
}
return false;
}
/**
* Are all flags from the given sequence set?
* @param flags flags to check
* @return {@code true}: if every bit flag is set in this masks<br/>
* {@code false}: if any bit flag is not set in this masks
*/
@SafeVarargs
public final boolean isSetAll(E... flags)
{
for (E flag : flags) {
if (!isSet(flag)) {
return false;
}
}
return true;
}
/**
* Are all flags from the given collection set?
* @param flags flags to check
* @return {@code true}: if every bit flag is set in this masks<br/>
* {@code false}: if any bit flag is not set in this masks
*/
public boolean isSetAll(@NotNull Collection<E> flags)
{
for (E flag : flags) {
if (!isSet(flag)) {
return false;
}
}
return true;
}
/**
* Get a bit mask where all flags are set which are set in this or in another one.
* @param other other bit mask defining the flags which are set
* @return this bit mask, but with set flags
* @see #and(EnumBitMask)
* @see #clear(EnumBitMask)
*/
public EnumBitMask<E> or(@NotNull EnumBitMask<E> other)
{
final BitMask combined = bitMask.or(other.getBitMask());
return combined.equals(bitMask)
? this
: new EnumBitMask<E>(combined);
}
/**
* Get a bit mask where all flags are set which are set in this and in another one.
* @param other other bit mask defining the flags which are set
* @return this bit mask, but with set flags
* @see #or(EnumBitMask)
* @see #clear(EnumBitMask)
*/
public EnumBitMask<E> and(@NotNull EnumBitMask<E> other)
{
final BitMask combined = bitMask.and(other.getBitMask());
return combined.equals(bitMask)
? this
: new EnumBitMask<E>(combined);
}
/**
* Set the given flag.
* @param flag flag to set
* @return bit mask where the given flag is set
*/
@NotNull
public EnumBitMask<E> set(@NotNull E flag)
{
if (isSet(flag)) {
return this;
}
return new EnumBitMask<>(flag.setIn(bitMask));
}
/**
* Set or clear the given flag.
* @param flag flag to change
* @param onOff {@code true}: set the flag<br/>
* {@code false}: clear the flag
* @return bit mask with flag change appropriately
*/
public EnumBitMask<E> setTo(@NotNull E flag, boolean onOff)
{
return onOff
? set(flag)
: clear(flag);
}
/**
* Set all given flags.
* @param flags flags to set
* @return bit mask, where the flags of this mask and the given flags are set
*/
@SafeVarargs
@NotNull
public final EnumBitMask<E> setAll(@NotNull E... flags)
{
BitMask combined = bitMask;
for (E flag : flags) {
combined = flag.setIn(combined);
}
return combined.equals(bitMask)
? this
: new EnumBitMask<E>(combined);
}
/**
* Clear the given flag.
* @param flag flag to clear
* @return bit flags where the given flag is cleared
*/
@NotNull
public EnumBitMask<E> clear(@NotNull E flag)
{
if (!isSet(flag)) {
return this;
}
return new EnumBitMask<>(flag.clearIn(bitMask));
}
/**
* Get a bit mask where all flags are cleared which are set in another one.
* @param other other bit mask defining the flags which are cleared
* @return this bit mask, but with cleared flags
* @see #and(EnumBitMask)
* @see #or(EnumBitMask)
*/
public EnumBitMask<E> clear(@NotNull EnumBitMask<E> other)
{
final BitMask combined = getBitMask().andNot(other.getBitMask());
return combined.equals(bitMask)
? this
: new EnumBitMask<E>(combined);
}
/**
* Clear all given flags.
* @param flags flags to clear
* @return bit mask, with the flags of this mask where all given flags are cleared
*/
@SafeVarargs
@NotNull
public final EnumBitMask<E> clearAll(@NotNull E... flags)
{
BitMask combined = bitMask;
for (E flag : flags) {
combined = flag.clearIn(combined);
}
return combined.equals(bitMask)
? this
: new EnumBitMask<E>(combined);
}
/**
* Flip the given flag.
* @param flag flag to flip
* @return bit flags where the given flag is inverted
*/
@NotNull
public EnumBitMask<E> flip(@NotNull E flag)
{
return isSet(flag)
? new EnumBitMask<E>(flag.clearIn(bitMask))
: new EnumBitMask<E>(flag.setIn(bitMask));
}
/**
* Flip all given flags.
* @param flags flags to flip
* @return bit mask, with the flags of this mask where all given flags are flipped
*/
@SafeVarargs
@NotNull
public final EnumBitMask<E> flipAll(@NotNull E... flags)
{
BitMask combined = bitMask;
for (E flag : flags) {
if (flag.isSetIn(combined)) {
combined = flag.clearIn(combined);
}
else {
combined = flag.setIn(combined);
}
}
return new EnumBitMask<E>(combined);
}
/**
* Get a list of all flags which are set in this mask.
* @param flags flags to check
* @return a list of all checked flags which are set in this mask
*/
@NotNull
@SafeVarargs
public final List<E> getAllSet(E... flags)
{
final List<E> result = new ArrayList<>(flags.length);
for (E f : flags) {
if (isSet(f)) {
result.add(f);
}
}
return result;
}
/**
* Get a list of all flags which are set in this mask.
* @param flags flags to check
* @return ma list of all checked flags which are set in this maks
*/
@NotNull
public List<E> getAllSet(@NotNull Iterable<E> flags)
{
final List<E> result = new LinkedList<>();
for (E f : flags) {
if (isSet(f)) {
result.add(f);
}
}
return result;
}
/**
* Get a list of all flags which are not set in this mask.
* @param flags flags to check
* @return a list of all checked flags which are not set in this mask
*/
@NotNull
@SafeVarargs
public final List<E> getAllCleared(E... flags)
{
final List<E> result = new ArrayList<>(flags.length);
for (E f : flags) {
if (!isSet(f)) {
result.add(f);
}
}
return result;
}
/**
* Get a list of all flags which are not set in this mask.
* @param flags flags to check
* @return ma list of all checked flags which are not set in this maks
*/
@NotNull
public List<E> getAllCleared(@NotNull Iterable<E> flags)
{
final List<E> result = new LinkedList<>();
for (E f : flags) {
if (!isSet(f)) {
result.add(f);
}
}
return result;
}
/**
* Is no flag set?
* @return {@code true}: if no flag in this bit mask is set<br/>
* {@code false}: if any flag in this bit mask is set
*/
public boolean isEmpty()
{
return bitMask.isEmpty();
}
As generics work in Java, an EnumBitMask
object is completely ignorant of the
enum value it represents. Therefore we can easily cast an EnumBitNask
for one
enum type to another one using the same object. So the next method basically does
nothing and is only necessary to keep the compiler happy.
/**
* Cast this enum bit mask to one using a different kind of bit flags.
* @param enumClass new enum bit flag class
* @param <F> type of enum bit flag class
* @return enum bit mask with same bit flags, but different enum flag type
*/
@SuppressWarnings("unchecked")
public <F extends Enum<F> & BitFlag> EnumBitMask<F> cast(@NotNull Class<F> enumClass)
{
// no need to create a new object, as this class is immutable,
// and the casting is only necessary to keep the compiler happy
return (EnumBitMask<F>)this;
}
To avoid creating EnumBitMask
objects representing the same value we cache already
created values. The unique()
methods return the cached values with the same flags set.
/**
* Get the unique object representing the given 16bit flags.
* This is an optimization which shares all enum bit masks with 16 bits.
* @return unique bitmask object representing the same flags as this one
*/
@SuppressWarnings("unchecked")
public EnumBitMask<E> unique16()
{
// cast is okay because all 16bit flag classes share the same underlying objects
return getCachedEnumBitMask16(this);
}
/**
* Get the unique object representing the given 32bit flags.
* This is an optimization which shares many enum bit masks with 32 bits.
* @return unique bitmask object representing the same flags as this one
*/
@SuppressWarnings("unchecked")
public EnumBitMask<E> unique32()
{
// cast is okay because all 16bit flag classes share the same underlying objects
return getCachedEnumBitMask32(this);
}
/**
* Convert a flag to a generic bit mask.
*
* This internally uses a bit set bit mask, which allows for all possible flags.
* @param flag flag to convert
* @param <F> type of bit flag
* @return bit mask where bit flag is set
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> toMask(@NotNull F flag)
{
return new EnumBitMask<F>(flag.setIn(BitSetBitMask.ZERO));
}
/**
* Convert a flag to a 16 bit bit mask.
*
* @param flag flag to convert
* @param <F> type of bit flag
* @return bit mask where bit flag is set
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> toMask16(@NotNull F flag)
{
return new EnumBitMask<F>(flag.setIn(BitMask16.ZERO)).unique16();
}
/**
* Convert a flag to a 32 bit bit mask.
*
* @param flag flag to convert
* @param <F> type of bit flag
* @return bit mask where bit flag is set
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> toMask32(@NotNull F flag)
{
return new EnumBitMask<F>(flag.setIn(BitMask32.ZERO)).unique32();
}
/**
* Convert a flag to a 64 bit bit mask.
*
* @param flag flag to convert
* @param <F> type of bit flag
* @return bit mask where bit flag is set
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> toMask64(@NotNull F flag)
{
return new EnumBitMask<F>(flag.setIn(BitMask64.ZERO));
}
/**
* Divide a bit mask into a set of flags.
* Only valid bits (i.e. bits which have a flag counterpart) will be considered.
* @param mask bit mask
* @param enumClass class of the enum flags in this mask, necessary to overcome
* the shortcomings of Java generics
* @param <F> type of bit flag
* @return set of bit flags which are set in the given mask
* @see #combine(Collection)
* @see #combine16(Collection)
* @see #combine32(Collection)
* @see #combine64(Collection)
*/
public static <F extends Enum<F> & BitFlag> Set<F> toFlags(@NotNull EnumBitMask<F> mask,
@NotNull Class<F> enumClass)
{
Set<F> result = EnumSet.noneOf(enumClass);
for (F flag : enumClass.getEnumConstants()) {
if (mask.isSet(flag)) {
result.add(flag);
}
}
return result;
}
/**
* Combine some flags into a bit mask.
* @param zero zero bit mask fitting for the given type
* @param bitFlags bit flags to combine
* @param <F> enum flag type
* @return combined mask
*/
private static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine(@NotNull BitMask zero,
F[] bitFlags)
{
EnumBitMask<F> mask = new EnumBitMask<>(zero);
for (F f : bitFlags) {
mask = mask.set(f);
}
return mask;
}
/**
* Combine some flags into a bit mask.
* @param zero zero bit mask fitting for the given type
* @param bitFlags bit flags to combine
* @param <F> enum flag type
* @return combined mask
*/
private static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine(@NotNull BitMask zero,
@NotNull Collection<F> bitFlags)
{
EnumBitMask<F> mask = new EnumBitMask<>(zero);
for (F f : bitFlags) {
mask = mask.set(f);
}
return mask;
}
/**
* Combine bit flags using at maximum 16 bits into a bit mask.
* @param bitFlags bit flags
* @param <F> bit flag type
* @return bit mask
* @see #get16(Enum[])
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine16(F ... bitFlags)
{
return combine(BitMask16.ZERO, bitFlags);
}
/**
* Combine bit flags using at maximum 16 bits into a bit mask.
* @param bitFlags bit flags to combine
* @param <F> bit flag type
* @return bit mask
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine16(@NotNull Collection<F> bitFlags)
{
return combine(BitMask16.ZERO, bitFlags);
}
/**
* Combine bit flags using at maximum 32 bits into a bit mask.
* @param bitFlags bit flags
* @param <F> bit flag type
* @return bit mask
* @see #get32(Enum[])
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine32(F ... bitFlags)
{
return combine(BitMask32.ZERO, bitFlags);
}
/**
* Combine bit flags using at maximum 32 bits into a bit mask.
* @param bitFlags bit flags to combine
* @param <F> bit flag type
* @return bit mask
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine32(@NotNull Collection<F> bitFlags)
{
return combine(BitMask32.ZERO, bitFlags);
}
/**
* Combine bit flags using at maximum 64 bits into a bit mask.
* @param bitFlags bit flags
* @param <F> bit flag type
* @return bit mask
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine64(F ... bitFlags)
{
return combine(BitMask64.ZERO, bitFlags);
}
/**
* Combine bit flags using at maximum 64 bits into a bit mask.
* @param bitFlags bit flags
* @param <F> bit flag type
* @return bit mask
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine64(@NotNull Collection<F> bitFlags)
{
return combine(BitMask64.ZERO, bitFlags);
}
/**
* Combine bit flags using an arbitrary number of bits into a bit mask.
* @param bitFlags bit flags
* @param <F> bit flag type
* @return bit mask
*/
@SafeVarargs
@SuppressWarnings("varargs")
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine(F ... bitFlags)
{
return combine(BitSetBitMask.ZERO, bitFlags);
}
/**
* Combine bit flags using an arbitrary number of bits into a bit mask.
* @param bitFlags bit flags to combine
* @param <F> bit flag type
* @return bit mask
*/
public static <F extends Enum<F> & BitFlag> EnumBitMask<F> combine(@NotNull Collection<F> bitFlags)
{
return combine(BitSetBitMask.ZERO, bitFlags);
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object o)
{
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EnumBitMask<E> that = (EnumBitMask<E>)o;
return bitMask.equals(that.bitMask);
}
@Override
public int hashCode()
{
return bitMask.hashCode();
}
/**
* Get a string representation.
* @return string representiation
*/
public String toString()
{
return bitMask.toString();
}
As said earlier, theEnumBitMask
object is completely ignorant of the enum it represents.
In the following we create a useless class used as the enum in our constant or cached values.
We’ll potentially cache all 65536 16bit values, although experience shows that most bit flags use only
the lower bits. For 32bit values we will restrict ourselves to some lower bits, and accept that
bit masks which high bits set don’t share their values. This is a tradeoff, and it depends on the usage
whether caching or duplicating is better. The Java property "ebm.mask32.shift"
allows to override the
default using only the lower 16 bit. Long enum values are currently not cached, because in my scenario thay
don’t appear.
/** Have an enum for fooling the compiler. */
private enum Potёmkin
implements BitFlag
{
;
/**
* Is this flag set in the given mask?
*
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
@Override
public boolean isSetIn(@NotNull BitMask mask)
{
return false;
}
/**
* Set this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
@Override
public BitMask setIn(@NotNull BitMask mask)
{
return mask;
}
/**
* Clear this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
@Override
public BitMask clearIn(@NotNull BitMask mask)
{
return mask;
}
}
/** 16 bit mask with no flag set. */
public static final EnumBitMask<?> NONE16 = new EnumBitMask<Potёmkin>(BitMask16.ZERO);
/** 32 bit mask with no flag set. */
public static final EnumBitMask<?> NONE32 = new EnumBitMask<Potёmkin>(BitMask32.ZERO);
/** 64 bit mask with no flag set. */
public static final EnumBitMask<?> NONE64 = new EnumBitMask<Potёmkin>(BitMask64.ZERO);
/** Cached values using 16bit masks. */
private static final Map<Short, EnumBitMask<?>> CACHE16 = new HashMap<>();
static {
CACHE16.put((short)0, NONE16);
CACHE16.put((short)-1, new EnumBitMask<>(new BitMask16(-1)));
/** Insert common values for and operation. */
for (int shift = 1; shift < 16; ++shift) {
final int value = (1 << shift) - 1;
CACHE16.put((short)value, new EnumBitMask<>(new BitMask16(value)));
}
}
/**
* Get a cached 16bit mask for the given value.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param value value (an int only for convenience, only the low 16bits are used)
* @param type enum type of returned
* @return matching bit mask
*/
@NotNull
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get16(int value, @NotNull Class<T> type)
{
final Short key = (short)value;
synchronized (CACHE16) {
EnumBitMask<?> mask = CACHE16.get(key);
if (mask == null) {
mask = new EnumBitMask<T>(new BitMask16(value));
CACHE16.put(key, mask);
}
return mask.cast(type);
}
}
/**
* Get a cached 16bit mask for a combination of flags.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param flags flags to combine
* @return matching bit mask
*/
@SafeVarargs
@NotNull
@SuppressWarnings({"unchecked", "varargs"})
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get16(T ... flags)
{
final EnumBitMask<T> mask = combine16(flags);
return getCachedEnumBitMask16(mask);
}
/**
* Get the cached value with the same settings as a given 16 bit mask.
* @param mask bit mask using 16 bits
* @param <T> bit flag type
* @return cached mask
*/
@NotNull
@SuppressWarnings("unchecked")
private static <T extends Enum<T> & BitFlag> EnumBitMask<T> getCachedEnumBitMask16(@NotNull EnumBitMask<T> mask)
{
final Short key = (short)mask.getBitMask().low32();
synchronized (CACHE16) {
final EnumBitMask<?> cachedMask = CACHE16.get(key);
if (cachedMask == null) {
CACHE16.put(key, mask);
return mask;
}
// all enum bit masks with the same width and value are internally equal
return (EnumBitMask<T>)cachedMask;
}
}
/**
* Get a cached 16bit mask for a combination of flags.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param mask basic mask
* @param flags flags to combine with mask
* @return combined bit mask
*/
@SafeVarargs
@NotNull
@SuppressWarnings("unchecked")
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get16(@NotNull EnumBitMask<T> mask,
T ... flags)
{
for (T flag : flags) {
mask = mask.set(flag);
}
return getCachedEnumBitMask16(mask);
}
/** Cached values using 32bit masks. */
private static final Map<Integer, EnumBitMask<?>> CACHE32 = new HashMap<>();
static {
CACHE32.put(0, NONE32);
CACHE32.put(-1, new EnumBitMask<>(new BitMask32(-1)));
/** Insert common values for and operation. */
for (int shift = 1; shift < 32; ++shift) {
final int value = (1 << shift) - 1;
CACHE32.put(value, new EnumBitMask<>(new BitMask32(value)));
}
}
/**
* Get a cached 32bit mask for the given value.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param value value
* @param type enum type of returned
* @return matching bit mask
*/
@NotNull
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get32(@NotNull Integer value, @NotNull Class<T> type)
{
synchronized (CACHE32) {
EnumBitMask<?> mask = CACHE32.get(value);
if (mask == null) {
mask = new EnumBitMask<T>(new BitMask32(value));
if ((value & EXCLUDE_MASK32) == 0) {
CACHE32.put(value, mask);
}
}
return mask.cast(type);
}
}
/**
* Get a cached 32bit mask for a combination of flags.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param flags flags to combine
* @return matching bit mask
*/
@SafeVarargs
@NotNull
@SuppressWarnings("varargs")
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get32(T ... flags)
{
final EnumBitMask<T> mask = combine32(flags);
return getCachedEnumBitMask32(mask);
}
/**
* Get the cached value with the same settings as a given 16 bit mask.
* @param mask bit mask using 16 bits
* @param <T> bit flag type
* @return cached mask
*/
@NotNull
@SuppressWarnings("unchecked")
private static <T extends Enum<T> & BitFlag> EnumBitMask<T> getCachedEnumBitMask32(EnumBitMask<T> mask)
{
final Integer key = mask.getBitMask().low32();
synchronized (CACHE32) {
final EnumBitMask<?> cachedMask = CACHE32.get(key);
if (cachedMask == null) {
if ((key & EXCLUDE_MASK32) == 0) {
CACHE32.put(key, mask);
}
return mask;
}
return (EnumBitMask<T>)cachedMask;
}
}
/**
* Get a cached 32bit mask for a combination of flags.
*
* This is an optimization reducing created bit masks.
*
* @param <T> enum type
* @param mask basic mask
* @param flags flags to combine with mask
* @return combined bit mask
*/
@SafeVarargs
@NotNull
@SuppressWarnings("unchecked")
public static <T extends Enum<T> & BitFlag> EnumBitMask<T> get32(@NotNull EnumBitMask<T> mask,
T ... flags)
{
for (T flag : flags) {
mask = mask.set(flag);
}
return getCachedEnumBitMask32(mask);
}
}
Conclusion
That’s it. A lot of work, but now we can define and use enum bit flags in a typesafe way, although that’s still a bit of work, too. The inherent documentation and the typesafety is worth the effort in my feeling.
/** Old style */
public class Attribute
{
/** Attribute flag: Attribute is invisible. */
public static final int ATTRIBUTE_FLAG_INVISIBLE = 1;
/** Attribute flag: Attribute is fixed. */
public static final int ATTRIBUTE_FLAG_FIXED = 2;
/** Attribute flag: Attribute should be checked during insertion. */
public static final int ATTRIBUTE_FLAG_CHECK = 4;
/** Attribute flag: Attribute is predefined. */
public static final int ATTRIBUTE_FLAG_PREDEFINED = 8;
private int attributeFlags;
/**
* Get the attribute flags.
* @return combination of {@link #ATTRIBUTE_FLAG_INVISIBLE},
* {@link #ATTRIBUTE_FLAG_FIXED},
* {@link #ATTRIBUTE_FLAG_CHECK}, and
* {@link #ATTRIBUTE_FLAG_PREDEFINED}
*/
public int getAttributeFlags()
{
return attributeFlags;
}
/**
* Set the attribute flags
* @param flags combination of {@link #ATTRIBUTE_FLAG_INVISIBLE},
* {@link #ATTRIBUTE_FLAG_FIXED},
* {@link #ATTRIBUTE_FLAG_CHECK}, and
* {@link #ATTRIBUTE_FLAG_PREDEFINED}
*/
public void setAttributeFlags(int flags)
{
attributeFlags = flags;
}
}
// Usage
int flags = attribute.getAttributeFlags();
if ((flags & Attribute.ATTRIBUTE_FLAG_INVISIBLE) == 0) {
// show attribute
}
if ((flags & Text.TEXT_FLAG_UNDERLINED) != 0) { // this should not be possible
// underline
}
versus
/** New style. */
public class Attribute
{
/** Attribute flags. */
public enum Flags
implements BitFlag
{
/** Attribute is invisible. */
Invisible(0),
/** Attribute is fixed. */
Fixed(1),
/** Attribute should be checked during insertion. */
Check(2),
/** Attribute is predefined. */
Predefined(3);
/** The attribute flags mask where all flags are unset. */
public static final EnumBitMask<AttributeFlags> NONE = EnumBitMask.NONE16.cast(AttributeFlags.class);
/** The attribute flags mask where all flags are set. */
public static final EnumBitMask<AttributeFlags> ALL = EnumBitMask.get16(values());
/** Wrapped real bit flag. */
private final BitFlag flag;
/**
* Constructor.
* @param flagIndex index of flag
*/
AttributeFlags(int flagIndex)
{
flag = SimpleBitFlag.get(flagIndex);
}
/**
* Is this flag set in the given mask?
*
* @param mask bit mask
* @return {@code true}: if the flag is set<br/>
* {@code false}: if the flag is not set
*/
@Override
public boolean isSetIn(@NotNull BitMask mask)
{
return flag.isSetIn(mask);
}
/**
* Set this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is set
*/
@NotNull
@Override
public BitMask setIn(@NotNull BitMask mask)
{
return flag.setIn(mask);
}
/**
* Clear this flag in the given bit mask.
*
* @param mask bit mask
* @return new bit mask where this flag is cleared
*/
@NotNull
@Override
public BitMask clearIn(@NotNull BitMask mask)
{
return flag.clearIn(mask);
}
/**
* Get the enum bit mask associated with a given internal value.
* @param internalValue internal value (only the lower 16 bits are used)
* @return associated bit mask
*/
@NotNull
public static EnumBitMask<AttributeFlags> fromInternal(int internalValue)
{
return EnumBitMask.get16(internalValue, AttributeFlags.class);
}
}
@NotNull
private EnumBitMask<Flags> attributeFlags = Flags.NONE;
/**
* Get the attribute flags.
* @return attribute flags
*/
@NotNull
public EnumBitMask<Flags> getAttributeFlags()
{
return attributeFlags;
}
/**
* Set the attribute flags.
* @param flags attribute flags
*/
public void setAttributeFlags(@NotNull EnumBitMask<Flags> flags)
{
attributeFlags = flags;
}
}
// Usage
EnumBitMask<Attribute.Flags> flags = attribute.getAttributeFlags();
if (!flags.isSet(Attribute.Flags.Invsible)) {
// display attribute
}
if (flags.isSet(Text.Flags.Underline)) { // this will give a compiler error
// underline
}
If you are interested: the complete implementation is contained in the free de·caff Commons in module caff-commons. Javadoc documentation of the above classes is also available under