/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.felix.dm.impl;

import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.SortedMap;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;

/**
 * Provides a way for creating type-safe configurations from a {@link Map} or {@link Dictionary}.
 * <p>
 * This class takes a map or dictionary along with a class, the configuration-type, and returns a proxy that converts
 * method calls from the configuration-type to lookups in the map or dictionary. The results of these lookups are then
 * converted to the expected return type of the invoked configuration method.<br>
 * As proxies are returned, no implementations of the desired configuration-type are necessary!
 * </p>
 * <p>
 * The lookups performed are based on the name of the method called on the configuration type. The method names are
 * "mangled" to the following form: <tt>[lower case letter] [any valid character]*</tt>. Method names starting with
 * <tt>get</tt> or <tt>is</tt> (JavaBean convention) are stripped from these prefixes. For example: given a dictionary
 * with the key <tt>"foo"</tt> can be accessed from a configuration-type using the following method names:
 * <tt>foo()</tt>, <tt>getFoo()</tt> and <tt>isFoo()</tt>.
 *  
 * If the property name contains some dots, the the following conventions are used: 
 * <ul> 
 * <li>camel casing: if a property contains multiple words separated by dots, then you can indicate words boundaries using medial capitalization.
 * For example, the property "foo.bar" could be accessed with a method name like "fooBar()" or "getFooBar()".  
 * <li> use underscore to wrap dots: underscore ("_") found in method names are converted to ".", unless they are followed by another underscore.
 * (in this case, the double "__" is then converted to single underscore ("_").
 * For Example: foo_bar() will be mapped to "foo.bar" property, and foo__bar() will be mapped to "foo_bar" property.
 * </ul>
 * </p>
 * <p>
 * The return values supported are: primitive types (or their object wrappers), strings, enums, arrays of
 * primitives/strings, {@link Collection} types, {@link Map} types, {@link Class}es and interfaces. When an interface is
 * returned, it is treated equally to a configuration type, that is, it is returned as a proxy.
 * </p>
 * <p>
 * Arrays can be represented either as comma-separated values, optionally enclosed in square brackets. For example:
 * <tt>[ a, b, c ]</tt> and <tt>a, b,c</tt> are both considered an array of length 3 with the values "a", "b" and "c".
 * Alternatively, you can append the array index to the key in the dictionary to obtain the same: a dictionary with
 * "arr.0" =&gt; "a", "arr.1" =&gt; "b", "arr.2" =&gt; "c" would result in the same array as the earlier examples.
 * </p>
 * <p>
 * Maps can be represented as single string values similarly as arrays, each value consisting of both the key and value
 * separated by a dot. Optionally, the value can be enclosed in curly brackets. Similar to array, you can use the same
 * dot notation using the keys. For example, a dictionary with <tt>"map" => "{key1.value1, key2.value2}"</tt> and a
 * dictionary with <tt>"map.key1" => "value1", "map2.key2" => "value2"</tt> result in the same map being returned.
 * Instead of a map, you could also define an interface with the methods <tt>getKey1()</tt> and <tt>getKey2</tt> and use
 * that interface as return type instead of a {@link Map}.
 * </p>
 * <p>
 * In case a lookup does not yield a value from the underlying map or dictionary, the following rules are applied:
 * <ol>
 * <li>primitive types yield their default value, as defined by the Java Specification;
 * <li>string, {@link Class}es and enum values yield <code>null</code>;
 * <li>for arrays, collections and maps, an empty array/collection/map is returned;
 * <li>for other interface types that are treated as configuration type a null-object is returned.
 * </ol>
 * </p>
 */
public final class Configurable {

	static class ConfigHandler implements InvocationHandler {
        private final ClassLoader m_cl;
        private final Map<?, ?> m_config;
        private Class<?> m_configType;
        
        /** Constant for the single element method */
        private static final String VALUE_METHOD = "value";

        /** Constant for the prefix constant. */
        private static final String PREFIX_CONSTANT = "PREFIX_";

        /** Capture all methods defined by the annotation interface */
        private static final Set<Method> ANNOTATION_METHODS = new HashSet<Method>();
        static
        {
            for(final Method m : Annotation.class.getMethods())
            {
                ANNOTATION_METHODS.add(m);
            }
        }

        public ConfigHandler(Class<?> type, ClassLoader cl, Map<?, ?> config) {
            m_configType = type;
            m_cl = cl;
            m_config = config;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("toString")) {
                return m_config.toString();
            }
            String name = getPropertyName(method.getName());

            Object result = convert(method.getGenericReturnType(), name, m_config.get(name), false /* useImplicitDefault */);
            if (result == null) {
                Object defaultValue = getDefaultValue(proxy, args, method, name);
                if (defaultValue != null) {
                    return defaultValue;
                }
            }

            return result;
        }

        @SuppressWarnings("unchecked")
        private Object convertParameterizedType(ParameterizedType type, String key, Object value, boolean useImplicitDefault) throws Exception {
            Class<?> resultType = (Class<?>) type.getRawType();
            if (Class.class.isAssignableFrom(resultType)) {
                if (value == null) {
                    return null;
                }
                return m_cl.loadClass(value.toString());
            }
            else if (Collection.class.isAssignableFrom(resultType)) {
                Collection<?> input = toCollection(key, value);
                if (input == null && ! useImplicitDefault) {
                	return null;
                }

                if (resultType == Collection.class || resultType == List.class) {
                    resultType = ArrayList.class;
                }
                else if (resultType == Set.class || resultType == SortedSet.class) {
                    resultType = TreeSet.class;
                }
                else if (resultType == Queue.class) {
                    resultType = LinkedList.class;
                }
                else if (resultType.isInterface()) {
                    throw new RuntimeException("Unknown collection interface: " + resultType);
                }

                Collection<Object> result = (Collection<Object>) resultType.newInstance();
                if (input != null) {
                    Type componentType = type.getActualTypeArguments()[0];
                    for (Object i : input) {
                        result.add(convert(componentType, key, i, false /* useImplicitDefault */));
                    }
                }
                return result;
            }
            else if (Map.class.isAssignableFrom(resultType)) {
                Map<?, ?> input = toMap(key, value);
                if (input == null && ! useImplicitDefault) {
                	return null;
                }

                if (resultType == SortedMap.class) {
                    resultType = TreeMap.class;
                }
                else if (resultType == Map.class) {
                    resultType = LinkedHashMap.class;
                }
                else if (resultType.isInterface()) {
                    throw new RuntimeException("Unknown map interface: " + resultType);
                }

                Map<Object, Object> result = (Map<Object, Object>) resultType.newInstance();
                Type keyType = type.getActualTypeArguments()[0];
                Type valueType = type.getActualTypeArguments()[1];

                if (input != null) {
                	for (Map.Entry<?, ?> entry : input.entrySet()) {
                		result.put(convert(keyType, key, entry.getKey(), false /* useImplicitDefault */), convert(valueType, key, entry.getValue(), false /* useImplicitDefault */));
                	}
                }
                return result;
            }

            throw new RuntimeException("Unhandled type: " + type);
        }

        @SuppressWarnings({ "unchecked", "rawtypes" })
        private Object convert(Type type, String key, Object value, boolean useImplicitDefault) throws Exception {
            if (type instanceof ParameterizedType) {
                return convertParameterizedType((ParameterizedType) type, key, value, useImplicitDefault);
            }
            if (type instanceof GenericArrayType) {
                return convertArray(((GenericArrayType) type).getGenericComponentType(), key, value, useImplicitDefault);
            }
            Class<?> resultType = (Class<?>) type;
            if (resultType.isArray()) {
                return convertArray(resultType.getComponentType(), key, value, useImplicitDefault);
            }
            if (resultType.isInstance(value)) {
                return value;
            }

            if (Boolean.class.equals(resultType) || Boolean.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_BOOLEAN : null;
                }
                return Boolean.valueOf(value.toString());
            }
            else if (Byte.class.equals(resultType) || Byte.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_BYTE : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).byteValue();
                }
                return Byte.valueOf(value.toString());
            }
            else if (Short.class.equals(resultType) || Short.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_SHORT : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).shortValue();
                }
                return Short.valueOf(value.toString());
            }
            else if (Integer.class.equals(resultType) || Integer.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_INT : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).intValue();
                }
                return Integer.valueOf(value.toString());
            }
            else if (Long.class.equals(resultType) || Long.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_LONG : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).longValue();
                }
                return Long.valueOf(value.toString());
            }
            else if (Float.class.equals(resultType) || Float.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_FLOAT : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).floatValue();
                }
                return Float.valueOf(value.toString());
            }
            else if (Double.class.equals(resultType) || Double.TYPE.equals(resultType)) {
                if (value == null) {
                    return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_DOUBLE : null;
                }
                if (value instanceof Number) {
                    return ((Number) value).doubleValue();
                }
                return Double.valueOf(value.toString());
            }
            else if (Number.class.equals(resultType)) {
                if (value == null) {
                    return null;
                }
                String numStr = value.toString();
                if (numStr.indexOf('.') > 0) {
                    return Double.valueOf(numStr);
                }
                return Long.valueOf(numStr);
            }
            else if (String.class.isAssignableFrom(resultType)) {
                return value == null ? null : value.toString();
            }
            else if (Enum.class.isAssignableFrom(resultType)) {
                if (value == null) {
                    return null;
                }
                Class<Enum> enumType = (Class<Enum>) resultType;
                return Enum.valueOf(enumType, value.toString().toUpperCase());
            }
            else if (resultType.isInterface()) {
                Map<?, ?> map = toMap(key, value);
                if (map == null) {
                	return useImplicitDefault ? create(resultType, Collections.emptyMap()) : null;
                }
                return create(resultType, map);
            }

            throw new RuntimeException("Unhandled type: " + type);
        }

        private Object convertArray(Type type, String key, Object value, boolean useImplicitDefault) throws Exception {
            if (value instanceof String) {
                String str = (String) value;
                if (type == Byte.class || type == byte.class) {
                    return str.getBytes("UTF-8");
                }
                if (type == Character.class || type == char.class) {
                    return str.toCharArray();
                }
            }

            Collection<?> input = toCollection(key, value);
            if (input == null && useImplicitDefault) {
            	input = Collections.emptyList();
            }
            if (input == null) {
                return null;
            }

            Class<?> componentClass = getRawClass(type);
            Object array = Array.newInstance(componentClass, input.size());

            int i = 0;
            for (Object next : input) {
                Array.set(array, i++, convert(type, key, next, false /* useImplicitDefault */));
            }
            return array;
        }

        private Object getDefaultValue(Object proxy, Object[] args, Method method, String key) throws Throwable {
        	Object def = null;
        	// Handle cases where the method is part of an annotation or is a java8 default method.
        	Class<?> methodClass = method.getDeclaringClass();
        	if (methodClass.isAnnotation()) {
        		// the config type is an annotation: simply invoke the default value
        		def = method.getDefaultValue();
        	} else if (method.isDefault()) {
        		if (System.getProperty("java.version", "1.8").startsWith("1.8")) {
        			// The config type is a java8 interface with a default method, invoke it.
        			// But it's challenging to invoke a default method from a dynamic proxy ... we have to use the MethodHandles.
        			// see https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles
        		
        			Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        			constructor.setAccessible(true);
        			def = constructor.newInstance(methodClass, MethodHandles.Lookup.PRIVATE)
        					.unreflectSpecial(method, methodClass)
        					.bindTo(proxy)
        					.invokeWithArguments(args);
        		} else {
        			// see https://dzone.com/articles/correct-reflective-access-to-interface-default-methods
        			def = MethodHandles.lookup()
        					.findSpecial(methodClass,         							
        								 method.getName(),  
        								 MethodType.methodType(method.getReturnType(), method.getParameterTypes()),  
        								 methodClass)
        					.bindTo(proxy)
        					.invokeWithArguments();
        		}
        	}
            return convert(method.getGenericReturnType(), key, def, true /* useImplicitDefault */);
        }

        private Class<?> getRawClass(Type type) {
            if (type instanceof ParameterizedType) {
                return (Class<?>) ((ParameterizedType) type).getRawType();
            }
            if (type instanceof Class) {
                return (Class<?>) type;
            }
            throw new RuntimeException("Unhandled type: " + type);
        }

        private Collection<?> toCollection(String prefix, Object value) {
            if (value instanceof Collection) {
                return (Collection<?>) value;
            }

            if (value == null) {
                List<Object> result = new ArrayList<>();

                String needle = prefix.concat(".");
                for (Map.Entry<?, ?> entry : m_config.entrySet()) {
                    String key = entry.getKey().toString();
                    if (!key.startsWith(needle)) {
                        continue;
                    }

                    int idx = 0;
                    try {
                        idx = Integer.parseInt(key.substring(needle.length()));
                    }
                    catch (NumberFormatException e) {
                        // Ignore
                    }

                    result.add(Math.min(result.size(), idx), entry.getValue());
                }

                return result.size() == 0 ? null : result;
            }

            if (value.getClass().isArray()) {
                if (value.getClass().getComponentType().isPrimitive()) {
                    int length = Array.getLength(value);
                    List<Object> result = new ArrayList<Object>(length);
                    for (int i = 0; i < length; i++) {
                        result.add(Array.get(value, i));
                    }
                    return result;
                }
                return Arrays.asList((Object[]) value);
            }

            if (value instanceof String) {
                String str = (String) value;
                if (str.startsWith("[") && str.endsWith("]")) {
                    str = str.substring(1, str.length() - 1);
                }
                // don't split in case we are parsing an empty [] list, in which case we need to return an empty list.
                return str.length() == 0 ? Collections.emptyList() : Arrays.asList(str.split("\\s*,\\s*"));
            }

            return Arrays.asList(value);
        }

        private Map<?, ?> toMap(String prefix, Object value) {
            if (value instanceof Map) {
                return (Map<?, ?>) value;
            }

            Map<String, Object> result = new HashMap<>();
            if (value == null) {
                String needle = prefix.concat(".");
                for (Map.Entry<?, ?> entry : m_config.entrySet()) {
                    String key = entry.getKey().toString();
                    if (key.startsWith(needle)) {
                        result.put(key.substring(needle.length()), entry.getValue());
                    }
                }
                if (result.size() == 0) {
                	return null;
                }
            }
            else if (value instanceof String) {
                String str = (String) value;
                if (str.startsWith("{") && str.endsWith("}")) {
                    str = str.substring(1, str.length() - 1);
                }
                for (String entry : str.split("\\s*,\\s*")) {
                    String[] pair = entry.split("\\s*\\.\\s*", 2);
                    if (pair.length == 2) {
                    	result.put(pair[0], pair[1]);
                    }
                }
            }

            return result;
        }
        
        private String getPropertyName(String methodName) {            
            // First, check if the config type defines a standard PREFIX_ string.
            String prefix = getPrefix(m_configType);
            
            // If the configuration type is a single valued annotation, derive the property name
            // from the interface name, using OSGi R7 Scr convention 
            
            if (isSingleElementAnnotation(m_configType) && methodName.equals(VALUE_METHOD)) {
                String propertyName = mapTypeNameToKey(m_configType.getSimpleName());
                return prefix == null ? propertyName : prefix.concat(propertyName);
            }
            
            // Now, derive the property name from the method name, using simple javabean convention.
            // i.e: fooBar() or getFooBar() will map to "fooBar" property.
            
            String javaBeanMethodName = derivePropertyNameUsingJavaBeanConvention(methodName);
            if (hasValueFor(javaBeanMethodName)) {
                // there is a value in the actual configuration for the derived property name.
                return javaBeanMethodName;
            }
            
            // Derive the property name from the method name, using javabeans and/or camel casing convention,
            // where each capital letter is assumed to map a "dot".
            // i.e: fooBar() or getFooBar() will map to "foo.bar" property.

            String camelCasePropertyName = derivePropertyNameUsingCamelCaseConvention(javaBeanMethodName);
            if (hasValueFor(camelCasePropertyName)) {
                // there is a value in the actual configuration for the derived property name.
                return camelCasePropertyName;
            }
            
            // Derive the property name from the method name, using OSGi metatype convention,
            // where a "_" is mapped to a dot, except if the understcore is followed by another undescore
            // (in this case, the double "__" is replaced by "_").
            // i.e: foo_bar() will map to "foo.bar" property and foo__bar() will map to "foo_bar" property.
            
            String metaTypePropertyName = derivePropertyNameUsingMetaTypeConvention(methodName);
            if (hasValueFor(metaTypePropertyName)) {
                // there is a value in the actual configuration for the derived property name.
                return metaTypePropertyName;
            }
            
            // No value could be found, return by default a property name derived from javabean convention.
            return javaBeanMethodName;
        }
        
        private String derivePropertyNameUsingJavaBeanConvention(String methodName) {
            StringBuilder sb = new StringBuilder(methodName);
            
            if (methodName.startsWith("get")) {
                sb.delete(0, 3);
            } else if (methodName.startsWith("is")) {
                sb.delete(0, 2);
            }
            
            char c = sb.charAt(0);
            if (Character.isUpperCase(c)) {
                sb.setCharAt(0, Character.toLowerCase(c));
            }
            
            return (sb.toString());
        }

        private String derivePropertyNameUsingCamelCaseConvention(String methodName) {
            StringBuilder sb = new StringBuilder(methodName);
            for (int i = 0; i < sb.length(); i++) {
                char c = sb.charAt(i);
                if (Character.isUpperCase(c)) {
                    // camel casing: replace fooBar -> foo.bar
                    sb.setCharAt(i, Character.toLowerCase(c));
                    sb.insert(i, ".");
                }
            }
            return sb.toString();            
        }
        
        // see metatype spec, chapter 105.9.2 in osgi r6 cmpn.
        private String derivePropertyNameUsingMetaTypeConvention(String methodName) {
            StringBuilder sb = new StringBuilder(methodName);
            // replace "__" by "_" or "_" by ".": foo_bar -> foo.bar; foo__BAR_zoo -> foo_BAR.zoo
            for (int i = 0; i < sb.length(); i ++) {
                if (sb.charAt(i) == '_') {
                    if (i < (sb.length() - 1) && sb.charAt(i+1) == '_') {
                        // replace foo__bar -> foo_bar
                        sb.replace(i, i+2, "_");
                    } else {
                        // replace foo_bar -> foo.bar
                        sb.replace(i, i+1, ".");
                    }
                } else if (sb.charAt(i) == '$') {
                    if (i < (sb.length() - 1) && sb.charAt(i+1) == '$') {
                        // replace foo__bar -> foo_bar
                        sb.replace(i, i+2, "$");
                    } else {
                        // remove single dollar character.
                        sb.delete(i, i+1);
                    }
                }
            }
            return sb.toString();
        }
        
        /**
         * Checks if a property name has a given value. This method takes care about special array values (arr.0, arr.1,...) 
         * and about map values (map.key1, map.key2, ...).
         * 
         * @param property name
         * @return true if the given property has a value in the actual configuration, false if not.
         */
        private boolean hasValueFor(String property)
        {
            if (m_config.containsKey(property)) {
                return true;
            }
            String needle = property.concat(".");       
            for (Map.Entry<?, ?> entry : m_config.entrySet()) {
                String key = entry.getKey().toString();
                if (key.startsWith(needle)) {
                    return true;
                }
            }
            return false;
        }
        
        // Code derived from Apache Felix SCR (See org.apache.felix.scr.impl.inject.Annotations.java)
        private String getPrefix(Class<?> clazz)
        {
            try
            {
                final Field f = clazz.getField(PREFIX_CONSTANT);
                if ( Modifier.isStatic(f.getModifiers())
                     && Modifier.isPublic(f.getModifiers())
                     && Modifier.isFinal(f.getModifiers())
                     && String.class.isAssignableFrom(f.getType()))
                {
                    final Object value = f.get(null);
                    if ( value != null )
                    {
                        return value.toString();
                    }
                }
            }
            catch ( final Exception ignore)
            {
                // ignore
            }
            return null;
        }
        
        /**
         * Check whether the provided type is a single element annotation.
         * A single element annotation has a method named "value" and all
         * other annotation methods must have a default value.
         * @param clazz The provided type
         * @return {@code true} if the type is a single element annotation.
         */
        static public boolean isSingleElementAnnotation(final Class<?> clazz)
        {
            boolean result = false;
            if ( clazz.isAnnotation() )
            {
                result = true;
                boolean hasValue = false;
                for ( final Method method: clazz.getMethods() )
                {
                    // filter out methods from Annotation
                    boolean isFromAnnotation = false;
                    for(final Method objMethod : ANNOTATION_METHODS)
                    {
                        if ( objMethod.getName().equals(method.getName())
                          && Arrays.equals(objMethod.getParameterTypes(), method.getParameterTypes()) )
                        {
                            isFromAnnotation = true;
                            break;
                        }
                    }
                    if ( isFromAnnotation )
                    {
                        continue;
                    }
                    if ( VALUE_METHOD.equals(method.getName()) )
                    {
                        hasValue = true;
                        continue;
                    }
                    if ( method.getDefaultValue() == null )
                    {
                        result = false;
                        break;
                    }

                }
                if ( result )
                {
                    result = hasValue;
                }

            }
            return result;
        }

        static String mapTypeNameToKey(String name)
        {
            final StringBuilder sb = new StringBuilder();
            boolean lastLow = false;
            for(final char c : name.toCharArray())
            {
                if ( lastLow && (Character.isLetter(c) || Character.isDigit(c)) && Character.isUpperCase(c) )
                {
                    sb.append('.');
                }
                lastLow = false;
                if ( (Character.isLetter(c) || Character.isDigit(c)) && Character.isLowerCase(c))
                {
                    lastLow = true;
                }
                sb.append(Character.toLowerCase(c));
            }
            return sb.toString();
        }

    }

    private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE;
    private static final Byte DEFAULT_BYTE = new Byte((byte) 0);
    private static final Short DEFAULT_SHORT = new Short((short) 0);
    private static final Integer DEFAULT_INT = new Integer(0);
    private static final Long DEFAULT_LONG = new Long(0);
    private static final Float DEFAULT_FLOAT = new Float(0.0f);
    private static final Double DEFAULT_DOUBLE = new Double(0.0);

    /**
     * Creates a configuration for a given type backed by a given dictionary.
     * 
     * @param type the configuration class, cannot be <code>null</code>;
     * @param config the configuration to wrap, cannot be <code>null</code>.
     * @param serviceProperties the component service properties, cannot be <code>null</code>.
     * @return an instance of the given type that wraps the given configuration.
     */
    public static <T> T create(Class<T> type, Dictionary<?, ?> config, Dictionary<?,?> serviceProperties) {
        Map<Object, Object> map = new HashMap<Object, Object>();
        for (Enumeration<?> e = serviceProperties.keys(); e.hasMoreElements();) {
            Object key = e.nextElement();
            map.put(key, serviceProperties.get(key));
        }        
        for (Enumeration<?> e = config.keys(); e.hasMoreElements();) {
            Object key = e.nextElement();
            map.put(key, config.get(key));
        }
        return create(type, map);
    }

    /**
     * Creates a configuration for a given type backed by a given map.
     * 
     * @param type the configuration class, cannot be <code>null</code>;
     * @param config the configuration to wrap, cannot be <code>null</code>.
     * @return an instance of the given type that wraps the given configuration.
     */
    public static <T> T create(Class<T> type, Map<?, ?> config) {
        ClassLoader cl = type.getClassLoader();
        Object result = Proxy.newProxyInstance(cl, new Class<?>[] { type }, new ConfigHandler(type, cl, config));
        return type.cast(result);
    }
}
