sourceafConcurrent::LocalRefManager.fan

using concurrent

** (Service) -
** Use to create 'LocalRef' / 'LocalList' / 'LocalMap' instances whose contents can be *cleaned* up. 
** Erm, I mean deleted!
**  
** This is particularly important in the context of web applications where resources need to be 
** *cleaned* up at the end of a web request / thread. 
** 
** Then when 'cleanUpThread()' is called, all thread local data created by this manager will be
** deleted from 'Actor.locals' 
**
** 'LocalRefManager' is automatically made available in IoC enabled applications. 
** 
** LocalXXX Injection
** ******************
** The Concurrent library also defines an IoC 'DependencyProvider' that injects 'LocalXXX' instances directly into your class.
** Where possible, the field name is used as the *local* name. Example: 
** 
** pre>
** syntax: fantom
** const class Example {
**     @Inject const LocalMap localMap
** 
**     new make(|This|in) { in(this) }
** }
** <pre
** 
** '@Inject.type' may be used to declare the underlying parameters of the 'LocalList / LocalMap':
** 
** pre>
** syntax: fantom
** const class Example {
**     @Inject { type=Str[]# }
**     const LocalList localList
** 
**     @Inject { type=[Str:Slot?]# }
**     const LocalMap localMap
** 
**     new make(|This|in) { in(this) }
** }
** <pre
** 
** If '@Inject.type' is used with a 'LocalMap', then if the key is a 'Str' the map will be 
** case-insensitive, otherwise it will be ordered.
**   
const mixin LocalRefManager {

    ** Creates a `afConcurrent::LocalRef` with the given default function.
    abstract LocalRef createRef(Str name, |->Obj?|? defFunc := null)

    ** Creates a `afConcurrent::LocalList` with the given name.
    abstract LocalList createList(Str name)

    ** Creates a `afConcurrent::LocalMap` with the given name.
    abstract LocalMap createMap(Str name)

    ** Creates a qualified name unique to this 'ThreadLocalManager' that when used to create a 
    ** Local Refs, List or Map, ensures it is cleanup up with all the others.  
    abstract Str createName(Str name)

    ** Returns all (fully qualified) keys in the current thread associated / used with this manager. 
    abstract Str[] keys() 
    
    ** Add a handler to be called on thread clean up. New handlers have to be added for each thread.
    abstract Void addCleanUpHandler(|->| handler)
    
    ** Removes all values in the current thread associated / used with this manager.
    abstract Void cleanUpThread()
}


internal const class LocalRefManagerImpl : LocalRefManager {
    static  
    private const AtomicInt counter := AtomicInt(0)
    private const LocalList cleanUpHandlers
    
    const Str prefix
    
    new make() {
        this.prefix = createPrefix
        this.cleanUpHandlers = createList("LocalRefManager.cleanupHandlers")
    }

    override LocalRef createRef(Str name, |->Obj?|? defFunc := null) {
        LocalRef(createName(name), defFunc)
    }

    override LocalList createList(Str name) {
        LocalList(createName(name))
    }

    override LocalMap createMap(Str name) {
        LocalMap(createName(name))
    }

    override Str createName(Str name) {
        "${prefix}.\${id}.${name}"
    }
    
    override Str[] keys() {
        Actor.locals.keys
            .findAll { it.startsWith(prefix) }
            .sort
    }
    
    override Void addCleanUpHandler(|->| handler) {
        cleanUpHandlers.add(handler)
    }
    
    override Void cleanUpThread() {
        cleanUpHandlers.each | |->| handler| { handler.call }
        keys.each { Actor.locals.remove(it) }
    }

    // ---- Helper Methods ------------------------------------------------------------------------
    
    private Str createPrefix() {
        count   := counter.incrementAndGet
        padded  := count.toStr.padl(2, '0')
        prefix  := "TLM-${padded}"
        return prefix
    }
}