using afConcurrent
const class EventHub {
private const LocalMap stash := LocalMap(typeof.qname)
private Str:EventDef eventDefs {
get { stash["eventDefs"] }
set { stash["eventDefs"] = it }
}
private Str:EventListenerDef[] eventListeners {
get { stash["eventListeners"] }
set { stash["eventListeners"] = it }
}
private Int idSeq {
get { stash["idSeq"] }
set { stash["idSeq"] = it }
}
new make(EventDef[] eventDefs) {
this.eventListeners = Str:EventListenerDef[][:] { caseInsensitive = true; def = [,].toImmutable }
this.eventDefs = Str:EventDef[:] { caseInsensitive = true }
this.idSeq = 1
eventDefs.each |eventDef| {
this.eventDefs[eventDef.name] = eventDef
}
}
Str addListener(Enum event, Func eventListener) {
addListener_(event.name, eventListener, false)
}
Void removeListener(Enum event, Str eventId) {
eventName := event.toStr
eventDef := eventDefs[eventName] ?: throw Err("Event '$eventName' has not been registered")
listener := eventListeners[eventName].find {
it.id == eventId
} ?: throw Err("Event Id '$eventId' not found for Event '$eventName'")
eventListeners[eventName].removeSame(listener)
}
virtual Void addEventSink(Obj eventSink) {
handlerNames := eventDefs.map |eventDef->Str?| {
eventName := eventDef.name
methodName := "on$eventName.capitalize"
if (eventSink.typeof.method(methodName, false) == null)
return null
method := ReflectUtils.findMethod(eventSink.typeof, methodName, eventDef.signature, false)
if (method == null) {
method = eventSink.typeof.method(methodName)
eventSig := "${methodName}(" + eventDef.signature.join(", ") + ")"
methodSig := "${eventSink.typeof.qname}.${methodName}(" + method.params.join(", ") + ")"
throw Err("Event handler ${methodSig} does not fit ${eventSig}")
}
listener := |Obj[]? params| { method.callList([eventSink].addAll(params)) }
addListener_(eventName, listener, true)
return methodName
}.exclude { it == null }.vals
nonHandled := eventSink.typeof.methods
.findAll { it.name.startsWith("on") }
.exclude { handlerNames.contains(it.name) }
if (!nonHandled.isEmpty)
throw Err("Event sink $eventSink.typeof.qname defines unused methods : " + nonHandled.map { it.name }.join(", "))
}
Void fireEvent(Enum event,
Obj? a := null, Obj? b := null, Obj? c := null, Obj? d := null,
Obj? e := null, Obj? f := null, Obj? g := null, Obj? h := null) {
params := ReflectUtils.toParamList(a, b, c, d, e, f, g, h)
eventName := event.toStr
eventDef := eventDefs[eventName] ?: throw Err("Event '$eventName' has not been registered")
// for selectTank(null) else sys::ArgErr: Too few arguments: 1 < instance+1..1
while (params.size < eventDef.signature.size)
params.add(null)
eventListeners[eventName].each {
it.call(params)
}
}
// ---- Private -------------------------------------------------------------------------------
Str addListener_(Str eventName, Func eventListener, Bool paramsAsList) {
eventDef := eventDefs[eventName] ?: throw Err("Event '$eventName' has not been registered")
id := "${typeof.name}-${idSeq}"
idSeq = idSeq + 1
eventListeners.getOrAdd(eventName) { EventListenerDef[,] } .add(EventListenerDef(eventDef, id, eventListener, paramsAsList))
return id
}
}
internal class EventListenerDef {
EventDef eventDef
Func eventListener
Bool paramsAsList
Str id
new make(EventDef eventDef, Str id, |Obj?| eventListener, Bool paramsAsList) {
this.eventDef = eventDef
this.id = id
this.eventListener = eventListener
this.paramsAsList = paramsAsList
}
Obj call(Obj?[] params) {
types := params.map { it?.typeof ?: Void#}
if (!paramsAsList && !ReflectUtils.paramTypesFitFuncSignature(types, eventListener))
throw Err("Event $eventDef.name Params do not fit definition : $types -> $eventListener.params")
try {
return eventListener.callList(paramsAsList ? [params] : params)
} catch (Err e) {
throw Err("Err calling $eventDef.name($eventDef.signature)", e)
}
}
}
const mixin EventDef {
abstract Str name()
abstract Type[] signature()
}
const class EventDefImpl : EventDef {
override const Str name
override const Type[] signature
new make(Str module, Str name, Type[] signature := Type#.emptyList) {
this.name = name
this.signature = signature
}
}