// Path: src/main/java/com/jenkov/container/itf/factory/FactoryException.java
// public class FactoryException extends ContainerException {
//     public FactoryException(String errorContext, String errorCode, String errorMessage) {
//         super(errorContext, errorCode, errorMessage);
//     }
// 
//     public FactoryException(String errorContext, String errorCode, String errorMessage, Throwable cause) {
//         super(errorContext, errorCode, errorMessage, cause);
//     }
// 
// }
// 
// Path: src/main/java/com/jenkov/container/itf/factory/ILocalFactory.java
// public interface ILocalFactory {
// 
//      public Class getReturnType();
// 
//     /**
//      * This method is intended to be called *internally between* factories when creating
//      * an instance of some class. It is not intended to be called directly by client code,
//      * or from the Container. For that purpose use the instance(Object ... parameters)
//      * method. If you do call this method directly, a null will suffice for the localProducts
//      * array.
//      *
//      * NOTE: Only called on local factories. Never on global factories.
//      *
//      * <br/><br/>
//      * If you develop a custom IFactory implementation you should
//      * extend LocalFactoryBase and override this method, not the instance(Object ... parameters)
//      * method. If your factory implementation calls any other factories it should pass on
//      * both the parameters and localProducts object arrays!!
//      * ... as in factory.instance(parameters, localProducts); .
//      *  Do not just call the instance() or instance(parameters) method.
//      *
//      * @param parameters
//      * @param localProducts
//      * @return The created object.
//      */
//     public Object instance(Object[] parameters, Object[] localProducts);
// }


import com.jenkov.container.itf.factory.FactoryException;
import com.jenkov.container.itf.factory.ILocalFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
package com.jenkov.container.impl.factory;



/**

 */
public class MethodFactory extends LocalFactoryBase implements ILocalFactory {

    protected Method         method                        = null;
    protected Object         methodInvocationTarget        = null;    //todo remove this field, or change it to Class (methodOwnerClass).
    protected ILocalFactory  methodInvocationTargetFactory = null;
    protected List<ILocalFactory> methodArgFactories            = new ArrayList<ILocalFactory>();


    public MethodFactory(Method method, ILocalFactory methodInvocationTargetFactory, List<ILocalFactory> methodArgFactories) {
        if(method == null) throw new IllegalArgumentException("Method cannot be null");
        this.method                        = method;
        this.methodInvocationTargetFactory = methodInvocationTargetFactory;
        this.methodArgFactories            = methodArgFactories;
    }

    public MethodFactory(Method method, List<ILocalFactory> methodArgFactories) {
        this.method             = method;
        this.methodArgFactories = methodArgFactories;
    }


    public Class getReturnType() {
        //if a method returns void, it should return the invocation target instead, enabling method chaining on methods returning void.
        if(isVoidReturnType()){
            if(methodInvocationTargetFactory != null) return methodInvocationTargetFactory.getReturnType();
            if(methodInvocationTarget != null)        return methodInvocationTarget.getClass();
        }

        //if a method returns a parameterized product, not java.lang.Object should be returned, but the parameterized type.
        //Type returnType = this.method.getGenericReturnType();
        //if(returnType instanceof ParameterizedType){
        //    return (Class) ((ParameterizedType) returnType).getActualTypeArguments()[0];
        //}
        //if(returnType instanceof TypeVariable){
            
        //}

        return this.method.getReturnType();
    }

    public Object instance(Object[] parameters, Object[] localProducts) {
        Object[] arguments = FactoryUtil.toArgumentArray(this.methodArgFactories, parameters, localProducts);
        try {
            if(this.methodInvocationTargetFactory != null) {
                //if a method returns void, it should return the invocation target instead, enabling method chaining on methods returning void.
                if(isVoidReturnType()){
                    Object target = this.methodInvocationTargetFactory.instance(parameters, localProducts);
// Path: src/main/java/com/jenkov/container/itf/factory/FactoryException.java
// public class FactoryException extends ContainerException {
//     public FactoryException(String errorContext, String errorCode, String errorMessage) {
//         super(errorContext, errorCode, errorMessage);
//     }
// 
//     public FactoryException(String errorContext, String errorCode, String errorMessage, Throwable cause) {
//         super(errorContext, errorCode, errorMessage, cause);
//     }
// 
// }
// 
// Path: src/main/java/com/jenkov/container/itf/factory/ILocalFactory.java
// public interface ILocalFactory {
// 
//      public Class getReturnType();
// 
//     /**
//      * This method is intended to be called *internally between* factories when creating
//      * an instance of some class. It is not intended to be called directly by client code,
//      * or from the Container. For that purpose use the instance(Object ... parameters)
//      * method. If you do call this method directly, a null will suffice for the localProducts
//      * array.
//      *
//      * NOTE: Only called on local factories. Never on global factories.
//      *
//      * <br/><br/>
//      * If you develop a custom IFactory implementation you should
//      * extend LocalFactoryBase and override this method, not the instance(Object ... parameters)
//      * method. If your factory implementation calls any other factories it should pass on
//      * both the parameters and localProducts object arrays!!
//      * ... as in factory.instance(parameters, localProducts); .
//      *  Do not just call the instance() or instance(parameters) method.
//      *
//      * @param parameters
//      * @param localProducts
//      * @return The created object.
//      */
//     public Object instance(Object[] parameters, Object[] localProducts);
// }


// Path: src/main/java/com/jenkov/container/impl/factory/MethodFactory.java
import com.jenkov.container.itf.factory.FactoryException;
import com.jenkov.container.itf.factory.ILocalFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;

package com.jenkov.container.impl.factory;



/**

 */
public class MethodFactory extends LocalFactoryBase implements ILocalFactory {

    protected Method         method                        = null;
    protected Object         methodInvocationTarget        = null;    //todo remove this field, or change it to Class (methodOwnerClass).
    protected ILocalFactory  methodInvocationTargetFactory = null;
    protected List<ILocalFactory> methodArgFactories            = new ArrayList<ILocalFactory>();


    public MethodFactory(Method method, ILocalFactory methodInvocationTargetFactory, List<ILocalFactory> methodArgFactories) {
        if(method == null) throw new IllegalArgumentException("Method cannot be null");
        this.method                        = method;
        this.methodInvocationTargetFactory = methodInvocationTargetFactory;
        this.methodArgFactories            = methodArgFactories;
    }

    public MethodFactory(Method method, List<ILocalFactory> methodArgFactories) {
        this.method             = method;
        this.methodArgFactories = methodArgFactories;
    }


    public Class getReturnType() {
        //if a method returns void, it should return the invocation target instead, enabling method chaining on methods returning void.
        if(isVoidReturnType()){
            if(methodInvocationTargetFactory != null) return methodInvocationTargetFactory.getReturnType();
            if(methodInvocationTarget != null)        return methodInvocationTarget.getClass();
        }

        //if a method returns a parameterized product, not java.lang.Object should be returned, but the parameterized type.
        //Type returnType = this.method.getGenericReturnType();
        //if(returnType instanceof ParameterizedType){
        //    return (Class) ((ParameterizedType) returnType).getActualTypeArguments()[0];
        //}
        //if(returnType instanceof TypeVariable){
            
        //}

        return this.method.getReturnType();
    }

    public Object instance(Object[] parameters, Object[] localProducts) {
        Object[] arguments = FactoryUtil.toArgumentArray(this.methodArgFactories, parameters, localProducts);
        try {
            if(this.methodInvocationTargetFactory != null) {
                //if a method returns void, it should return the invocation target instead, enabling method chaining on methods returning void.
                if(isVoidReturnType()){
                    Object target = this.methodInvocationTargetFactory.instance(parameters, localProducts);
                    if(target == null){