sourceafFancom::Flag.fan


**
** 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 myFlag : Flags {
**     static const myFlag one     := myFlag(1, "one")
**     static const myFlag two     := myFlag(2, "two")
**     static const myFlag three   := myFlag(3, "three")
**     static const myFlag four    := myFlag(4, "four")
** 
**     new make(|This|? f := null) : super(f) { }
**     new makeFromDefinition(Int flag, Str? name) : super(flag, name) { }
**     new makeFromVariant(Variant variant) : super(variant) { }
** }
** <pre
** 
** Set and clear bits by using '+' and '-' operators:
** 
** pre>
** syntax: fantom
** 
** (myFlag.two + myFlag.two) .toStr  --> two
** (myFlag.two - myFlag.four).toStr  --> two
** <pre
** 
** Multiple flags may be set:
** 
** pre>
** syntax: fantom
** 
** (myFlag.one + myFlag.four).toStr  --> one|four
** (myFlag.two + myFlag.four).toStr  --> two|four
** <pre
** 
** Flags are automatically coalesced:
** 
** pre>
** syntax: fantom
** 
** (myFlag.one + myFlag.three) .toStr  --> three 
** <pre
** 
** Unknown flags are presented as numbers:
** 
** pre>
** syntax: fantom
** (myFlag(16))               .toStr  --> (18)
** (myFlag(10))               .toStr  --> two|(8)
** (myFlag(27))               .toStr  --> three|(8)|(16)
** <pre
** 
** Every 'Flags' subclass needs to declare the following ctor to fulfil the variant surrogate 
** contract:
** 
** pre>
** syntax: fantom
** 
** new makeFromVariant(Variant variant) : super(variant) { }
** <pre
**  
abstract const class Flag {
    const Int value
    private const Str? pName
    
    Str name {
        get { pName == null ? computeName : pName }
        private set { }
    }
    
    protected new make(|This|? f := null) {
        f?.call(this)
    }
    
    protected new makeFromDefinition(Int value, Str? name) {
        this.value = value
        this.pName = name
        if (name != null && name.isEmpty)
            throw FancomErr("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)
        newVariant  := Variant(newValue)
        return (Flag) Helper.toVariantSurrogate(this.typeof, newVariant)
    }

    ** Removes Flag b.  Shortcut is a - b.
    @Operator 
    This minusInt(Int b) {
        newValue    := value.and(b.not) 
        newVariant  := Variant(newValue)
        return (Flag) Helper.toVariantSurrogate(this.typeof, newVariant)
    }
    
    ** Same as 'containsAll'
    @Deprecated { msg = "Use 'containsAny' or 'containsAll' instead" }
    Bool contains(Flag flag) {
        value.and(flag.value) == flag.value
    }

    ** 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
    }

    override Bool equals(Obj? obj) {
        if (obj == null)
            return false
        if (!obj.typeof.fits(Flag#))
            return false
        return (obj as Flag).value == value
    }
    
    override Int hash() {
        return value.hash
    }
    
    override Str toStr() {
        name
    }
    
    // ---- Variant Surrogate Methods ------------------------------------------------------------- 
    
    ** Variant surrogate ctor 
    new makeFromVariant(Variant variant) {
        this.value = variant.asInt
        this.pName = null
    }

    ** Variant surrogate method
    Variant toVariant() {
        Variant(value)
    }
    
    // ---- 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| { (f1 as Flag).value <=> (f2 as Flag).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
                (f2 as Flag).value <=> (f1 as Flag).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(|This|? f := null) : super(f) { }
    new makeFromDefinition(Int flag, Str name) : super(flag, name) { }
    new makeFromVariant(Variant variant) : super(variant) { }
}