** Looks up values via a type inheritance search.
**
** Example, if a 'TypeLookup' was created with 'Obj#, Num#' and 'Int#', the inheritance and
** matching types would look like:
**
** pre>
** Type findParent() findChildren()
**
** Obj Obj Obj, Num, Int
** |
** Num Num Num, Int
** |
** Int Int Int
** <pre
**
** Note that 'findParent()' and 'findChildren()' return the value associated with the type, not the
** type itself. They also match the given type if 'TypeLookup' was created with it, hence
** 'findParent()' above matches itself.
**
** While the above results are quite obvious, 'TypeLookup' is more useful when passed a type it
** doesn't know about:
**
** findParent(Float#) // --> Num#
** findChildren(Float#) // --> Err
**
** When searching the type hierarchy for a closest match (see 'findParent()' ), note that
** 'TypeLookup' also searches mixins.
**
** Example usages can be found in:
** - [IoC]`http://www.fantomfactory.org/pods/afIoc`: All known implementations of a mixin are looked up via 'findChildren()'
** - [BedSheet]`http://www.fantomfactory.org/pods/afBedSheet`: Strategies for handling Err types are looked up via 'findParent()'
**
** If performance is required, then use [Concurrent]`http://www.fantomfactory.org/pods/afConcurrent`
** to create a 'TypeLookup' that caches the lookups.
** Full code for a 'CachingTypeLookup' is given below:
**
** pre>
** syntax: fantom
**
** using afBeanUtils
** using afConcurrent
**
** ** A 'TypeLookup' that caches the lookup results.
** internal const class CachingTypeLookup : TypeLookup {
** private const AtomicMap parentCache := AtomicMap()
** private const AtomicMap childrenCache := AtomicMap()
**
** new make(Type:Obj? values) : super(values) { }
**
** ** Cache the lookup results
** override Obj? findParent(Type type, Bool checked := true) {
** nonNullable := type.toNonNullable
** return parentCache.getOrAdd(nonNullable) { doFindParent(nonNullable, checked) }
** }
**
** ** Cache the lookup results
** override Obj?[] findChildren(Type type, Bool checked := true) {
** nonNullable := type.toNonNullable
** return childrenCache.getOrAdd(nonNullable) { doFindChildren(nonNullable, checked) }
** }
**
** ** Clears the lookup cache
** Void clear() {
** parentCache.clear
** childrenCache.clear
** }
** }
** <pre
@Js
const class TypeLookup {
private const Type:Obj? values
** Creates a 'TypeLookup' with the given map. All types are coerced to non-nullable types.
** An 'ArgErr' is thrown if a duplicate is found in the process.
new make(Type:Obj? values) {
values = values.rw
nonDups := Type:Obj?[:]
nonDups.ordered = values.ordered // mainly for testing, lookup is faster when not ordered
values.each |val, type| {
nonNullable := type.toNonNullable
if (nonDups.containsKey(nonNullable))
throw ArgErr("Type $nonNullable is already mapped to value ${nonDups[nonNullable]}")
nonDups[nonNullable] = val
}
this.values = nonDups
}
** Returns the value that matches the given type. This is just standard Map behaviour.
**
** If no match is found and 'checked' is 'false', 'null' is returned.
Obj? findExact(Type exact, Bool checked := true) {
nonNullable := exact.toNonNullable
return values.get(nonNullable) ?: check(nonNullable, checked)
}
** Returns the value of the closest parent of the given type.
** Note an exact match is performed first.
** Example:
** pre>
** strategy := StrategyRegistry( [Obj#:1, Num#:2, Int#:3] )
** strategy.findClosestParent(Obj#) // --> 1
** strategy.findClosestParent(Num#) // --> 2
** strategy.findClosestParent(Float#) // --> 2
** strategy.findClosestParent(Wotever#) // --> Err
** <pre
**
** If no parent is found and 'checked' is 'false', 'null' is returned.
virtual Obj? findParent(Type type, Bool checked := true) {
doFindParent(type, checked)
}
** Returns the values of the children of the given type.
** Note an exact match is performed first.
** Example:
** pre>
** strategy := StrategyRegistry( [Obj#:1, Num#:2, Int#:3] )
** strategy.findChildren(Obj#) // --> [1, 2, 3]
** strategy.findChildren(Num#) // --> [2, 3]
** strategy.findChildren(Float#) // --> Err
** <pre
**
** If no children are found and 'checked' is 'false', an empty list is returned.
virtual Obj?[] findChildren(Type type, Bool checked := true) {
doFindChildren(type, checked)
}
** Returns a list of all the types this lookup is configured for.
** (Handy for debug / error messages.)
Type[] types() {
values.keys
}
** It kinda sucks to need this method, but it's a workaround to this
** [super issue]`http://fantom.org/sidewalk/topic/2289`.
@NoDoc
virtual Obj? doFindParent(Type type, Bool checked := true) {
nonNullable := type.toNonNullable
// chill, I got tests for all this!
deltas := values
.findAll |val, t2| { nonNullable.fits(t2) }
.map |val, t2->Int?| {
nonNullable.inheritance.eachWhile |sup, i| {
(sup == t2 || sup.mixins.contains(t2)) ? i : null
}
}
if (deltas.isEmpty)
return check(nonNullable, checked)
minDelta := deltas.vals.min
match := deltas.eachWhile |delta, t2| { (delta == minDelta) ? t2 : null }
return values[match]
}
** It kinda sucks to need this method, but it's a workaround to this
** [super issue]`http://fantom.org/sidewalk/topic/2289`.
@NoDoc
virtual Obj?[] doFindChildren(Type type, Bool checked := true) {
nonNullable := type.toNonNullable
children := values.findAll |val, key| { key.fits(type) }.vals
return !children.isEmpty ? children : (check(nonNullable, checked) ?: Obj?#.emptyList)
}
private Obj? check(Type nonNullable, Bool checked) {
checked ? throw ArgNotFoundErr("Could not find match for Type ${nonNullable}.", values.keys) : null
}
@NoDoc
override Str toStr() {
values.keys.toStr
}
}