sourceafBeanUtils::BeanFactory.fan


** Creates Lists, Maps and other Objects, optionally setting field values.
** Ctors may be of any scope: 'public', 'protected', 'internal' and even 'private'.
** 
** Fields are either set post construction or via an it-block ctor argument 
** (which must be the last method parameter). 
** 
** 'const' types **must** provide an it-block ctor if fields are to be set. 
@Js @Deprecated { msg="Use BeanBuilder instead" }
class BeanFactory {

    // Note: Where this class was nice, it's a lot of overhead to create() a single type
    // Common use-cases are now clear so the simplier BeanBuilder is much cleaner and quicker. 
    
    ** The type this factory will create 
    Type type {
        private set // keep this as non-const to keep binary backwards compatibility
    }

    @NoDoc
    protected Obj?[]        ctorArgs
    @NoDoc
    protected Field:Obj?    fieldVals
    
    ** Makes a factory for the given type.
    new make(Type type, Obj?[]? ctorArgs := null, [Field:Obj?]? fieldVals := null) {
        this.type = type
        this.ctorArgs   = ctorArgs  ?: Obj?#.emptyList.rw
        this.fieldVals  = fieldVals ?: Field:Obj?[:]
    }
    
    ** Fantom Bug: `http://fantom.org/sidewalk/topic/2163#c13978`
    @Operator 
    private Obj? get(Obj key) { null }

    ** Sets a field on the type to be instantiated.
    @Operator
    This set(Field field, Obj? val) {
        if (!type.fits(field.parent))
            throw ArgErr("Field ${field.qname} does not belong to ${type.qname}".replace("sys::", ""))
        fieldVals[field] = val
        return this
    }

    ** Sets a field on the type to be instantiated.
    This setByName(Str fieldName, Obj? val) {
        field := type.field(fieldName)
        return set(field, val)
    }

    ** Adds a ctor argument.
    @Operator
    This add(Obj? arg) {
        ctorArgs.add(arg)
        return this
    }

    ** Creates an instance of the object, optionally using the given ctor.
    ** 
    ** If no ctor is given, a suitable one is picked that matches the arguments accumulated by the factory. 
    Obj create(Method? ctor := null) {
        if (ctor != null && ctor.parent != type)
            throw ArgErr("Ctor ${ctor.qname} does not belong to $type.qname".replace("sys::", ""))
        
        return doCreate(ctor)
    }
    
    @NoDoc
    protected virtual Obj doCreate(Method? ctor := null) {
        
        // if there is nothing to set, try our luck at a default value
        // needed for Ints, Strs, Lists and Maps, etc...
        if (ctor == null && ctorArgs.isEmpty) {
            defVal := makeFromDefaultValue(type)
            if (defVal != null)
                return setFieldVals(defVal)
        }

        args := ctorArgs

        if (ctor != null) {
            if (!ctor.params.isEmpty && ctor.params[-1].type.fits(|This|#) && args.size == (ctor.params.size - 1)) {
                itBlockFunc := Field.makeSetFunc(fieldVals.dup)     // that .dup() is very important!
                args.add(itBlockFunc)
                fieldVals.clear
            }

            argTypes := args.map { it?.typeof }
            if (!ReflectUtils.argTypesFitMethod(argTypes, ctor) || args.size > ctor.params.size)
                throw Err(msg_ctorArgMismatch(ctor, args))
            
            return setFieldVals(ctor.callList(args))
        }

        // look for ctors that may / or may not take an it-block, favouring those that do
        itBlockFunc     := Field.makeSetFunc(fieldVals.dup)     // that .dup() is very important!
        argsWithOut     := ctorArgs
        argsWith        := ctorArgs.dup.add(itBlockFunc)
        argTypesWithOut := (Type[]) argsWithOut.map { it?.typeof }
        argTypesWith    := (Type[]) argsWith   .map { it?.typeof }
        ctorsWithOut    := ReflectUtils.findCtors(type, argTypesWithOut).exclude { argsWithOut.size > it.params.size }
        ctorsWith       := ReflectUtils.findCtors(type, argTypesWith   ).exclude { argsWith.size    > it.params.size }
        ctorsBoth       := ctorsWithOut.dup.addAll(ctorsWith).unique
        
        if (ctorsWithOut.isEmpty && ctorsWith.isEmpty)
            throw Err("Could not find a ctor on ${type.qname} to match argument types - ${argTypesWithOut}".replace("sys::", ""))
        if (ctorsBoth.size > 1 && ctorsWith.size != 1)  // favour ctors with it-blocks
            throw Err(msg_tooManyCtorsFound(type, ctorsBoth.map { it.name }, argTypesWithOut))
        
        if (ctorsWith.size == 1) {
            ctor = ctorsWith.first
            args = argsWith
            fieldVals.clear
        } else {
            ctor = ctorsWithOut.first
            args = argsWithOut
        }
        
        return setFieldVals(ctor.callList(args))
    }
    
    @NoDoc
    override Str toStr() {
        "BeanFactory for $type.qname"
    }
    
    ** Returns a default value for the given type. 
    ** Use as a replacement for [Type.make()]`sys::Type.make`.
    ** 
    ** Returned objects are *not* guaranteed to be immutable. 
    ** Call 'toImmutable()' on returned object if you need 'const' Lists and Maps.
    ** 
    ** The default type is determined by the following algorithm:
    ** 1. If the type is nullable (and 'force == false') return 'null'
    ** 1. If the type is a Map, an empty map is returned
    ** 1. If the type is a List, an empty list is returned (with zero capacity)
    ** 1. If one exists, a public no-args ctor is called to create the object
    ** 1. If it exists, the value of the type's 'defVal' slot is returned
    **    (must be a static field or method with zero params)
    ** 1. 'ArgErr' is thrown 
    ** 
    ** This method differs from [Type.make()]`sys::Type.make` for the following reasons:
    **  - 'null' is returned if type is nullable. 
    **  - Can create Lists and Maps
    **  - The public no-args ctor can be called *anything*. 
    static Obj? defaultValue(Type type, Bool force := false) {
        if (type.isNullable && !force)
            return null

        return makeFromDefaultValue(type) ?: throw ArgErr("${type.signature} is null and does not have a default value".replace("sys::", "")) 
    }

    @NoDoc
    protected static Obj? makeFromDefaultValue(Type type) {
        if (type.name == "List") {
            valType := type.params["V"] ?: Obj?#
            list := valType.emptyList.rw
            list.capacity = 0
            return list
        }

        if (type.name == "Map") {
            mapType := type.isGeneric ? Obj:Obj?# : type
            return Map(mapType.toNonNullable)
        }

        ctors := ReflectUtils.findCtors(type, Type#.emptyList)
        if (ctors.size == 1 && ctors.first.isPublic)
            return ctors.first.call
        
        defValField := ReflectUtils.findField(type, "defVal", null, true)
        if (defValField != null && defValField.isPublic)
            return defValField.get
        
        defValMethod := ReflectUtils.findMethod(type, "defVal", Type#.emptyList, true)
        if (defValMethod != null && defValMethod.isPublic)
            return defValMethod.call

        return null
    }

    private Obj? setFieldVals(Obj? obj) {
        fieldVals.each |val, field| {
            field.set(obj, val)
        }
        return obj
    }
    
    private static Str msg_ctorArgMismatch(Method ctor, Obj?[] args) {
        ctorSig := ctor.qname + "(" + ctor.params.join(", ") + ")"
        return "Arguments do not match ctor params for ${ctorSig} - ${args}".replace("sys::", "")
    }

    private static Str msg_tooManyCtorsFound(Type type, Str[] ctorNames, Type?[] argTypes) {
        "Found more than 1 ctor on ${type.qname} ${ctorNames} that match argument types - ${argTypes}".replace("sys::", "")
    }
}