package com.typesafe.config;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.time.Duration;
import com.typesafe.config.impl.ConfigImplUtil;

/**
 * Factory for automatic creation of config classes populated with values from config.
 *
 * Example usage:
 *
 * Config configSource = ConfigFactory.parseReader(new InputStreamReader("converters.conf"));
 * ConverterConfig config = ConfigBeanFactory.create(configSource,ConverterConfig.class);
 *
 * Supports nested configs.
 * Supports automatic types conversion
 * (https://github.com/typesafehub/config/blob/master/HOCON.md#automatic-type-conversions).
 */
public class ConfigBeanFactory {

    /**
     * Creates instance of class containing configuration info from config source
     * @param config - source of config information
     * @param clazz - class to be created
     * @param <T>
     * @return - instance of config class populated with data from config source
     */
    public static <T> T create(Config config, Class<T> clazz) {
        return createInternal(config, clazz);
    }

    private static <T> T createInternal(Config config, Class<T> clazz) {
        Map<String, ?> configAsMap = config.root().unwrapped();
        Map<String, Object> configProps = new HashMap<String, Object>();
        Map<String,String> originalNames = new HashMap<String, String>();
        for (Map.Entry<String, ?> configProp : configAsMap.entrySet()) {
            configProps.put(ConfigImplUtil.toCamelCase(configProp.getKey()), configProp.getValue());
            originalNames.put(ConfigImplUtil.toCamelCase(configProp.getKey()),configProp.getKey());
        }

        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(clazz);
        } catch(IntrospectionException e) {
            throw new ConfigException.Generic("Could not get bean information for class " + clazz.getName(), e);
        }

        try {
            T bean = clazz.newInstance();
            for (PropertyDescriptor beanProp : beanInfo.getPropertyDescriptors()) {
                if (beanProp.getReadMethod() == null || beanProp.getWriteMethod() == null) {
                    continue;
                }
                Method setter = beanProp.getWriteMethod();
                Object configValue = configProps.get(beanProp.getName());
                if (configValue == null) {
                    throw new ConfigException.Generic(
                            "Could not find property '" + beanProp.getName() + "' from class '" + clazz.getName() + "' in config.");
                }
                if (configValue instanceof Map) {
                    configValue = createInternal(config.getConfig(originalNames.get(beanProp.getDisplayName())), beanProp.getPropertyType());
                } else {
                    Class parameterClass = setter.getParameterTypes()[0];
                    configValue = getValueWithAutoConversion(parameterClass, config, originalNames.get(beanProp.getDisplayName()));
                }
                setter.invoke(bean, configValue);

            }
            return bean;
        } catch (InstantiationException e) {
            throw new ConfigException.Generic(clazz + " needs a public no args constructor", e);
        } catch (IllegalAccessException e) {
            throw new ConfigException.Generic(clazz + " getters and setters are not accessible", e);
        } catch (InvocationTargetException e) {
            throw new ConfigException.Generic("Calling bean method caused exception", e);
        }
    }

    private static Object getValueWithAutoConversion(Class parameterClass, Config config, String configPropName) {
        if (parameterClass == Boolean.class || parameterClass == boolean.class) {
            return config.getBoolean(configPropName);
        } else if (parameterClass == Byte.class || parameterClass == byte.class) {
            return Integer.valueOf(config.getInt(configPropName)).byteValue();
        } else if (parameterClass == Short.class || parameterClass == short.class) {
            return Integer.valueOf(config.getInt(configPropName)).shortValue();
        } else if (parameterClass == Integer.class || parameterClass == int.class) {
            return config.getInt(configPropName);
        } else if (parameterClass == Double.class || parameterClass == double.class) {
            return config.getDouble(configPropName);
        } else if (parameterClass == Long.class || parameterClass == long.class) {
            return config.getLong(configPropName);
        } else if (parameterClass == String.class) {
            return config.getString(configPropName);
        } else if (parameterClass == Duration.class) {
            return config.getDuration(configPropName);
        } else if (parameterClass == ConfigMemorySize.class) {
            return config.getMemorySize(configPropName);
        }

        return config.getAnyRef(configPropName);
    }
}

@@ -442,69 +442,56 @@
 atic
+ally
  creati
-on of config classes populated with values from c
+ng a Java class from a %7B@link C
 onfig
+%7D
 .%0A *
@@ -512,16 +512,25 @@
 age:%0A *%0A
+ * %3Cpre%3E%0A
  * Confi
@@ -564,75 +564,39 @@
 ory.
-parseReader(new InputStreamReader(%22converters.conf%22));%0A * Converter
+load().getConfig(%22foo%22);%0A * Foo
 Conf
@@ -649,17 +649,12 @@
 rce,
-Converter
+ Foo
 Conf
@@ -670,166 +670,448 @@
 ;%0A *
-%0A * Supports nested configs.%0A * Supports automatic types conversion%0A * (https://github.com/typesafehub/config/blob/master/HOCON.md#automatic-type-conversions)
+ %3C/pre%3E%0A *%0A * The Java class should follow JavaBean conventions. Field types%0A * can be any of the types you can normally get from a %7B@link%0A * Config%7D, including %3Ccode%3Ejava.time.Duration%3C/code%3E or %7B@link%0A * ConfigMemorySize%7D. Fields may also be another JavaBean-style%0A * class.%0A *%0A * Fields are mapped to config by converting the config key to%0A * camel case.  So the key %3Ccode%3Efoo-bar%3C/code%3E becomes JavaBean%0A * setter %3Ccode%3EsetFooBar%3C/code%3E
 .%0A *
@@ -1169,16 +1169,19 @@
 Creates 
+an 
 instance
@@ -1188,62 +1188,63 @@
  of 
+a 
 class
- containing configuration info from config source
+, initializing its fields from a %7B@link Config%7D.
 %0A   
@@ -1260,18 +1260,16 @@
 m config
- -
  source 
@@ -1309,18 +1309,16 @@
 am clazz
- -
  class t
@@ -1322,19 +1322,24 @@
 s to be 
-cre
+instanti
 ated%0A   
@@ -1352,16 +1352,57 @@
 aram %3CT%3E
+ the type of the class to be instantiated
 %0A     * 
@@ -1409,17 +1409,18 @@
 @return 
--
+an
  instanc
@@ -1424,22 +1424,19 @@
 ance of 
-config
+the
  class p
@@ -1459,29 +1459,26 @@
 ta from 
+the 
 config
- source
 %0A     */

Tweak the ConfigBeanFactory javadoc a bit