sourceafIoc::OrderedConfig.fan


** Passed into module contribution methods to allow the method to, err, contribute!
**
** A service can *collect* contributions in three different ways:
** - As an unordered list of values
** - As an ordered list of values
** - As a map of keys and values
** 
** The service defines the *type* of contribution by declaring a parameterised list or map in its 
** ctor or builder method. Contributions must be compatible with the type.
class OrderedConfig {

    internal const  Type            contribType
    private  const  ServiceDef      serviceDef
    private         InjectionCtx    ctx
    private         Orderer         orderer
    private         Int             impliedCount
    private         Str[]?          impliedConstraint
    
    internal new make(InjectionCtx ctx, ServiceDef serviceDef, Type contribType) {
        if (contribType.name != "List")
            throw WtfErr("Ordered Contrib Type is NOT list???")
        if (contribType.isGeneric)
            throw IocErr(IocMessages.orderedConfigTypeIsGeneric(contribType, serviceDef.serviceId)) 

        this.ctx            = ctx
        this.serviceDef     = serviceDef
        this.contribType    = contribType
        this.orderer        = Orderer()
        this.impliedCount   = 1
    }

    ** A helper method that instantiates an object, injecting any dependencies. See `Registry.autobuild`.  
    Obj autobuild(Type type) {
        ctx.objLocator.trackAutobuild(ctx, type)
    }

    ** Adds an unordered object to a service's configuration.
    Void addUnordered(Obj object) {
        id := "Unordered${impliedCount}"
        addOrdered(id, object)
    }

    ** Adds all the unordered objects to a service's configuration.
    Void addUnorderedAll(Obj[] objects) {
        objects.each |obj| {
            addUnordered(obj)
        }
    }

    ** Adds an ordered object to a service's contribution. Each object has a unique id (case 
    ** insensitive) that is used by the constraints for ordering. Each constraint must start with 
    ** the prefix 'BEFORE:' or 'AFTER:'.
    ** 
    ** pre>
    ** config.addOrdered("Breakfast", eggs)
    ** config.addOrdered("Lunch", ham, ["AFTER: breakfast", "BEFORE: dinner"])
    ** config.addOrdered("Dinner", pie)
    ** <pre
    ** 
    ** Configuration contributions are ordered across modules. 
    Void addOrdered(Str id, Obj object, Str[] constraints := Str#.emptyList) {
        if (!object.typeof.fits(listType))
            throw IocErr(IocMessages.orderedConfigTypeMismatch(object.typeof, listType))

        if (constraints.isEmpty)
            constraints = impliedConstraint ?: Str#.emptyList
        
        orderer.addOrdered(id, object, constraints)

        // keep an implied list ordering
        impliedCount++
        impliedConstraint = ["after: $id"]
    }

//  ** Overrides a contributed ordered object. The original override must exist.
//  Void overrideOrdered(Str id, Obj object, Str[] constraints := [,]) {
//  }

    internal Void contribute(InjectionCtx ctx, Contribution contribution) {
        // implied ordering only per contrib method
        impliedConstraint = null
        contribution.contributeOrdered(ctx, this)
    }
    
    internal List getConfig() {
        ctx.track("Ordering configuration contributions") |->List| {
            contribs := orderer.order.map { it.payload }
            return List.make(listType, 10).addAll(contribs)
        }
    }
    
    private once Type listType() {
        contribType.params["V"]
    }
    
    override Str toStr() {
        "OrderedConfig of $listType"
    }
}