sourceafMongo::Flag.fan


** (Advanced)
** A Flag represents many states by setting and clearing bits on a Int. 
** 
** Using Ints as flags is still valid, but the Flags class gives superior debugging info. An 
** example Flags class:
** 
** pre>
** syntax: fantom
** 
** const class MyFlags : Flag {
**     static const MyFlags one     := MyFlags(1, "one")
**     static const MyFlags two     := MyFlags(2, "two")
**     static const MyFlags three   := MyFlags(3, "three")
**     static const MyFlags four    := MyFlags(4, "four")
** 
**     new make(|This|? f := null) : super(f) { }
**     new makeFromDefinition(Int flag, Str? name) : super(flag, name) { }
** }
** <pre
** 
** Set and clear bits by using '+' and '-' operators:
** 
** pre>
** syntax: fantom
** 
** (MyFlags.two + MyFlags.two) .toStr  --> two
** (MyFlags.two - MyFlags.four).toStr  --> two
** <pre
** 
** Multiple flags may be set:
** 
** pre>
** syntax: fantom
** 
** (MyFlags.one + MyFlags.four).toStr  --> one|four
** (MyFlags.two + MyFlags.four).toStr  --> two|four
** <pre
** 
** Flags are automatically coalesced:
** 
** pre>
** syntax: fantom
** 
** (MyFlags.one + MyFlags.three) .toStr  --> three 
** <pre
** 
** Unknown flags are presented as numbers:
** 
** pre>
** syntax: fantom
** 
** (MyFlags(16))               .toStr  --> (18)
** (MyFlags(10))               .toStr  --> two|(8)
** (MyFlags(27))               .toStr  --> three|(8)|(16)
** <pre
abstract const class Flag {
    const Int value
    private const Str? pName
    
    Str name {
        get { pName == null ? computeName : pName }
        private set { }
    }
    
    protected new make(Int value, Str? name) {
        this.value = value
        this.pName = name
        if (name != null && name.isEmpty)
            throw ArgErr("Flag name can not be empty")
    }

    ** Add Flag b.  Shortcut is a + b.
    @Operator 
    This plus(Flag b) {
        plusInt(b.value)
    }

    ** Removes Flag b.  Shortcut is a - b.
    @Operator 
    This minus(Flag b) {
        minusInt(b.value)
    }

    ** Add Flag b.  Shortcut is a + b.
    @Operator 
    This plusInt(Int b) {
        newValue    := value.or(b)
        return (Flag) this.typeof.make([newValue, null])
    }

    ** Removes Flag b.  Shortcut is a - b.
    @Operator 
    This minusInt(Int b) {
        newValue    := value.and(b.not) 
        return (Flag) this.typeof.make([newValue, null])
    }
    
    ** Returns 'true' if *any* of the given flag values are set on this object.
    Bool containsAny(Flag flag) {
        value.and(flag.value) > 0
    }

    ** Returns 'true' if *all* the given flag values are set on this object.
    Bool containsAll(Flag flag) {
        value.and(flag.value) == flag.value
    }

    @NoDoc
    override Bool equals(Obj? obj) {
        (obj as Flag)?.value == value
    }
    
    @NoDoc
    override Int hash() {
        return value.hash
    }
    
    @NoDoc
    override Str toStr() {
        name
    }
    
    // ---- Private Methods ----------------------------------------------------------------------- 

    private Str computeName() {
        Flag[]
        match := [,]
        flags := this.findFlags
        value := this.value
        
        while (value > 0) {
            flag := flags.find |flag| {
                flag.value != 0 && flag.value.and(value) == flag.value
            }
            
            if (flag == null) {
                bit := findSetBits(value)[0]
                flag = ValueFlag(bit, "($bit)")
            }
            
            match.add(flag)
            value -= flag.value
        }

        if (match.isEmpty && !flags.isEmpty && flags[-1].value == 0)
            match.add(flags[-1])
        
        return match
            .sort |f1, f2| { ((Flag) f1).value <=> ((Flag) f2).value }
            .map |flag| { flag.pName }
            .join("|")
    }
    
    private Flag[] findFlags() {
        return typeof.fields
            .findAll |field| {
                field.isStatic && field.type == typeof
            }
            .map |field| {
                field.get
            }
            .sort |f1, f2| { 
                // inverse value order - required for finding composites
                ((Flag) f2).value <=> ((Flag) f1).value
            }
    }
    
    private Int[] findSetBits(Int value) {
        // I'm gonna take a leap of faith that no-one uses the MSB of a 64 signed long!
        (63..0)
            .map |i| { 
                2.pow(i) 
            }
            .findAll |bit| {
                value.and(bit) == bit 
            } 
    }
}

internal const class ValueFlag : Flag {
    new make(Int flag, Str name) : super(flag, name) { }
}