package org.carrot2.core.parameter;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.Map;

import org.carrot2.core.constraint.Constraint;
import org.carrot2.core.constraint.ConstraintViolationException;

public class ParameterBinder
{
    /**
     * Initialize a given object with default values of instance-time binding parameters ({@link BindingPolicy#INSTANTIATION}).
     */
    public static <T> void bind(T instance, Map<String, Object> values,
        BindingPolicy policy) throws InstantiationException
    {
        if (instance.getClass().getAnnotation(Bindable.class) == null)
        {
            throw new IllegalArgumentException("Class is not bindable: "
                + instance.getClass().getName());
        }

        final Collection<ParameterDescriptor> parameterDescriptors = ParameterDescriptorBuilder
            .getParameterDescriptors(instance.getClass(), policy);

        final Map<String, Field> fields = ParameterDescriptorBuilder.getFieldMap(instance
            .getClass(), policy);

        for (ParameterDescriptor parameterDescriptor : parameterDescriptors)
        {
            Object value = values.get(parameterDescriptor.getKey());

            // Try to coerce from class to its instance first
            if (value instanceof Class)
            {
                if (policy == BindingPolicy.RUNTIME)
                {
                    throw new RuntimeException(
                        "Only instantiation-time parameters can "
                            + "be bound to class values, offending field: "
                            + parameterDescriptor.getKey());
                }

                Class<?> clazz = ((Class<?>) value);
                try
                {
                    value = clazz.newInstance();
                }
                catch (IllegalAccessException e)
                {
                    throw new InstantiationException(
                        "Could not create instance of class: " + clazz.getName()
                            + " for parameter " + parameterDescriptor.getKey());
                }
            }

            if (value != null)
            {
                // Check constraints
                Constraint constraint = parameterDescriptor.getConstraint();
                if (constraint != null)
                {
                    if (!constraint.isMet(value))
                    {
                        throw new ConstraintViolationException(parameterDescriptor,
                            constraint, value);
                    }
                }
            }

            if (value == null)
            {
                // check the default value of parameter.
                value = parameterDescriptor.getDefaultValue();
            }

            final Field field = fields.get(parameterDescriptor.key);
            final Parameter binding = field.getAnnotation(Parameter.class);

            // TODO: if there is no default value, throw an exception.
            if (binding != null && value != null
                && value.getClass().getAnnotation(Bindable.class) != null)
            {
                // Recursively descend into other types.
                bind(value, values, policy);
            }

            try
            {
                field.set(instance, value);
            }
            catch (Exception e)
            {
                throw new RuntimeException("Could not assign field "
                    + instance.getClass().getName() + "#" + field.getName()
                    + " with value " + value, e);
            }
        }
    }

    /**
     * Create an instance of a given class and initialize it with default values of
     * instance-time binding parameters ({@link BindingPolicy#INSTANTIATION}).
     */
    public static <T> T createInstance(Class<T> clazz, Map<String, Object> values)
        throws InstantiationException
    {
        final T instance;
        try
        {
            instance = clazz.newInstance();
        }
        catch (IllegalAccessException e)
        {
            throw new InstantiationException(
                "Could not create instance (illegal access): " + e);
        }

        bind(instance, values, BindingPolicy.INSTANTIATION);

        return instance;
    }
}

@@ -3030,137 +3030,372 @@
     
-// TODO: if there is no default value, throw an exception.%0D%0A            if (binding != null && value != null%0D%0A                && 
+if (binding != null)%0D%0A            %7B%0D%0A                if (value == null)%0D%0A                %7B%0D%0A                    // If there is no default value, throw an exception.%0D%0A                    throw new InstantiationException(%22Parameter field must have a%22 +%0D%0A                    %09%09%22 non-null default value: %22 + field.getName());%0D%0A                %7D%0D%0A%0D%0A                if (
 valu
@@ -3451,35 +3451,43 @@
 l)%0D%0A            
+  
+  
 %7B%0D%0A
+    
                 
@@ -3528,16 +3528,20 @@
 types.%0D%0A
+    
         
@@ -3574,24 +3574,43 @@
 , policy);%0D%0A
+                %7D%0D%0A
             

Throw an exception in case default "class" parameter value is empty.