sourceafIoc::ServiceBuilder.fan


** Use to define an IoC service.
** 
** pre>
** syntax: fantom
** serviceBuilder := regBuilder.addService()
** serviceBuilder
**     .withId("acme::penguins")
**     .withType(Penguins#)
**     .withImplType(PenguinsImpl#)
**     .withScope("root")
** <pre
** 
** The above could be inlined and, taking advantage of defaults, could be shortened to just:
** 
** pre>
** syntax: fantom
** regBuilder.addService(Penguins#).withRootScope
** <pre
@Js
mixin ServiceBuilder {

    ** Sets the unique service ID.
    ** If not set explicitly it is taken to be the qualified Type name.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService.withId("thread")
    ** <pre
    abstract This withId(Str? id)

    ** Sets the type of the service. May also be set via 'addService()'. 
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Penguin#)
    ** 
    ** regBuilder.addService.withType(Penguin#)
    ** <pre
    ** 
    ** If the service type is a mixin then either an implementation type or a builder must be subsequently set.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(IPenguin#).withImplType(PenguinImpl#)
    ** <pre
    ** 
    abstract This withType(Type type)

    ** Sets the implementation of the service. Used when the service is autobuilt. May also be set via 'addService()'.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(IPenguin#, PenguinImpl#)
    ** 
    ** regBuilder.addService(Penguin#).withImplType(PenguinImpl#)
    ** <pre
    ** 
    ** 'ImplType' is used, along with 'ctorArgs' and 'fieldVals' to autobuild an instance of the service.
    abstract This withImplType(Type? implType)
    
    ** Creates an alias ID that this service is also known as.
    ** 
    ** pre>
    ** syntax: fantom
    ** reg := regBuilder { 
    **     addService(Wolf#).withAlias("acme::Sheep") 
    ** }.build
    ** 
    ** reg.rootScope.serviceById("acme::Wolf")
    ** 
    ** reg.rootScope.serviceById("acme::Sheep")  // --> same service as 'acme::wolf'
    ** <pre
    abstract This withAlias(Str alias)

    ** Creates multiple aliases that this service is also known as.
    abstract This withAliases(Str[]? aliases)

    ** Adds an alias Type that this service is also known as.
    ** 
    ** pre>
    ** syntax: fantom
    ** reg := regBuilder { 
    **     addService(Wolf#).withAliasType(Sheep#) 
    ** }.build
    ** 
    ** reg.rootScope.serviceByType(Wolf#)
    ** 
    ** reg.rootScope.serviceByType(Sheep#)  // --> same service as Wolf
    ** <pre
    abstract This withAliasType(Type aliasType)

    ** Sets many Types that this service is also known as.
    abstract This withAliasTypes(Type[]? aliasTypes)

    ** Sets the scope that the service can be created in.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Wolf#).withScope("thread")
    ** <pre
    abstract This withScope(Str scope)

    ** Sets multiple scopes that the service can be created in.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Wolf#).withScopes(["root", "thread"])
    ** <pre
    abstract This withScopes(Str[]? scopes)

    ** Convenience for 'withScope("root")'. 
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Wolf#).withRootScope
    ** <pre
    abstract This withRootScope()
    
    ** Sets a func that creates instances of the services. 
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Wolf#).withBuilder |Scope scope -> Obj?| {
    **     return Penguin()
    ** }
    ** <pre
    ** 
    ** 'Scope' is the current scope the service is being built in.
    abstract This withBuilder(|Scope -> Obj?|? serviceBuilder)
    
    ** Set constructor arguments to be used when the service is autobuilt. 
    ** Note the args must be immutable.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Penguin#).withCtorArgs([arg1, arg2])
    ** <pre
    abstract This withCtorArgs(Obj?[]? args)

    ** Set field values to used when the service is autobuilt. 
    ** An alternative to using ctor args. 
    ** Note all vals must be immutable.
    ** 
    ** pre>
    ** syntax: fantom
    ** regBuilder.addService(Penguin#).withFieldVals([Penguin#arg1 : "val1", Penguin#arg2 : "val2"])
    ** <pre
    abstract This withFieldVals([Field:Obj?]? fieldVals)
}

@Js
internal class ServiceBuilderImpl : ServiceBuilder {
    internal SrvDef srvDef
    
    internal new make(Type moduleId) {
        this.srvDef = SrvDef {
            it.moduleId = moduleId
        }
    }

    override This withId(Str? serviceId) {
        srvDef.id = serviceId
        return this
    }

    override This withType(Type serviceType) {
        srvDef.type = serviceType.toNonNullable
        if (srvDef.id == null)
            srvDef.id = serviceType.qname
        if (srvDef.builder == null)
            srvDef.autobuild = true
        return this
    }

    override This withImplType(Type? serviceImplType) {
        if (serviceImplType != null && serviceImplType.isMixin) 
            throw ArgErr(ErrMsgs.autobuilder_bindImplNotClass(serviceImplType))
        srvDef.implType = serviceImplType?.toNonNullable

        if (serviceImplType != null) {
            srvDef.autobuild    = true
            srvDef.builder      = null
        } else
            srvDef.autobuild    = false

        return this
    }

    override This withAlias(Str alias) {
        srvDef.aliases = [alias]
        return this
    }

    override This withAliases(Str[]? aliases) {
        srvDef.aliases = aliases
        return this
    }

    override This withAliasType(Type aliasType) {
        srvDef.aliasTypes = [aliasType]
        return this
    }

    override This withAliasTypes(Type[]? aliasTypes) {
        srvDef.aliasTypes = aliasTypes
        return this
    }

    override This withScope(Str scope) {
        withScopes([scope])
    }

    override This withScopes(Str[]? serviceScopes) {
        if (serviceScopes != null && srvDef.moduleId != IocModule# && serviceScopes.any { it.equalsIgnoreCase("builtIn") })
            throw IocErr(ErrMsgs.serviceBuilder_scopeReserved(srvDef.id ?: "", "builtIn"))

        srvDef.declaredScopes = toImmutableObj(serviceScopes)
        return this
    }

    override This withRootScope() {
        withScopes(["root"])
    }
    
//  override This addScope(Str scope) {
//      if (srvDef.moduleId != IocModule#.qname && scope.equalsIgnoreCase("builtIn"))
//          throw IocErr(ErrMsgs.serviceBuilder_scopeReserved(srvDef.id ?: "", "builtIn"))
//
//      srvDef.declaredScopes =  toImmutableObj((srvDef.declaredScopes ?: Str[,]).rw.add(scope))
//      return this
//  }
    
    override This withBuilder(|Scope -> Obj?|? serviceBuilder) {
        srvDef.builder = toImmutableObj(serviceBuilder)
        if (serviceBuilder != null) {
            srvDef.autobuild    = false
            srvDef.implType     = null
        } else
            srvDef.autobuild    = true
        return this
    }
    
    ** Passed as args to the service ctor. The args must be immutable.
    override This withCtorArgs(Obj?[]? args) {
        srvDef.ctorArgs = toImmutableObj(args)
        if (args != null)   // if null, we may not be autobuilding
            srvDef.autobuild = true
        return this
    }

    ** Field values to set in the service impl. An alternative to using ctor args. All vals must be immutable.
    override This withFieldVals([Field:Obj?]? fieldVals) {
        srvDef.fieldVals = toImmutableObj(fieldVals)
        if (fieldVals != null)  // if null, we may not be autobuilding
            srvDef.autobuild = true
        return this
    }
    
    private static Obj? toImmutableObj(Obj? obj) {
        if (obj is Func)
            return Env.cur.runtime == "js" ? obj : obj.toImmutable
        return obj?.toImmutable
    }
}