/*
* JBoss, Home of Professional Open Source
* Copyright 2007, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.metatype.plugins.values;

import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.Stack;
import java.util.WeakHashMap;
import java.util.Map.Entry;

import javax.management.ObjectName;

import org.jboss.beans.info.spi.BeanInfo;
import org.jboss.beans.info.spi.PropertyInfo;
import org.jboss.config.plugins.property.PropertyConfiguration;
import org.jboss.config.spi.Configuration;
import org.jboss.metatype.api.types.ArrayMetaType;
import org.jboss.metatype.api.types.CollectionMetaType;
import org.jboss.metatype.api.types.CompositeMetaType;
import org.jboss.metatype.api.types.EnumMetaType;
import org.jboss.metatype.api.types.GenericMetaType;
import org.jboss.metatype.api.types.MapCompositeMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.types.MetaTypeFactory;
import org.jboss.metatype.api.types.PropertiesMetaType;
import org.jboss.metatype.api.types.SimpleMetaType;
import org.jboss.metatype.api.types.TableMetaType;
import org.jboss.metatype.api.values.ArrayValue;
import org.jboss.metatype.api.values.ArrayValueSupport;
import org.jboss.metatype.api.values.CollectionValue;
import org.jboss.metatype.api.values.CollectionValueSupport;
import org.jboss.metatype.api.values.CompositeValue;
import org.jboss.metatype.api.values.CompositeValueSupport;
import org.jboss.metatype.api.values.EnumValue;
import org.jboss.metatype.api.values.EnumValueSupport;
import org.jboss.metatype.api.values.GenericValue;
import org.jboss.metatype.api.values.GenericValueSupport;
import org.jboss.metatype.api.values.InstanceFactory;
import org.jboss.metatype.api.values.MapCompositeValueSupport;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.MetaValueFactory;
import org.jboss.metatype.api.values.PropertiesMetaValue;
import org.jboss.metatype.api.values.SimpleValue;
import org.jboss.metatype.api.values.SimpleValueSupport;
import org.jboss.metatype.api.values.TableValue;
import org.jboss.metatype.api.values.TableValueSupport;
import org.jboss.metatype.plugins.types.DefaultMetaTypeFactory;
import org.jboss.metatype.plugins.values.mappers.PropertiesObjectNameMetaMapper;
import org.jboss.metatype.spi.values.MetaMapper;
import org.jboss.metatype.spi.values.MetaValueBuilder;
import org.jboss.reflect.plugins.introspection.ParameterizedClassInfo;
import org.jboss.reflect.spi.ArrayInfo;
import org.jboss.reflect.spi.ClassInfo;
import org.jboss.reflect.spi.TypeInfo;
import org.jboss.reflect.spi.TypeInfoFactory;

/**
 * DefaultMetaValueFactory.
 * 
 * @author <a href="ales.justin@jboss.com">Ales Justin</a>
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @author Scott.Stark@jboss.org
 * @version $Revision: 87758 $
 */
public class DefaultMetaValueFactory extends MetaValueFactory
{
   /** The metatype factory */
   private MetaTypeFactory metaTypeFactory = MetaTypeFactory.getInstance();

   /** The primitive default value map */
   private static final Map<Class<?>, Object> primitiveDefaultValueMap;

   static
   {
      primitiveDefaultValueMap = new HashMap<Class<?>, Object>();
      primitiveDefaultValueMap.put(byte.class, (byte)0);
      primitiveDefaultValueMap.put(boolean.class, false);
      primitiveDefaultValueMap.put(char.class, '0');
      primitiveDefaultValueMap.put(short.class, (short)0);
      primitiveDefaultValueMap.put(int.class, 0);
      primitiveDefaultValueMap.put(long.class, 0l);
      primitiveDefaultValueMap.put(float.class, (float)0);
      primitiveDefaultValueMap.put(double.class, (double)0);
   }

   /** The configuration */
   private static Configuration configuration;

   static
   {
      configuration = AccessController.doPrivileged(new PrivilegedAction<Configuration>()
      {
         public Configuration run()
         {
            return new PropertyConfiguration();
         }
      });
   }

   /** The thread local stack of objects mapped to meta values */
   private ThreadLocal<Stack<Map<Object, MetaValue>>> mappingStack = new ThreadLocal<Stack<Map<Object, MetaValue>>>()
   {
      protected Stack<Map<Object, MetaValue>> initialValue()
      {
         return new Stack<Map<Object, MetaValue>>();
      }
   };

   /** The builders */
   private Map<Class<?>, WeakReference<MetaValueBuilder<?>>> builders = new WeakHashMap<Class<?>, WeakReference<MetaValueBuilder<?>>>();
 
   /** The Object type info */
   private static final TypeInfo OBJECT_TYPE_INFO = configuration.getTypeInfo(Object.class);

   /** The instance factory builders */
   private Map<Class<?>, InstanceFactory<?>> instanceFactoryMap = new WeakHashMap<Class<?>, InstanceFactory<?>>();

   public DefaultMetaValueFactory()
   {
      // set default collection instance factories
      setInstanceFactory(List.class, ListInstanceFactory.INSTANCE);
      setInstanceFactory(Set.class, SetInstanceFactory.INSTANCE);
      setInstanceFactory(SortedSet.class, SortedSetInstanceFactory.INSTANCE);
   }

   public void setBuilder(Class<?> clazz, MetaValueBuilder<?> builder)
   {
      synchronized (builders)
      {
         if (builder == null)
            builders.remove(clazz);
         builders.put(clazz, new WeakReference<MetaValueBuilder<?>>(builder));
      }
   }

   public <T> void setInstanceFactory(Class<T> clazz, InstanceFactory<T> factory)
   {
      synchronized(instanceFactoryMap)
      {
         if (factory == null)
            instanceFactoryMap.remove(clazz);
         else
            instanceFactoryMap.put(clazz, factory);
      }
   }

   /**
    * Create a simple value
    * 
    * @param type the type
    * @param value the value
    * @return the simple value
    */
   public static SimpleValue createSimpleValue(SimpleMetaType type, Serializable value)
   {
      if (value == null)
         return null;

      return new SimpleValueSupport(type, value);
   }

   /**
    * Create an enum value
    * 
    * @param <T> the enum type
    * @param type the type
    * @param value the value
    * @return the enum value
    */
   public static <T extends Enum<?>> EnumValue createEnumValue(EnumMetaType type, T value)
   {
      if (value == null)
         return null;

      return new EnumValueSupport(type, value.name());
   }

   /**
    * Create a generic value
    * 
    * @param type the type
    * @param value the value
    * @param mapping the mapping
    * @return the generic value
    */
   public static GenericValue createGenericValue(GenericMetaType type, Object value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;
      
      if (value instanceof Serializable == false)
         throw new IllegalArgumentException("Not serializable: " + value.getClass().getName());

      GenericValue result = new GenericValueSupport(type, (Serializable) value);
      mapping.put(value, result);
      return result;
   }

   /**
    * Create a collection value
    *
    * @param type the type
    * @param value the value
    * @param mapping the mapping
    * @return the generic value
    */
   public CollectionValue createCollectionValue(CollectionMetaType type, Object value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;

      Collection<?> collection = (Collection<?>)value;
      MetaValue[] elements = new MetaValue[collection.size()];
      int i = 0;
      for(Object ce : collection)
      {
         // recalculate element info, since usually more deterministic
         TypeInfo typeInfo = configuration.getTypeInfo(ce.getClass());
         MetaType metaType = metaTypeFactory.resolve(typeInfo);
         elements[i++] = internalCreate(ce, typeInfo, metaType);             
      }
      CollectionValue result = new CollectionValueSupport(type, elements);
      mapping.put(value, result);
      return result;
   }

   /**
    * Transform a primitive array into an Object[]. Converts
    * a primitive array like char[] to Object[]. 
    * 
    * @param type - the primitive array class type info.
    * @param value - the primitive array instance.
    * @return object array
    */
   public static Object[] convertPrimativeArray(TypeInfo type, Object value)
   {
      if (value == null)
         return null;

      Object[] oa;
      if( type instanceof ArrayInfo )
      {
         // Get the Object form of the element
         ArrayInfo arrayInfo = ArrayInfo.class.cast(type);
         TypeInfo etype = arrayInfo.getComponentType();
         int size = Array.getLength(value);
         oa = new Object[size];
         for(int n = 0; n < size; n ++)
         {
            Object nvalue = Array.get(value, n);
            // Recursively convert nested array elements
            if (etype.isArray())
            {
               oa[n] = convertPrimativeArray(etype, nvalue);
            }
            oa[n] = nvalue;
         }
      }
      else
      {
         oa = (Object[]) value;
      }
      
      return oa;      
   }

   /**
    *  Map null to primitive.
    *
    * @param type the type info
    * @return default value
    */
   @SuppressWarnings("deprecation")
   public static Object mapNullToPrimitive(TypeInfo type)
   {
      return primitiveDefaultValueMap.get(type.getType());
   }

   /**
    * Transform a primitive array into an Object[]. Converts
    * a primitive array like char[] to Object[].
    *
    * @param value - the primitive array instance.
    * @return object array
    */
   public static Object[] convertPrimativeArray(Object value)
   {
      if (value == null)
         return null;
      return convertPrimativeArray(configuration.getTypeInfo(value.getClass()), value);
   }

   /**
    * Create an array value
    * 
    * @param type the type
    * @param value the value
    * @param mapping the mapping
    * @return the composite value
    */
   @SuppressWarnings("unchecked")
   public ArrayValue createArrayValue(ArrayMetaType type, Object value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;

      ArrayValueSupport result = new ArrayValueSupport(type);
      mapping.put(value, result);
      
      Object[] array;
      
      MetaType elementType = type.getElementType();
      int dimension = type.getDimension();
      
      Object[] oldArray;
      Class<?> componentType;
      try
      {
         componentType = Class.forName(type.getClassName());
      }
      catch (Exception e)
      {
         throw new RuntimeException("Unable to determine component type for " + type, e);
      }
      
      ClassInfo classInfo = configuration.getClassInfo(value.getClass());
      if (classInfo.isArray())
      {
         // See if this is a primitive array
         ArrayInfo arrayInfo = ArrayInfo.class.cast(classInfo);
         TypeInfo compInfo = arrayInfo.getComponentType();
         while(compInfo instanceof ArrayInfo)
         {
            arrayInfo = ArrayInfo.class.cast(compInfo);
            compInfo = arrayInfo.getComponentType();
         }
         // Translate 
         if (compInfo.isPrimitive())
            oldArray = convertPrimativeArray(classInfo, value);
         else
            oldArray = (Object[]) value;
      }
      else
         throw new UnsupportedOperationException("Cannot construct array for " + value.getClass());
      
      array = createArray(elementType, componentType.getComponentType(), dimension, oldArray);
      result.setValue(array);
      return result;
   }

   /**
    * Create an array
    * 
    * @param elementType the element type
    * @param componentType the component type
    * @param dimension the dimension
    * @param oldArray the old array
    * @return the array
    */
   protected Object[] createArray(MetaType elementType, Class<?> componentType, int dimension, Object[] oldArray)
   {
      if (oldArray == null)
         return null;
      
      Object[] newArray = new Object[oldArray.length];
      
      if (dimension > 1)
      {
         Object[] nestedOld;
         for (int i = 0; i < oldArray.length; ++i)
         {
            if ( !(oldArray[i] instanceof Object[]) )
               nestedOld = convertPrimativeArray(oldArray[i]);
            else
               nestedOld = (Object[]) oldArray[i];
            Object[] result = createArray(elementType, componentType.getComponentType(), dimension-1, nestedOld);
            newArray[i] = result;
         }
      }
      else
      {
         for (int i = 0; i < oldArray.length; ++i)
            newArray[i] = internalCreate(oldArray[i], null, elementType);
      }
      
      return newArray;
   }
   
   /**
    * Create a composite value
    * 
    * @param type the type
    * @param value the value
    * @param mapping the mapping
    * @return the composite value
    */
   @SuppressWarnings("unchecked")
   public CompositeValue createCompositeValue(CompositeMetaType type, Object value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;
      
      // See if this is a Map<String,?> type
      if(type instanceof MapCompositeMetaType)
      {
         if((value instanceof Map) == false)
            throw new RuntimeException("Expected Map value for: " + type+", was: "+(value != null ? value.getClass() : "null"));
         Map<String,?> map = (Map) value;
         MapCompositeMetaType mapType = (MapCompositeMetaType) type;
         MetaType mapValueType = mapType.getValueType();
         MapCompositeValueSupport result = new MapCompositeValueSupport(mapValueType);
         for(Entry<String,?> entry : map.entrySet())
         {
            Object entryValue = entry.getValue();
            MetaValue entryMetaValue = internalCreate(entryValue, null, mapValueType);
            result.put(entry.getKey(), entryMetaValue);
         }
         mapping.put(value, result);
         return result;
      }

      CompositeValueSupport result = new CompositeValueSupport(type);
      mapping.put(value, result);

      BeanInfo beanInfo;
      try
      {
         ClassLoader cl = value.getClass().getClassLoader();
         if (cl == null)
            beanInfo = configuration.getBeanInfo(value.getClass());
         else
            beanInfo = configuration.getBeanInfo(type.getTypeName(), cl);
      }
      catch (Exception e)
      {
         throw new RuntimeException("Error retrieving BeanInfo for " + type, e);
      }

      for (String name : type.itemSet())
      {
         MetaType itemType = type.getType(name);
         Object itemValue = null;
         try
         {
            PropertyInfo property = beanInfo.getProperty(name);
            if (property.isReadable())
               itemValue = beanInfo.getProperty(value, name);
         }
         catch (RuntimeException e)
         {
            throw e;
         }
         catch (Error e)
         {
            throw e;
         }
         catch (Throwable t)
         {
            throw new RuntimeException("Error getting property: " + name + " for " + value.getClass(), t);
         }

         MetaValue item = internalCreate(itemValue, null, itemType);
         result.set(name, item);
      }
      
      return result;
   }

   /**
    * Create a table value
    * 
    * @param type the type
    * @param value the value
    * @param mapping the mapping
    * @return the table value
    */
   @SuppressWarnings("unchecked")
   public TableValue createTableValue(TableMetaType type, Map value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;
      
      TableValueSupport table = new TableValueSupport(type);
      mapping.put(value, table);
      
      CompositeMetaType entryType = type.getRowType();
      MetaType keyType = entryType.getType(DefaultMetaTypeFactory.MAP_KEY);
      MetaType valType = entryType.getType(DefaultMetaTypeFactory.MAP_VALUE);

      for (Iterator<Map.Entry> i = value.entrySet().iterator(); i.hasNext();)
      {
         Map.Entry entry = i.next();
         MetaValue key = internalCreate(entry.getKey(), null, keyType);
         MetaValue val = internalCreate(entry.getValue(), null, valType);
         CompositeValueSupport data = new CompositeValueSupport(entryType, DefaultMetaTypeFactory.MAP_ITEM_NAMES, new MetaValue[] { key, val });
         table.put(data);
      }

      return table;
   }
   
   @Override
   public MetaValue create(Object value)
   {
      return internalCreate(value, null, null);
   }
   
   @Override
   public MetaValue create(Object value, Type type)
   {
      TypeInfo typeInfo = configuration.getTypeInfo(type);
      return internalCreate(value, typeInfo, null);
   }
   
   @Override
   public MetaValue create(Object value, TypeInfo type)
   {
      return internalCreate(value, type, null);
   }
   @Override
   public MetaValue create(Object value, MetaType type)
   {
      return internalCreate(value, null, type);
   }

   @Override
   public Object unwrap(MetaValue metaValue)
   {
      return internalUnwrap(metaValue, null);
   }

   @Override
   public Object unwrap(MetaValue metaValue, Type type)
   {
      TypeInfo typeInfo = configuration.getTypeInfo(type);
      return internalUnwrap(metaValue, typeInfo);
   }

   @Override
   public Object unwrap(MetaValue metaValue, TypeInfo type)
   {
      return internalUnwrap(metaValue, type);
   }

   /**
    * Unwrap value from meta value.
    *
    * @param metaValue the meta value
    * @param type expected type info
    * @return unwrapped value
    */
   protected Object internalUnwrap(MetaValue metaValue, TypeInfo type)
   {
      if (metaValue == null)
         return null;

      MetaMapper<?> mapper = MetaMapper.getMetaMapper(type);
      if (mapper != null)
         return mapper.unwrapMetaValue(metaValue);
      
      MetaType metaType = metaValue.getMetaType();

      if (metaType.isSimple())
      {
         Serializable value = ((SimpleValue)metaValue).getValue();
         Object v = getValue(metaType, type, value);
         if(v == null && metaType.isPrimitive())
         {
            if (type == null)
               type = getTypeInfo(metaType, metaValue);
            return mapNullToPrimitive(type);
         }
          else
            return v; 
      }
      else if (metaType.isEnum())
      {
         String value = ((EnumValue)metaValue).getValue();
         return getValue(metaType, type, value);
      }
      else if (metaType.isGeneric())
      {
         Serializable value = ((GenericValue)metaValue).getValue();
         return getValue(metaType, type, value);
      }
      else if (metaType.isArray())
      {
         ArrayValue arrayValue = (ArrayValue)metaValue;
         if (type == null)
            type = getTypeInfo(metaType, arrayValue.getValue());
         Object array = newArrayInstance(type, arrayValue.getLength());
         for (int i = 0; i < Array.getLength(array); i++)
         {
            Object element = arrayValue.getValue(i);
            if (element instanceof MetaValue)
               element = unwrapMetaValue((MetaValue)element, type, array);
            else if (element != null && element.getClass().isArray())
               element = unwrapArray(array, element);

            Array.set(array, i, element);
         }
         return array;
      }
      else if (metaType.isProperties())
      {
         PropertiesMetaValue propsValue = (PropertiesMetaValue) metaValue;
         return unwrapProperties(propsValue, type);
      }
      else if (metaType.isComposite())
      {
         CompositeValue compositeValue = (CompositeValue)metaValue;
         return unwrapComposite(compositeValue, type);
      }
      else if (metaType.isCollection())
      {
         CollectionValue collectionValue = (CollectionValue)metaValue;
         return unwrapCollection(collectionValue, type);
      }
      else if (metaType.isTable())
      {
         TableValue tableValue = (TableValue)metaValue;
         return unwrapTable(tableValue, type);
      }

      throw new IllegalArgumentException("Unsupported meta value: " + metaValue.getMetaType());
   }

   /**
    * Do a simple check.
    * If current type param is null,
    * try getting type info from meta type
    * and value's classloader.
    *
    * @param type the type info
    * @param value tester value
    * @param metaType the meta type
    * @return type info
    */
   protected TypeInfo checkTypeInfo(TypeInfo type, Object value, MetaType metaType)
   {
      if (type == null && value != null)
         type = getTypeInfo(metaType, value);
      return type;
   }

   /**
    * Get the value.
    * Join type check and value conversion.
    *
    * @param metaType the meta type
    * @param typeInfo the type info
    * @param value the value
    * @return the converted value
    */
   protected Object getValue(MetaType metaType, TypeInfo typeInfo, Object value)
   {
      typeInfo = checkTypeInfo(typeInfo, value, metaType);
      return convertValue(value, typeInfo);
   }

   /**
    * Unwrap MetaValue.
    *
    * @param element the meta value
    * @param type parent type
    * @param array parent array
    * @return unwrapped value
    */
   protected Object unwrapMetaValue(MetaValue element, TypeInfo type, Object array)
   {
      TypeInfo elementType;
      if (type instanceof ClassInfo)
         elementType = ((ClassInfo)type).getComponentType();
      else
         elementType = getTypeInfo(element.getMetaType(), array);
      return unwrap(element, elementType);
   }

   /**
    * Unwrap array.
    * @param array parent array
    * @param element current array element
    * @return unwrapped array element
    */
   protected Object unwrapArray(Object array, Object element)
   {
      TypeInfo elementType = configuration.getTypeInfo(array.getClass().getComponentType());
      int subSize = Array.getLength(element);
      Object newElement = newArrayInstance(elementType, subSize);
      for(int i = 0; i < subSize; i++)
      {
         Object subElement = Array.get(element, i);
         if (subElement instanceof MetaValue)
            subElement = unwrapMetaValue((MetaValue)subElement, elementType, newElement);
         if (subElement != null && subElement.getClass().isArray())
            subElement = unwrapArray(newElement, subElement);

         Array.set(newElement, i, subElement);
      }
      return newElement;
   }

   protected Object unwrapProperties(PropertiesMetaValue propsValue, TypeInfo type)
   {
      Class<?> clazz = type.getType();
      // Handle unwrapping to ObjectName
      if(clazz.isAssignableFrom(ObjectName.class))
      {
         String domainUnwrap = (String) propsValue.remove("domain");
         ObjectName name;
         try
         {
            name = PropertiesObjectNameMetaMapper.createObjectName(domainUnwrap, propsValue);
         }
         catch (Exception e)
         {
            throw new IllegalStateException("Failed to unwrap Properties to ObjectName", e);
         }
         return name;
      }
      // Just return the value as a java.util.Properties
      return propsValue;
   }

   /**
    * Unwrap composite.
    *
    * @param compositeValue the composite value
    * @param typeInfo expected type info
    * @return unwrapped value
    */
   @SuppressWarnings("deprecation")
   protected Object unwrapComposite(CompositeValue compositeValue, TypeInfo typeInfo)
   {
      CompositeMetaType compositeMetaType = compositeValue.getMetaType();
      String typeName = compositeMetaType.getTypeName();
      ClassLoader cl;
      if (typeInfo != null)
         cl = typeInfo.getType().getClassLoader();
      else
         cl = Thread.currentThread().getContextClassLoader();
      
      try
      {
         BeanInfo beanInfo = configuration.getBeanInfo(typeName, cl);
         ClassInfo classInfo = beanInfo.getClassInfo();
         Class<?> clazz = classInfo.getType();
         if (classInfo.isInterface())
         {
            // Handle map specially
            if (clazz.isAssignableFrom(Map.class))
               return unwrapCompositeMap(compositeValue);

            InvocationHandler handler = createCompositeValueInvocationHandler(compositeValue);
            Class<?>[] interfaces = new Class[]{clazz};
            return Proxy.newProxyInstance(clazz.getClassLoader(), interfaces, handler);            
         }
         else if(clazz.isAssignableFrom(ObjectName.class))
         {
            // TODO: this should be handled more generically
            MetaValue domain = compositeValue.get("domain");
            String domainUnwrap = (String) unwrap(domain, String.class);
            MetaValue keys = compositeValue.get("keyPropertyList");
            Hashtable keysUnwrap = null;
            if(keys instanceof PropertiesMetaValue)
               keysUnwrap = (PropertiesMetaValue) keys;
            ObjectName name = new ObjectName(domainUnwrap, keysUnwrap);
            return name;
         }

         Object bean = createNewInstance(beanInfo);
         for (String name : compositeMetaType.itemSet())
         {
            MetaValue itemValue = compositeValue.get(name);
            PropertyInfo propertyInfo = beanInfo.getProperty(name);
            Object value = unwrap(itemValue, propertyInfo.getType());
            propertyInfo.set(bean, value);
         }
         return bean;
      }
      catch (Throwable t)
      {
         throw new UndeclaredThrowableException(t);
      }
   }

   protected Map unwrapCompositeMap(CompositeValue compositeValue)
   {
      HashMap map = new HashMap();
      CompositeMetaType metaType = compositeValue.getMetaType();
      for(String key : metaType.itemSet())
      {
         MetaValue mv = compositeValue.get(key);
         Object value = unwrap(mv);
         map.put(key, value);
      }
      return map;
   }

   /**
    * Create composite invocation handler.
    *
    * @param compositeValue the composite value
    * @return composite invocation handler
    */
   protected InvocationHandler createCompositeValueInvocationHandler(CompositeValue compositeValue)
   {
      return new CompositeValueInvocationHandler(compositeValue);
   }

   /**
    * Unwrap collection meta value.
    *
    * @param collectionValue the collection value
    * @param type the type info
    * @return unwrapped collection
    */
   @SuppressWarnings("unchecked")
   protected Object unwrapCollection(CollectionValue collectionValue, TypeInfo type)
   {
      try
      {
         BeanInfo collectionInfo;
         // null is not instance of
         if (type instanceof ClassInfo)
         {
            collectionInfo = configuration.getBeanInfo(type);
         }
         else
         {
            MetaType metaType = collectionValue.getMetaType();
            collectionInfo = configuration.getBeanInfo(metaType.getTypeName(), Thread.currentThread().getContextClassLoader());
         }
         ClassInfo classInfo = collectionInfo.getClassInfo();
         Collection collection = (Collection)createNewInstance(collectionInfo);

         TypeInfo componentType = classInfo.getComponentType();
         boolean isObjectTypeInfo = OBJECT_TYPE_INFO.equals(componentType);

         for (MetaValue metaValue : collectionValue)
         {
            TypeInfo iterTypeInfo = isObjectTypeInfo ? getTypeInfo(metaValue.getMetaType(), null) : componentType;
            collection.add(unwrap(metaValue, iterTypeInfo));
         }
         return collection;
      }
      catch (Throwable t)
      {
         throw new UndeclaredThrowableException(t);
      }
   }

   /**
    * Unwrap table meta value.
    *
    * @param tableValue the table value
    * @param type the type info
    * @return unwrapped table (map)
    */
   @SuppressWarnings("deprecation")
   protected Object unwrapTable(TableValue tableValue, TypeInfo type)
   {
      if (type instanceof ParameterizedClassInfo)
      {
         ParameterizedClassInfo parameterizedType = (ParameterizedClassInfo)type;
         ClassInfo rawType = parameterizedType.getRawType();
         if (Map.class.isAssignableFrom(rawType.getType()))
         {
            TypeInfo keyType = parameterizedType.getActualTypeArguments()[0];
            TypeInfo valueType = parameterizedType.getActualTypeArguments()[1];
            return createMap(tableValue, keyType, valueType);
         }
      }
      throw new UnsupportedOperationException("Insufficient information to unwrap table: " + tableValue + ", " + type);
   }

   /**
    * Create a map
    *
    * @param tableValue the table value
    * @param keyType the key type
    * @param valueType the value type
    * @return the map
    */
   protected Map<?,?> createMap(TableValue tableValue, TypeInfo keyType, TypeInfo valueType)
   {
      if (tableValue == null)
         return null;

      Map<Object, Object> result = new HashMap<Object, Object>();
      Collection<CompositeValue> values = tableValue.values();
      for (CompositeValue entry : values)
      {
         Object key = unwrap(entry.get(DefaultMetaTypeFactory.MAP_KEY), keyType);
         Object val = unwrap(entry.get(DefaultMetaTypeFactory.MAP_VALUE), valueType);
         result.put(key, val);
      }
      return result;
   }

   /**
    * Create a PropertiesMetaValue
    * @param metaType
    * @param value
    * @param mapping
    * @return
    */
   protected MetaValue createPropertiesValue(PropertiesMetaType metaType,
         Object value, Map<Object, MetaValue> mapping)
   {
      if (value == null)
         return null;
      if((value instanceof Hashtable) == false)
         throw new IllegalArgumentException("PropertiesMetaType requires a java.lang.Properties value, value.class: "+value.getClass());

      Hashtable pvalue = (Hashtable) value;
      PropertiesMetaValue pmv = new PropertiesMetaValue();
      for(Object key : pvalue.keySet())
      {
         if ((key instanceof String) == false)
            throw new IllegalArgumentException("key("+key+") is not a String");
         String skey = (String) key;
         Object ovalue = pvalue.get(skey);
         if ((key instanceof String) == false)
            throw new IllegalArgumentException("value for key("+key+") is not a String, value.class: "+ovalue.getClass());
         String svalue = (String) ovalue;
         pmv.setProperty(skey, svalue);
      }
      return pmv;
   }

   /**
    * Create new instance.
    *
    * @param beanInfo the bean info
    * @return new instance
    * @throws Throwable for any error
    */
   @SuppressWarnings("deprecation")
   protected Object createNewInstance(BeanInfo beanInfo) throws Throwable
   {
      ClassInfo classInfo = beanInfo.getClassInfo();
      if (classInfo.isInterface())
      {
         InstanceFactory<?> instanceFactory = instanceFactoryMap.get(classInfo.getType());
         if (instanceFactory == null)
            throw new IllegalArgumentException("Cannot instantiate interface BeanInfo, missing InstanceFactory: " + classInfo);

         return instanceFactory.instantiate(beanInfo);
      }
      return beanInfo.newInstance();
   }

   /**
    * Get new array instance.
    *
    * @param typeInfo the type info
    * @param size the size
    * @return new array instance
    */
   protected Object newArrayInstance(TypeInfo typeInfo, int size)
   {
      if (typeInfo == null)
         throw new IllegalArgumentException("Null type info.");

      try
      {
         return typeInfo.newArrayInstance(size);
      }
      catch (Throwable t)
      {
         throw new UndeclaredThrowableException(t);
      }
   }

   /**
    * Get the class info from meta type.
    *
    * @param metaType the meta type
    * @param value the value which can provide classloader
    * @return type info
    */
   protected TypeInfo getTypeInfo(MetaType metaType, Object value)
   {
      if (metaType == null)
         throw new IllegalArgumentException("Null meta type, cannot determine class name.");
      if (value == null)
         throw new IllegalArgumentException("Null value, cannot determine classloader.");

      // get the classloader from the array we plan to fill
      ClassLoader cl = value.getClass().getClassLoader();
      return getTypeInfo(metaType, cl);
   }

   /**
    * Get the class info from meta type.
    *
    * @param metaType the meta type
    * @param cl the classloader
    * @return class info
    */
   protected TypeInfo getTypeInfo(MetaType metaType, ClassLoader cl)
   {
      if (cl == null)
         cl = Thread.currentThread().getContextClassLoader();

      try
      {
         TypeInfoFactory tif = configuration.getTypeInfoFactory();
         if (metaType.isArray())
         {
            ArrayMetaType arrayMetaType = (ArrayMetaType)metaType;
            MetaType elementMetaType = arrayMetaType.getElementType();
            String elementTypeName = elementMetaType.getTypeName();
            if (arrayMetaType.isPrimitiveArray())
               elementTypeName = ArrayMetaType.getPrimitiveName(elementTypeName);
            TypeInfo elementTypeInfo = tif.getTypeInfo(elementTypeName, cl);
            int dimension = arrayMetaType.getDimension() - 1; // minus 1, since we already use first in next line
            TypeInfo typeInfo = elementTypeInfo.getArrayType();
            while(dimension > 0)
            {
               typeInfo = typeInfo.getArrayType();
               dimension--;
            }
            return typeInfo;
         }
         return tif.getTypeInfo(metaType.getTypeName(), cl);
      }
      catch (ClassNotFoundException e)
      {
         throw new UndeclaredThrowableException(e);
      }
   }

   /**
    * Create a meta value from the object
    * 
    * @param value the value
    * @param type the type
    * @param metaType the metaType
    * @return the meta value
    */
   @SuppressWarnings("unchecked")
   protected MetaValue internalCreate(Object value, TypeInfo type, MetaType metaType)
   {
      if (value == null)
         return null;

      if (type == null)
         type = configuration.getTypeInfo(value.getClass());

      value = convertValue(value, type);

      boolean start = false;
      if (metaType == null)
      {
         start = true;
         metaType = metaTypeFactory.resolve(type);
      }
      
      // For more complicated values we need to keep a mapping of objects to meta values
      // this avoids duplicate meta value construction and recursion 
      Map<Object, MetaValue> mapping;
      if (start)
      {
         // This is the start of the mapping
         mapping = new HashMap<Object, MetaValue>();
         mappingStack.get().push(mapping);
      }
      else
      {
         // Check the existing mapping
         mapping = mappingStack.get().peek();
         MetaValue result = mapping.get(value);
         // Seen this before
         if (result != null)
            return result;
      }

      try
      {
         MetaValue result = isBuilder(metaType, type, value, mapping);

         if (result == null)
         {
            MetaMapper<Object> mapper = (MetaMapper) MetaMapper.getMetaMapper(type);
            if (mapper != null)
               result = mapper.createMetaValue(metaType, value);
         }

         if (result == null)
         {
            if (metaType.isSimple())
               result = createSimpleValue((SimpleMetaType) metaType, (Serializable) value);
            else if (metaType.isEnum())
               result = createEnumValue((EnumMetaType) metaType, (Enum<?>) value);
            else if (metaType.isArray())
               result = createArrayValue((ArrayMetaType) metaType, value, mapping);
            else if (metaType.isComposite())
               result = createCompositeValue((CompositeMetaType) metaType, value, mapping);
            else if (metaType.isTable())
               result = createTableValue((TableMetaType) metaType, (Map<?,?>) value, mapping);
            else if (metaType.isGeneric())
               result = createGenericValue((GenericMetaType) metaType, value, mapping);
            else if (metaType.isCollection())
               result = createCollectionValue((CollectionMetaType) metaType, value, mapping);
            else if (metaType.isProperties())
               result = createPropertiesValue((PropertiesMetaType) metaType, value, mapping);
            else
               throw new IllegalStateException("Unknown metaType: " + metaType);
         }
         return result;
      }
      finally
      {
         // Remove the mapping from the stack
         if (start)
            mappingStack.get().pop();
      }
   }

   /**
    * Convert the value.
    *
    * @param value the value
    * @param typeInfo type info
    * @return converted value if type info not null
    * @throws UndeclaredThrowableException for any error
    */
   protected Object convertValue(Object value, TypeInfo typeInfo)
   {
      try
      {
         return typeInfo != null ? typeInfo.convertValue(value) : value;
      }
      catch (Throwable t)
      {
         throw new UndeclaredThrowableException(t);
      }
   }

   /**
    * Check for a builder
    * 
    * @param metaType the meta type
    * @param type the type
    * @param value the value
    * @param mapping the mappings
    * @return the meta value
    */
   @SuppressWarnings({"unchecked", "deprecation"})
   protected MetaValue isBuilder(MetaType metaType, TypeInfo type, Object value, Map<Object, MetaValue> mapping)
   {
      MetaValueBuilder builder = null;
      synchronized (builders)
      {
         WeakReference<MetaValueBuilder<?>> weak = builders.get(type.getType());
         if (weak != null)
            builder = weak.get();
      }
      if (builder == null)
         return null;
      MetaValue result = builder.buildMetaValue(metaType, value);
      if (result != null)
         mapping.put(value, result);
      
      return result;
   }
}
