/*
 * Copyright (C) 2013,2014 The Cat Hive Developers.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.cathive.fx.inject.core;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.AbstractMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javafx.util.Builder;
import javafx.util.StringConverter;
import javafx.util.converter.BigDecimalStringConverter;
import javafx.util.converter.BigIntegerStringConverter;
import javafx.util.converter.BooleanStringConverter;
import javafx.util.converter.ByteStringConverter;
import javafx.util.converter.DefaultStringConverter;
import javafx.util.converter.DoubleStringConverter;
import javafx.util.converter.FloatStringConverter;
import javafx.util.converter.IntegerStringConverter;
import javafx.util.converter.LongStringConverter;
import javafx.util.converter.ShortStringConverter;

/**
 * Ugly helper class.
 * @author Benjamin P. Jung
 * @since 1.1.0
 */
public abstract class FXMLComponentBuilder<T> extends AbstractMap<String, Object> implements Builder<T> {

    // FIXME The whole logic that finds appropriate setter methods and look up the
    //       StringConverters in a Map seems a duplicate of what has already been implemented
    //       in the JavaFX runtime. Unfortunately I couldn't think of any better
    //       solution... :-(

    private static final Map<Class<?>, Class<? extends StringConverter<?>>> STRING_CONVERTERS;
    static {

        STRING_CONVERTERS = new HashMap<>();

        // String
        STRING_CONVERTERS.put(String.class, DefaultStringConverter.class);

        // Primitives
        STRING_CONVERTERS.put(boolean.class, BooleanStringConverter.class);
        STRING_CONVERTERS.put(byte.class, ByteStringConverter.class);
        STRING_CONVERTERS.put(double.class, DoubleStringConverter.class);
        STRING_CONVERTERS.put(float.class, FloatStringConverter.class);
        STRING_CONVERTERS.put(int.class, IntegerStringConverter.class);
        STRING_CONVERTERS.put(long.class, LongStringConverter.class);
        STRING_CONVERTERS.put(short.class, ShortStringConverter.class);

        // Primitive wrappers
        STRING_CONVERTERS.put(Boolean.class, BooleanStringConverter.class);
        STRING_CONVERTERS.put(Byte.class, ByteStringConverter.class);
        STRING_CONVERTERS.put(Double.class, DoubleStringConverter.class);
        STRING_CONVERTERS.put(Float.class, FloatStringConverter.class);
        STRING_CONVERTERS.put(Integer.class, IntegerStringConverter.class);
        STRING_CONVERTERS.put(Long.class, LongStringConverter.class);
        STRING_CONVERTERS.put(Short.class, ShortStringConverter.class);

        // Other types
        STRING_CONVERTERS.put(BigDecimal.class, BigDecimalStringConverter.class);
        STRING_CONVERTERS.put(BigInteger.class, BigIntegerStringConverter.class);
    }

    private final Class<T> componentClass;

    private final Map<String, Object> componentProperties = new HashMap<>();

    protected FXMLComponentBuilder(final Class<T> componentClass) {
        super();
        this.componentClass = componentClass;
    }

    protected abstract <T> T getInstance(Class<T> clazz);

    @Override
    public T build() {
        final T component = getInstance(this.componentClass);
        for (String key: componentProperties.keySet()) {
            final Object value = componentProperties.get(key);
            final String setterName = String.format("set%s%s", key.substring(0, 1).toUpperCase(), key.substring(1));
            try {
                Method setterMethod = null;
                for (Method method: componentClass.getMethods()) {
                    if (method.getName().equals(setterName)) {
                        setterMethod = method;
                        break;
                    }
                }
                if (setterMethod == null) {
                    throw new IllegalStateException(String.format("No setter for field '%s' could be found.", key));
                }
                final Class<?> paramType = setterMethod.getParameterTypes()[0];
                Object paramValue;
                if (Enum.class.isAssignableFrom(paramType)) {
                    paramValue = paramType.getMethod("valueOf", String.class).invoke(setterMethod, value);
                } else {
                    final StringConverter<?> stringConverter = getStringConverter(paramType);
                    paramValue = stringConverter.fromString((String) value);
                }
                setterMethod.invoke(component, paramValue);
            } catch (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | InstantiationException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return component;
    }

    private StringConverter<?> getStringConverter(Class<?> valueClass) throws InstantiationException, IllegalAccessException {

        if (STRING_CONVERTERS.containsKey(valueClass)) {
            return STRING_CONVERTERS.get(valueClass).newInstance();
        } else {
            throw new IllegalArgumentException(String.format("Can't find StringConverter for class '%s'.", valueClass.getName()));
        }

    }

    @Override
    public Object put(String key, Object value) {
        componentProperties.put(key, value);
        return null;
    }

    @Override
    public Set<Map.Entry<String, Object>> entrySet() {
        throw new UnsupportedOperationException();
    }

}
@@ -985,528 +985,143 @@
 ter.
-BigDecimalStringConverter;%0Aimport javafx.util.converter.BigIntegerStringConverter;%0Aimport javafx.util.converter.BooleanStringConverter;%0Aimport javafx.util.converter.ByteStringConverter;%0Aimport javafx.util.converter.DefaultStringConverter;%0Aimport javafx.util.converter.DoubleStringConverter;%0Aimport javafx.util.converter.FloatStringConverter;%0Aimport javafx.util.converter.IntegerStringConverter;%0Aimport javafx.util.converter.LongStringConverter;%0Aimport javafx.util.converter.ShortStringConverter;%0A%0A/**%0A * Ugly helper clas
+*;%0A%0A/**%0A * Abstract base class for all component builders that use some kind of%0A * dependency injection framework to construct instance
 s.%0A 
@@ -1971,32 +1971,107 @@
 nverter.class);%0A
+        STRING_CONVERTERS.put(char.class, CharacterStringConverter.class);%0A
         STRING_C
@@ -2583,32 +2583,112 @@
 nverter.class);%0A
+        STRING_CONVERTERS.put(Character.class, CharacterStringConverter.class);%0A
         STRING_C
@@ -3486,24 +3486,451 @@
 ass;%0A    %7D%0A%0A
+    /**%0A     * Returns an instance of the requested class from the dependency injection framework being used.%0A     * @param clazz%0A     *         Class to be constructed via DI container.%0A     * @param %3CT%3E%0A     *         Type of the class to be constructed.%0A     * @return%0A     *         An instance of the requested class that has been fetched%0A     *         via the concrete implementation of DI container being used.%0A     */%0A
     protecte

Enhance functionality and documentation.