sourceafIoc::RegistryBuilder.fan


** Builds a `Registry` from Modules. Modules may be added manually, defined by 
** [meta-data]`sys::Pod.meta` in dependent pods or defined by [index properties]`docLang::Env#index`
class RegistryBuilder {
    private const static Log    logger      := Utils.getLog(RegistryBuilder#)
    
    private BuildCtx    ctx         := BuildCtx("Building IoC Registry")
    private OneShotLock lock        := OneShotLock(IocMessages.registryBuilt)
    private ModuleDef[] moduleDefs  := [,]

    new make() {
        addModule(IocModule#)
    }

    ** Adds a module to the registry
    This addModule(Type moduleType) {
        ctx.track("Adding module definition for '$moduleType.qname'") |->| {
            lock.check
            logger.info("Adding module definition for $moduleType.qname")
            
            ctx.withModule(moduleType) |->| {           
                if (moduleDefs.find { it.moduleType == moduleType } != null) {
                    logger.warn(IocMessages.moduleAlreadyAdded(moduleType))
                    return
                }
                
                moduleDef := ModuleDefImpl(ctx.tracker, moduleType)
                addModuleDef(moduleDef)
                
                if (moduleType.hasFacet(SubModule#)) {
                    subModule := Utils.getFacetOnType(moduleType, SubModule#) as SubModule
                    ctx.track("Found SubModule facet on $moduleType.qname : $subModule.modules") |->| {
                        subModule.modules.each { 
                            addModule(it)
                        }
                    }
                } else
                    ctx.log("No SubModules found")
            }
        }
        return this
    }

    ** Adds many modules to the registry
    This addModules(Type[] moduleTypes) {
        lock.check
        moduleTypes.each |moduleType| {
            addModule(moduleType)
        }
        return this
    }

    ** Checks all dependencies of the given [pod]`sys::Pod` for the meta-data key 'afIoc.module' 
    ** which defines the qualified name of a module to load.
    This addModulesFromDependencies(Pod pod, Bool addTransitiveDependencies := true) {
        logger.info("Adding modules from dependencies of '$pod.name'")
        addModulesFromDependenciesRecursive(pod, addTransitiveDependencies)
        return this
    }

    ** Looks for all index properties of the key 'afIoc.module' which defines a qualified name of 
    ** a module to load.
    This addModulesFromIndexProperties() {
        logger.info("Adding modules from index properties")
        ctx.track("Adding modules from index properties") |->| {
            lock.check
            moduleNames := Env.cur.index("afIoc.module")
            moduleNames.each {
                addModuleFromTypeName(it)
            }
            if (moduleNames.isEmpty)
                ctx.log("No modules found")
        }
        return this
    }
    
    ** Constructs and returns the registry; this may only be done once. The caller is responsible for invoking
    ** `Registry.startup`
    Registry build() {
        lock.lock
        registry := RegistryImpl(ctx.tracker, moduleDefs)
        ctx.tracker.end
        return registry
    }
    
    // ---- Private Methods -----------------------------------------------------------------------

    private Type[] addModulesFromDependenciesRecursive(Pod pod, Bool addTransitiveDependencies) {
        ctx.track("Adding modules from dependencies of '$pod.name'") |->Type[]| {
            lock.check
            
            Type?[] modTypes := [,]
            
            // don't forget me!
            ctx.withPod(pod) |->| {
                modType := addModuleFromPod(pod)
                if (modType != null)
                    modTypes.add(modType)
                
                pod.depends.each {
                    dependency := Pod.find(it.name)
                    ctx.withPod(dependency) |->| {
                        modType = addModuleFromPod(dependency)
                        if (modType != null)
                            modTypes.add(modType)
                        
                        if (addTransitiveDependencies) {
                            mods := ctx.track("Adding transitive dependencies for '$dependency.name'") |->Obj| {
                                deps := addModulesFromDependenciesRecursive(dependency, addTransitiveDependencies)
                                if (deps.isEmpty)
                                    ctx.log("No transitive dependencies found")
                                return deps
                            } 
                            modTypes.addAll(mods)
                        } else
                            ctx.log("Not looking for transitive dependencies")
                    }
                }
                
                if (modTypes.isEmpty)
                    ctx.log("No modules found")             
            }
            
            return modTypes
        }
    }   
    
    private Type? addModuleFromPod(Pod pod) {
        qname := pod.meta[IocConstants.podMetaModuleName]
        if (qname != null) {
            return ctx.track("Pod '$pod.name' defines module $qname") |->Obj| {
                return addModuleFromTypeName(qname)
            }
        }
        return null
    }

    private Type addModuleFromTypeName(Str moduleTypeName) {
        moduleType := Type.find(moduleTypeName)
        addModule(moduleType)
        return moduleType
    }

    private This addModuleDef(ModuleDef moduleDef) {
        this.moduleDefs.add(moduleDef)
        return this
    }
}

internal class BuildCtx {
    private Pod[]       podStack        := [,]
    private Type[]      moduleStack     := [,]
            OpTracker   tracker

    new make(Str desc) {
        tracker = OpTracker(desc)
    }
    
    Obj? track(Str description, |->Obj?| operation) {
        tracker.track(description, operation)
    }

    Void log(Str description) {
        tracker.log(description)
    }

    Obj? withModule(Type module, |->Obj?| operation) {
        moduleStack.push(module)
        try {
            // check for recursion
            moduleStack[0..<-1].each { 
                if (it == module)
                    throw IocErr(IocMessages.moduleRecursion(moduleStack.map { it.qname }))
            }           
            return operation.call()
        } finally {         
            moduleStack.pop
        }
    }   

    Void withPod(Pod pod, |->Obj?| operation) {
        podStack.push(pod)
        try {
            ignore := false
            
            // check for recursion
            podStack[0..<-1].each { 
                if (it == pod) {
                    this.log("Pod '$pod.name' already inspected...ignoring")
                    ignore = true
                }
            }
            
            if (!ignore)
                operation.call()
            
        } finally {         
            moduleStack.pop
        }
    }   
}