/*
* 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.managed.plugins.factory;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;

import org.jboss.beans.info.spi.BeanInfo;
import org.jboss.beans.info.spi.PropertyInfo;
import org.jboss.config.spi.Configuration;
import org.jboss.logging.Logger;
import org.jboss.managed.api.Fields;
import org.jboss.managed.api.ManagedObject;
import org.jboss.managed.api.ManagedOperation;
import org.jboss.managed.api.ManagedParameter;
import org.jboss.managed.api.ManagedProperty;
import org.jboss.managed.api.MutableManagedObject;
import org.jboss.managed.api.ManagedOperation.Impact;
import org.jboss.managed.api.annotation.ActivationPolicy;
import org.jboss.managed.api.annotation.AnnotationDefaults;
import org.jboss.managed.api.annotation.ConstraintsPopulatorFactory;
import org.jboss.managed.api.annotation.DefaultValueBuilderFactory;
import org.jboss.managed.api.annotation.FieldsFactory;
import org.jboss.managed.api.annotation.ManagementComponent;
import org.jboss.managed.api.annotation.ManagementConstants;
import org.jboss.managed.api.annotation.ManagementDeployment;
import org.jboss.managed.api.annotation.ManagementObject;
import org.jboss.managed.api.annotation.ManagementObjectID;
import org.jboss.managed.api.annotation.ManagementObjectRef;
import org.jboss.managed.api.annotation.ManagementOperation;
import org.jboss.managed.api.annotation.ManagementParameter;
import org.jboss.managed.api.annotation.ManagementProperties;
import org.jboss.managed.api.annotation.ManagementProperty;
import org.jboss.managed.api.annotation.ManagementPropertyFactory;
import org.jboss.managed.api.annotation.ManagementRuntimeRef;
import org.jboss.managed.api.annotation.Masked;
import org.jboss.managed.api.annotation.RunStateProperty;
import org.jboss.managed.api.annotation.ViewUse;
import org.jboss.managed.api.factory.ManagedObjectFactory;
import org.jboss.managed.api.factory.ManagedObjectDefinition;
import org.jboss.managed.plugins.DefaultFieldsImpl;
import org.jboss.managed.plugins.ManagedObjectImpl;
import org.jboss.managed.plugins.ManagedOperationImpl;
import org.jboss.managed.plugins.ManagedParameterImpl;
import org.jboss.managed.plugins.WritethroughManagedPropertyImpl;
import org.jboss.managed.spi.factory.InstanceClassFactory;
import org.jboss.managed.spi.factory.ManagedObjectBuilder;
import org.jboss.managed.spi.factory.ManagedObjectPopulator;
import org.jboss.managed.spi.factory.ManagedParameterConstraintsPopulator;
import org.jboss.managed.spi.factory.ManagedParameterConstraintsPopulatorFactory;
import org.jboss.managed.spi.factory.ManagedPropertyConstraintsPopulator;
import org.jboss.managed.spi.factory.ManagedPropertyConstraintsPopulatorFactory;
import org.jboss.metadata.spi.MetaData;
import org.jboss.metadata.spi.signature.FieldSignature;
import org.jboss.metadata.spi.signature.MethodSignature;
import org.jboss.metadata.spi.signature.Signature;
import org.jboss.metatype.api.annotations.MetaMapping;
import org.jboss.metatype.api.annotations.MetaMappingFactory;
import org.jboss.metatype.api.types.ArrayMetaType;
import org.jboss.metatype.api.types.CollectionMetaType;
import org.jboss.metatype.api.types.GenericMetaType;
import org.jboss.metatype.api.types.MetaType;
import org.jboss.metatype.api.types.MetaTypeFactory;
import org.jboss.metatype.api.values.MetaValue;
import org.jboss.metatype.api.values.MetaValueFactory;
import org.jboss.metatype.plugins.values.defaults.BigDecimalDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.BigIntegerDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.BooleanDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.BooleanPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.ByteDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.BytePrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.CharDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.CharPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.DoubleDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.DoublePrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.FloatDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.FloatPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.IntDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.IntPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.LongDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.LongPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.PropertiesDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.ShortDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.ShortPrimitiveDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.StringDefaultValueBuilder;
import org.jboss.metatype.plugins.values.defaults.StringObjectNameDefaultBuilder;
import org.jboss.metatype.spi.values.DefaultValueBuilder;
import org.jboss.metatype.spi.values.MetaMapper;
import org.jboss.metatype.spi.values.MetaMapperFactory;
import org.jboss.reflect.spi.AnnotatedInfo;
import org.jboss.reflect.spi.ClassInfo;
import org.jboss.reflect.spi.MethodInfo;
import org.jboss.reflect.spi.ParameterInfo;
import org.jboss.reflect.spi.TypeInfo;

/**
 * The base ManagedObjectFactory implementation.
 * 
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @author Scott.Stark@jboss.org
 * @version $Revision: 87524 $
 */
public class AbstractManagedObjectFactory extends ManagedObjectFactory
   implements ManagedObjectBuilder
{
   private static final Logger log = Logger.getLogger(AbstractManagedObjectFactory.class);

   /** The configuration */
   private static final Configuration configuration = PropertyConfigurationAccess.getConfiguration();

   /** The managed object meta type */
   public static final GenericMetaType MANAGED_OBJECT_META_TYPE = new GenericMetaType(ManagedObject.class.getName(), ManagedObject.class.getName());
   
   /** The meta type factory */
   private MetaTypeFactory metaTypeFactory = MetaTypeFactory.getInstance(); 

   /** The meta value factory */
   private MetaValueFactory metaValueFactory = MetaValueFactory.getInstance();
   /** A default InstanceClassFactory used when there is no explicit ICF for a given class */
   private InstanceClassFactory<?> defaultInstanceFactory;
   /** A default ManagedObjectPopulator used when there is no explicit ManagedObjectBuilder for a given class */
   private ManagedObjectPopulator<?> defaultManagedObjectPopulator;

   /** The managed object builders */
   private Map<Class<?>, ManagedObjectBuilder> builders = new WeakHashMap<Class<?>, ManagedObjectBuilder>();

   /** The instance to class factories */
   private Map<Class<?>, InstanceClassFactory<?>> instanceFactories = new WeakHashMap<Class<?>, InstanceClassFactory<?>>();

   /** The default value builders */
   private Map<MetaType, DefaultValueBuilder> defaultBuilders = new HashMap<MetaType, DefaultValueBuilder>();

   /**
    * Create a ManagedProperty by looking to the factory for ctor(Fields)
    * @param factory - the ManagedProperty implementation class
    * @param fields - the fields to pass to the ctor
    * @return the managed property if successful, null otherwise
    */
   public static ManagedProperty createManagedProperty(Class<? extends ManagedProperty> factory, Fields fields)
   {
      ManagedProperty property = null;
      try
      {
         Class<?>[] sig = {Fields.class};
         Constructor<? extends ManagedProperty> ctor = factory.getConstructor(sig);
         Object[] args = {fields};
         property = ctor.newInstance(args);
      }
      catch(Exception e)
      {
         log.debug("Failed to create ManagedProperty", e);
      }
      return property;
   }

   /**
    * Create an AbstractManagedObjectFactory that uses an AbstractInstanceClassFactory
    * as the defaultInstanceFactory and AbstractManagedObjectPopulator as the
    * defaultManagedObjectPopulator. The MetaTypeFactory, MetaValueFactory are
    * obtained from the respective getInstance() factory methods.
    */
   public AbstractManagedObjectFactory()
   {
      // Create an AbstractInstanceClassFactory as the default ICF
      DefaultInstanceClassFactory icf = new DefaultInstanceClassFactory();
      icf.setMof(this);
      defaultInstanceFactory = icf;
      // Create an AbstractManagedObjectPopulator as the default
      defaultManagedObjectPopulator = new AbstractManagedObjectPopulator<Serializable>(configuration, icf, instanceFactories);
      initDefaultValueBuilders();
   }
   /**
    * Create an AbstractManagedObjectFactory the given factories, supporting
    * information.
    * 
    * @param metaTypeFactory
    * @param metaValueFactory
    * @param defaultInstanceFactory
    * @param defaultManagedObjectPopulator
    * @param builders
    * @param instanceFactories
    */
   public AbstractManagedObjectFactory(MetaTypeFactory metaTypeFactory,
         MetaValueFactory metaValueFactory,
         InstanceClassFactory<?> defaultInstanceFactory,
         ManagedObjectPopulator<?> defaultManagedObjectPopulator,
         Map<Class<?>, ManagedObjectBuilder> builders,
         Map<Class<?>, InstanceClassFactory<?>> instanceFactories)
   {
      this.metaTypeFactory = metaTypeFactory;
      this.metaValueFactory = metaValueFactory;
      this.defaultInstanceFactory = defaultInstanceFactory;
      this.defaultManagedObjectPopulator = defaultManagedObjectPopulator;
      this.builders = builders;
      this.instanceFactories = instanceFactories;
      initDefaultValueBuilders();
   }

   /**
    * Add managed object definition.
    *
    * @param definition the MO definition
    */
   public void addManagedObjectDefinition(ManagedObjectDefinition definition)
   {
      if (definition == null || definition.isValid() == false)
         throw new IllegalArgumentException("Invalid MO definition: " + definition);

      setBuilder(definition.getType(), definition.getBuilder());
   }

   /**
    * Remove managed object definition.
    *
    * @param definition the MO definition
    */
   public void removeManagedObjectDefinition(ManagedObjectDefinition definition)
   {
      if (definition == null)
         return;

      setBuilder(definition.getType(), null);
   }

   public void addDefaultValueBuilder(DefaultValueBuilder builder)
   {
      if(builder == null)
         throw new IllegalArgumentException("Null DefaultValueBuilder");
      setDefaultsBuilder(builder.getType(), builder);
   }
   public void setDefaultsBuilder(MetaType type, DefaultValueBuilder builder)
   {
      synchronized (defaultBuilders)
      {
         if (builder == null)
            defaultBuilders.remove(type);
         else
            defaultBuilders.put(type, builder);
      }
   }
   public void removeDefaltValueBuilder(DefaultValueBuilder builder)
   {
      if (builder == null)
         return;
      setDefaultsBuilder(builder.getType(), null);
   }

   /**
    * Get the configuration
    * 
    * @return the configuration
    */
   public Configuration getConfiguration()
   {
      return configuration;
   }

   
   public MetaTypeFactory getMetaTypeFactory()
   {
      return metaTypeFactory;
   }

   public void setMetaTypeFactory(MetaTypeFactory metaTypeFactory)
   {
      this.metaTypeFactory = metaTypeFactory;
   }

   public MetaValueFactory getMetaValueFactory()
   {
      return metaValueFactory;
   }

   public void setMetaValueFactory(MetaValueFactory metaValueFactory)
   {
      this.metaValueFactory = metaValueFactory;
   }

   public Map<Class<?>, ManagedObjectBuilder> getBuilders()
   {
      return builders;
   }

   public void setBuilders(Map<Class<?>, ManagedObjectBuilder> builders)
   {
      this.builders = builders;
   }

   public Map<Class<?>, InstanceClassFactory<?>> getInstanceFactories()
   {
      return instanceFactories;
   }

   public void setInstanceFactories(
         Map<Class<?>, InstanceClassFactory<?>> instanceFactories)
   {
      this.instanceFactories = instanceFactories;
   }

   /**
    * Get the default InstanceClassFactory
    * @return the current default InstanceClassFactory
    */
   public InstanceClassFactory<?> getDefaultInstanceFactory()
   {
      return defaultInstanceFactory;
   }
   /**
    * Set the default InstanceClassFactory. This is used when there is not
    * match an exact match by the {@linkplain #getInstanceClassFactory(Class)}
    * factory method.
    * 
    * @param defaultInstanceFactory the default InstanceClassFactory to fall
    * back to. It may be null if no default should be used.
    */
   public void setDefaultInstanceFactory(
         InstanceClassFactory<? extends Serializable> defaultInstanceFactory)
   {
      this.defaultInstanceFactory = defaultInstanceFactory;
   }

   
   public ManagedObjectPopulator<?> getDefaultManagedObjectPopulator()
   {
      return defaultManagedObjectPopulator;
   }

   /**
    * A default implementation of ManagedObjectPopulator that is used when
    * there is no ManagedObjectBuilder registered for a given type.
    * @see #getBuilder(Class)
    * @param defaultManagedObjectPopulator
    */
   public void setDefaultManagedObjectPopulator(
         ManagedObjectPopulator<? extends Serializable> defaultManagedObjectPopulator)
   {
      this.defaultManagedObjectPopulator = defaultManagedObjectPopulator;
   }

   @Override
   public <T> ManagedObject createManagedObject(Class<T> clazz, MetaData metaData)
   {
      if (clazz == null)
         throw new IllegalArgumentException("Null class");

      ManagedObject result = createSkeletonManagedObject(clazz, metaData);
      ManagedObjectPopulator<T> populator = getPopulator(clazz);
      populator.createObject(result, clazz, metaData);
      
      return result;
   }

   public ManagedObject initManagedObject(Object instance, Class<?> instanceType,
         MetaData metaData, String name, String nameType)
   {
      if (instance == null)
         throw new IllegalArgumentException("instance cannot be null");

      Class<?> clazz = instance.getClass();
      InstanceClassFactory icf = defaultInstanceFactory;
      if(instanceType != null && instanceType != clazz)
         icf = getInstanceClassFactory(instanceType, metaData);
      if(icf == defaultInstanceFactory)
         icf = getInstanceClassFactory(clazz, metaData);

      Class<Object> moClass;
      try
      {
         moClass = icf.getManagedObjectClass(instance);
      }
      catch(ClassNotFoundException e)
      {
         log.debug("Failed to load class for ManagedObject", e);
         return null;
      }
      if(moClass == null)
      {
         log.debug("ICF returned null class: "+instance);
         return null;
      }

      ManagedObject result = createSkeletonManagedObject(moClass, metaData);
      if (result == null)
      {
         log.debug("Null ManagedObject created for: "+moClass);
         return null;
      }
      log.debug("Created skeleton ManagedObject: "+result);

      if(result instanceof MutableManagedObject)
      {
         MutableManagedObject mmo = (MutableManagedObject) result;
         ManagedObjectPopulator<Object> populator = getPopulator(moClass);
         populator.populateManagedObject(mmo, instance, metaData);
      }

      return result;
   }

   @Override
   public void setBuilder(Class<?> clazz, ManagedObjectBuilder builder)
   {
      synchronized (builders)
      {
         if (builder == null)
            builders.remove(clazz);
         else
            builders.put(clazz, builder);
      }
   }

   @Override
   public <T> void setInstanceClassFactory(Class<T> clazz, InstanceClassFactory<T> factory)
   {
      synchronized (instanceFactories)
      {
         if (factory == null)
         {
            instanceFactories.remove(clazz);
            log.debug("Removed ICF for: "+clazz);
         }
         else
         {
            instanceFactories.put(clazz, factory);
            log.debug("Set ICF for: "+clazz+", to: "+factory);
         }
      }      
   }

   /**
    * Create a skeleton managed object
    * 
    * @param <T> the type
    * @param clazz the clazz
    * @return the skeleton managed object, null if clazz is not
    *    marked as a ManagementObject.
    * {@linkplain ManagementObject}
    */
   protected <T> ManagedObject createSkeletonManagedObject(Class<T> clazz, MetaData metaData)
   {
      if (clazz == null)
         throw new IllegalArgumentException("Null class");

      ManagedObjectBuilder builder = getBuilder(clazz);
      return builder.buildManagedObject(clazz, metaData);
   }
   
   /**
    * The ManagedObjectBuilder.buildManagedObject implementation. This is based
    * on the org.jboss.managed.api.annotation.* package annotations.
    * @param clazz the attachment class
    * @param metaData - the optional metadata repository accessor used to query
    * for management annotation overrides/additions to the clazz
    * @return the ManagementObject if clazz is properly annotated, null if
    *    it does not have a ManagementObject annotation on the class or metaData.
    *    The BeanInfo used by this method is stored as a transient attachment
    *    under the BeanInfo.class.getName() ket.
    */
   @SuppressWarnings("unchecked")
   public ManagedObject buildManagedObject(Class<?> clazz, MetaData metaData)
   {
      boolean trace = log.isTraceEnabled();
      BeanInfo beanInfo = configuration.getBeanInfo(clazz);
      ClassInfo classInfo = beanInfo.getClassInfo();

      ManagementObject managementObject = getAnnotation(ManagementObject.class, classInfo, metaData);
      if( managementObject == null )
      {
         if (trace)
            log.trace("No ManagementObject annotation, skipping ManagedObject for class: "+clazz);
         // Skip the ManagedObject creation
         return null;
      }

      // If a targetInterface exists, rebuild the BeanInfo from that
      Class<?> targetInterface = managementObject.targetInterface();
      if(targetInterface != Object.class)
      {
         beanInfo = getBeanInfo(targetInterface);
         classInfo = beanInfo.getClassInfo();
      }
      HashMap<String, Annotation> moAnnotations = new HashMap<String, Annotation>();
      moAnnotations.put(ManagementObject.class.getName(), managementObject);
      ManagementDeployment mnagementDeployment = getAnnotation(ManagementDeployment.class, classInfo, metaData);
      if(mnagementDeployment != null)
         moAnnotations.put(ManagementDeployment.class.getName(), mnagementDeployment);
      ManagementObjectID moID = getAnnotation(ManagementObjectID.class, classInfo, metaData);
      if (moID != null)
         moAnnotations.put(ManagementObjectID.class.getName(), moID);

      // Process the ManagementObject fields
      boolean isRuntime = managementObject.isRuntime();
      String name = classInfo.getName();
      String nameType = null;
      String attachmentName = classInfo.getName();
      Class<? extends Fields> moFieldsFactory = null;
      ConstraintsPopulatorFactory moCPF = null;
      Class<? extends ManagedPropertyConstraintsPopulatorFactory> moConstraintsFactory = null;
      Class<? extends ManagedProperty> moPropertyFactory = null;
      if (managementObject != null)
      {
         name = managementObject.name();
         if (name.length() == 0 || name.equals(ManagementConstants.GENERATED))
            name = classInfo.getName();
         nameType = managementObject.type();
         if (nameType.length() == 0)
            nameType = null;
         attachmentName = managementObject.attachmentName();
         if (attachmentName.length() == 0)
            attachmentName = classInfo.getName();
         // Check for a component specification
         ManagementComponent mc = null;
         if (metaData != null )
            mc = metaData.getAnnotation(ManagementComponent.class);
         if (mc == null)
            mc = managementObject.componentType();
         // Work around JBMDR-51 by checking type/subtype
         // if (mc.equals(AnnotationDefaults.COMP_TYPE) == false)
         if (mc.type().length() > 0 || mc.subtype().length() > 0)
         {
            log.debug("ManagedObject("+name+") is ManagedComponent type: "+mc);
            moAnnotations.put(ManagementComponent.class.getName(), mc);
         }
         // ManagementObject level default factory classes
         FieldsFactory ff = getAnnotation(FieldsFactory.class, classInfo, metaData);
         if(ff != null)
            moFieldsFactory = ff.value();
         moCPF = getAnnotation(ConstraintsPopulatorFactory.class, classInfo, metaData);
         if(moCPF != null)
            moConstraintsFactory = moCPF.value();
         ManagementPropertyFactory mpf = getAnnotation(ManagementPropertyFactory.class, classInfo, metaData);
         if(mpf != null)
            moPropertyFactory = mpf.value();
      }

      if (trace)
      {
         log.trace("Building MangedObject(name="+name+",nameType="+nameType
               +",attachmentName="+attachmentName+",isRuntime="+isRuntime+")");
      }

      ManagementProperties propertyType = ManagementProperties.ALL;
      Set<String> classProperties = null;
      if (managementObject != null)
      {
         propertyType = managementObject.properties();
         if(propertyType == ManagementProperties.CLASS || propertyType == ManagementProperties.CLASS_AND_EXPLICIT)
         {
            classProperties = new HashSet<String>();
            for(ManagementProperty mp : managementObject.classProperties())
            {
               if(mp.name().length() > 0)
                  classProperties.add(mp.name());
               if(mp.mappedName().length() > 0)
                  classProperties.add(mp.mappedName());
            }
         }
      }

      // Build the ManagedProperties
      Set<ManagedProperty> properties = new HashSet<ManagedProperty>();

      Set<PropertyInfo> propertyInfos = beanInfo.getProperties();
      if (propertyInfos != null && propertyInfos.isEmpty() == false)
      {
         for (PropertyInfo propertyInfo : propertyInfos)
         {
            // Ignore the "class" property
            if ("class".equals(propertyInfo.getName()))
               continue;

            ManagementProperty managementProperty = getAnnotation(ManagementProperty.class, propertyInfo, metaData);
            ManagementObjectID id = getAnnotation(ManagementObjectID.class, propertyInfo, metaData);
            ManagementObjectRef ref = getAnnotation(ManagementObjectRef.class, propertyInfo, metaData);
            ManagementRuntimeRef runtimeRef = getAnnotation(ManagementRuntimeRef.class, propertyInfo, metaData);
            RunStateProperty rsp = getAnnotation(RunStateProperty.class, propertyInfo, metaData);
            Masked masked = getAnnotation(Masked.class, propertyInfo, metaData);
            DefaultValueBuilderFactory defaultsFactory = getAnnotation(DefaultValueBuilderFactory.class, propertyInfo, metaData);
            HashMap<String, Annotation> propAnnotations = new HashMap<String, Annotation>();
            if (managementProperty != null)
               propAnnotations.put(ManagementProperty.class.getName(), managementProperty);
            if (id != null)
            {
               propAnnotations.put(ManagementObjectID.class.getName(), id);
               // This overrides the MO nameType
               nameType = id.type();
            }
            if (ref != null)
               propAnnotations.put(ManagementObjectRef.class.getName(), ref);
            if (runtimeRef != null)
               propAnnotations.put(ManagementRuntimeRef.class.getName(), runtimeRef);
            if (rsp != null)
               propAnnotations.put(RunStateProperty.class.getName(), rsp);
            if (masked != null)
               propAnnotations.put(Masked.class.getName(), masked);

            // Check whether this property should be included
            boolean includeProperty = false;
            switch(propertyType)
            {
               // Only if the property as a ManagementProperty
               case EXPLICIT:
                  includeProperty = managementProperty != null &&
                  (managementProperty.ignored() == false);
               break;
               // Only if the property is listed in the classProperties
               case CLASS:
                  includeProperty = classProperties.contains(propertyInfo.getName());
               break;
               // Only if the property is listed in the classProperties
               case CLASS_AND_EXPLICIT:
                  includeProperty = classProperties.contains(propertyInfo.getName())
                     || (managementProperty != null && managementProperty.ignored() == false);
               break;
               // Any property that is not ignored
               case ALL:
                  includeProperty = managementProperty == null
                     || managementProperty.ignored() == false;
               break;
            }

            if (includeProperty)
            {
               Fields fields = null;
               Class<? extends Fields> factory = moFieldsFactory;
               FieldsFactory ff = getAnnotation(FieldsFactory.class, propertyInfo, metaData);
               if(ff != null)
                  factory = ff.value();
               if (factory != null)
               {
                  try
                  {
                     fields = factory.newInstance();
                  }
                  catch (Exception e)
                  {
                     log.debug("Failed to created Fields", e);
                  }
               }
               if (fields == null)
                  fields = new DefaultFieldsImpl();

               if( propertyInfo instanceof Serializable )
               {
                  Serializable info = Serializable.class.cast(propertyInfo);
                  fields.setField(Fields.PROPERTY_INFO, info);
               }

               String propertyName = propertyInfo.getName();
               if (managementProperty != null)
                  propertyName = managementProperty.name();
               if( propertyName.length() == 0 )
                  propertyName = propertyInfo.getName();
               fields.setField(Fields.NAME, propertyName);

               // This should probably always the the propertyInfo name?
               String mappedName = propertyInfo.getName();
               if (managementProperty != null)
                  mappedName = managementProperty.mappedName();
               if( mappedName.length() == 0 )
                  mappedName = propertyInfo.getName();
               fields.setField(Fields.MAPPED_NAME, mappedName);

               String description = ManagementConstants.GENERATED;
               if (managementProperty != null)
                  description = managementProperty.description();
               if (description.equals(ManagementConstants.GENERATED))
                  description = propertyName;
               fields.setField(Fields.DESCRIPTION, description);

               if (trace)
               {
                  log.trace("Building MangedProperty(name="+propertyName
                        +",mappedName="+mappedName
                        +") ,annotations="+propAnnotations);
               }

               boolean mandatory = false;
               if (managementProperty != null)
                  mandatory = managementProperty.mandatory();
               if (mandatory)
                  fields.setField(Fields.MANDATORY, Boolean.TRUE);
               
               boolean readOnly = propertyInfo.isWritable() == false;
               if (readOnly == false && managementProperty != null)
                  readOnly = managementProperty.readOnly();
               if (readOnly)
                  fields.setField(Fields.READ_ONLY, Boolean.TRUE);

               boolean managed = false;
               if (managementProperty != null)
                  managed = managementProperty.managed();
               // View Use
               if (managementProperty != null)
               {
                  ViewUse[] use = managementProperty.use();
                  fields.setField(Fields.VIEW_USE, use);
               }
               // ActivationPolicy
               ActivationPolicy apolicy = ActivationPolicy.IMMEDIATE;
               if (managementProperty != null)
               {
                  apolicy = managementProperty.activationPolicy();
               }
               fields.setField(Fields.ACTIVATION_POLICY, apolicy);
               // The managed property type
               MetaMapper[] mapperReturn = {null};
               MetaType metaType = this.getMetaType(propertyInfo, propertyInfo.getType(), metaData, false, mapperReturn);

               // Determine meta type based on property type
               if(metaType == null)
               {
                  if (managed)
                  {
                     TypeInfo typeInfo = propertyInfo.getType();
                     if(typeInfo.isArray())
                        metaType = new ArrayMetaType(1, MANAGED_OBJECT_META_TYPE);
                     else if (typeInfo.isCollection())
                        metaType = new CollectionMetaType(typeInfo.getName(), MANAGED_OBJECT_META_TYPE);
                     else
                        metaType = MANAGED_OBJECT_META_TYPE;
                  }
                  else
                  {
                     metaType = metaTypeFactory.resolve(propertyInfo.getType());
                  }
               }
               fields.setField(Fields.META_TYPE, metaType);

               // Default value
               if(managementProperty != null)
               {
                  String defaultValue = managementProperty.defaultValue();
                  if(defaultValue.length() > 0)
                  {
                     try
                     {
                       // Check for a DefaultValueBuilderFactory
                        DefaultValueBuilder builder = null;
                        if(defaultsFactory != null)
                        {
                              Class<? extends DefaultValueBuilder> factoryClass = defaultsFactory.value();
                              builder = factoryClass.newInstance();
                        }
                        // Lookup the builder by metaType
                        if(builder == null)
                        {
                           builder = defaultBuilders.get(metaType);
                        }
                        if(builder != null)
                        {
                           MetaValue defaultMV = builder.buildMetaValue(defaultValue);
                           if(defaultMV != null)
                              fields.setField(Fields.DEFAULT_VALUE, defaultMV);
                        }
                        else
                        {
                           log.warn("Failed to find DefaultValueBuilder for type: "+metaType);
                        }
                     }
                     catch(Exception e)
                     {
                        log.warn("Failed to create default value for: "+propertyInfo, e);
                     }
                  }
               }

               // Property annotations
               if (propAnnotations.isEmpty() == false)
                  fields.setField(Fields.ANNOTATIONS, propAnnotations);

               // Delegate others (legal values, min/max etc.) to the constraints factory
               try
               {
                  Class<? extends ManagedPropertyConstraintsPopulatorFactory> factoryClass = moConstraintsFactory;
                  ConstraintsPopulatorFactory cpf = getAnnotation(ConstraintsPopulatorFactory.class, propertyInfo, metaData);
                  if(cpf != null)
                     factoryClass = cpf.value();
                  else
                     cpf = moCPF;
                  if(factoryClass != null)
                  {
                     ManagedPropertyConstraintsPopulatorFactory mpcpf = factoryClass.newInstance();
                     ManagedPropertyConstraintsPopulator populator = mpcpf.newInstance(cpf.min(), cpf.max(), cpf.legalValues(), cpf.args());
                     if (populator != null)
                        populator.populateManagedProperty(clazz, propertyInfo, fields);
                  }
               }
               catch(Exception e)
               {
                  log.debug("Failed to populate constraints for: "+propertyInfo, e);
               }

               ManagedProperty property = null;
               Class<? extends ManagedProperty> mpClass = moPropertyFactory;
               ManagementPropertyFactory mpf = getAnnotation(ManagementPropertyFactory.class, propertyInfo, metaData);
               if (mpf != null)
                  mpClass = mpf.value();
               if (mpClass != null)
                  property = getManagedProperty(mpClass, fields);
               // we should have write-through by default
               // use factory to change this default behavior
               if (property == null)
                  property = createDefaultManagedProperty(fields);
               // Pass the MetaMapper as an attachment
               if (mapperReturn[0] != null)
                  property.setTransientAttachment(MetaMapper.class.getName(), mapperReturn[0]);
               properties.add(property);
            }
            else if (trace)
               log.trace("Ignoring property: " + propertyInfo);
         }
      }

      /* TODO: Operations. In general the bean metadata does not contain
         operation information.
      */
      Set<ManagedOperation> operations = new HashSet<ManagedOperation>();
      
      Set<MethodInfo> methodInfos = beanInfo.getMethods();
      if (methodInfos != null && methodInfos.isEmpty() == false)
      {
         for (MethodInfo methodInfo : methodInfos)
         {
            ManagementOperation managementOp = getAnnotation(ManagementOperation.class, methodInfo, metaData);
            if (managementOp == null)
               continue;

            ManagedOperation op = getManagedOperation(methodInfo, managementOp, metaData);
            operations.add(op);
         }
      }

      ManagedObjectImpl result = new ManagedObjectImpl(name, properties);
      result.setAnnotations(moAnnotations);
      // Set the component name to name if this is a runtime MO with a name specified
      if (isRuntime && name.equals(classInfo.getName()) == false)
         result.setComponentName(name);
      if (nameType != null)
         result.setNameType(nameType);
      if (attachmentName != null)
         result.setAttachmentName(attachmentName);
      if (operations.size() > 0 )
         result.setOperations(operations);
      for (ManagedProperty property : properties)
         property.setManagedObject(result);
      result.setTransientAttachment(BeanInfo.class.getName(), beanInfo);
      return result;
   }

   /**
    * Create default MangedProperty instance.
    * Override this method for different default.
    *
    * @param fields the fields
    * @return new ManagedProperty instance
    */
   protected ManagedProperty createDefaultManagedProperty(Fields fields)
   {
      return new WritethroughManagedPropertyImpl(fields, metaValueFactory, this);
   }

   /**
    * Get the property name.
    *
    * @param property managed property
    * @return property name
    */
   protected String getPropertyName(ManagedProperty property)
   {
      // First look to the mapped name
      String name = property.getMappedName();
      if (name == null)
         property.getName();
      return name;
   }

   /**
    * 
    * @param methodInfo
    * @param opAnnotation
    * @return the managed operation
    */
   protected ManagedOperation getManagedOperation(MethodInfo methodInfo,
         ManagementOperation opAnnotation, MetaData metaData)
   {
      String name = methodInfo.getName();
      String description = opAnnotation.description();
      Impact impact = opAnnotation.impact();
      ManagementParameter[] params = opAnnotation.params();
      ParameterInfo[] paramInfo = methodInfo.getParameters();
      ArrayList<ManagedParameter> mparams = new ArrayList<ManagedParameter>();
      Class<? extends ManagedParameterConstraintsPopulatorFactory> opConstraintsFactor = opAnnotation.constraintsFactory();

      // The op return type
      MetaMapper[] returnTypeMapper = {null};
      MetaType returnType = getMetaType(methodInfo, methodInfo.getReturnType(), metaData, true, returnTypeMapper);

      // Process the op parameters
      if( paramInfo != null )
      {
         for(int i = 0; i < paramInfo.length; i ++)
         {
            ParameterInfo pinfo = paramInfo[i];
            String pname = pinfo.getName();
            String pdescription = null;
            ManagementParameter mpa = null;
            // Look to ManagementParameter for info
            if (i < params.length)
            {
               mpa = params[i];
               if (mpa.name().equals(AnnotationDefaults.EMPTY_STRING) == false)
                  pname = mpa.name();
               if (mpa.description().equals(AnnotationDefaults.EMPTY_STRING) == false)
                  pdescription = mpa.description();
            }
            // Generate a name if there is none
            if (pname == null)
               pname = "arg#" + i;
            Fields fields =  new DefaultFieldsImpl(pname);
            if (pdescription != null)
               fields.setField(Fields.DESCRIPTION, pdescription);
            MetaMapper[] paramMapper = {null};
            MetaType metaType = getMetaType(pinfo, pinfo.getParameterType(), metaData, true, paramMapper);
            fields.setField(Fields.META_TYPE, metaType);
            // Delegate others (legal values, min/max etc.) to the constraints factory
            try
            {
               Class<? extends ManagedParameterConstraintsPopulatorFactory> factoryClass = opConstraintsFactor;
               if (factoryClass == ManagementParameter.NULL_CONSTRAINTS.class)
               {
                  if (mpa != null)
                     factoryClass = mpa.constraintsFactory();
               }
               ManagedParameterConstraintsPopulatorFactory factory = factoryClass.newInstance();
               ManagedParameterConstraintsPopulator populator = factory.newInstance();
               if (populator != null)
                  populator.populateManagedParameter(name, pinfo, fields);
            }
            catch(Exception e)
            {
               log.debug("Failed to populate constraints for: "+pinfo, e);
            }

            ManagedParameterImpl mp = new ManagedParameterImpl(fields);
            if(paramMapper[0] != null)
               mp.setTransientAttachment(MetaMapper.class.getName(), paramMapper[0]);
            mparams.add(mp);
         }
      }
      ManagedParameter[] parameters = new ManagedParameter[mparams.size()];
      mparams.toArray(parameters);

      ManagedOperationImpl op = new ManagedOperationImpl(name, description, impact, parameters, returnType);
      if(returnTypeMapper[0] != null)
         op.setTransientAttachment(MetaMapper.class.getName(), returnTypeMapper[0]);
      return op;
   }

   /**
    * Get the MetaType for info by looking for MetaMapping/MetaMappingFactory
    * annotations in addition to the info type.
    * 
    * @param methodInfo
    * @param metaData
    * @return the MetaType for info's type
    */
   protected MetaType getMetaType(AnnotatedInfo info, TypeInfo infoType, MetaData metaData,
         boolean useTypeFactory, MetaMapper[] mapperReturn)
   {
      MetaType returnType = null;
      // First look for meta mappings
      MetaMapper<?> metaMapper = null;
      MetaMapping metaMapping = getAnnotation(MetaMapping.class, info, metaData);
      MetaMappingFactory metaMappingFactory = getAnnotation(MetaMappingFactory.class, info, metaData);
      if(metaMappingFactory != null)
      {
         Class<? extends MetaMapperFactory<?>> mmfClass = metaMappingFactory.value();
         try
         {
            MetaMapperFactory<?> mmf = mmfClass.newInstance();
            String[] args = metaMappingFactory.args();
            if(args.length > 0)
               metaMapper = mmf.newInstance(args);
            else
               metaMapper = mmf.newInstance();
         }
         catch(Exception e)
         {
            log.debug("Failed to create MetaMapperFactory: "+metaMappingFactory, e);
         }
      }
      if(metaMapping != null)
      {
         // Use the mapping for the type
         Class<? extends MetaMapper<?>> mapperClass = metaMapping.value();
         try
         {
            metaMapper = mapperClass.newInstance();
         }
         catch(Exception e)
         {
            log.debug("Failed to create MetaMapper: "+metaMapping, e);
         }
      }
      if(metaMapper != null)
      {
         returnType = metaMapper.getMetaType();
         // Return the MetaMapper
         if(mapperReturn != null && mapperReturn.length > 0)
            mapperReturn[0] = metaMapper;
      }

      if(returnType == null && useTypeFactory)
      {
         // Use the type factory to convert the info type
         returnType = metaTypeFactory.resolve(infoType);
      }
      return returnType;
   }

   /**
    * Get the builder for a class
    * 
    * @param clazz the class
    * @return the builder
    */
   protected ManagedObjectBuilder getBuilder(Class<?> clazz)
   {
      synchronized (builders)
      {
         ManagedObjectBuilder builder = builders.get(clazz);
         if (builder != null)
            return builder;
      }
      return this;
   }

   /**
    * Get the instance factory for a class
    * 
    * @param clazz the class
    * @return the InstanceClassFactory
    */
   @SuppressWarnings("unchecked")
   public <X> InstanceClassFactory<X> getInstanceClassFactory(Class<X> clazz,
         MetaData metaData)
   {
      InstanceClassFactory defaultFactory = defaultInstanceFactory;
      if(metaData != null)
      {
         InstanceClassFactory mdrFactory = metaData.getMetaData(InstanceClassFactory.class);
         if(mdrFactory != null)
            defaultFactory = mdrFactory;
      }
      InstanceClassFactory<X> factory = (InstanceClassFactory<X>)
         Utility.getInstanceClassFactory(clazz, instanceFactories,
               defaultFactory);
      return factory;
   }

   /**
    * Initialize the DefaultValueBuilder map with known implementations
    */
   protected void initDefaultValueBuilders()
   {
      addDefaultValueBuilder(new BigDecimalDefaultValueBuilder());
      addDefaultValueBuilder(new BigIntegerDefaultValueBuilder());
      addDefaultValueBuilder(new DoubleDefaultValueBuilder());
      addDefaultValueBuilder(new DoublePrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new FloatDefaultValueBuilder());
      addDefaultValueBuilder(new FloatPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new CharDefaultValueBuilder());
      addDefaultValueBuilder(new CharPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new IntDefaultValueBuilder());
      addDefaultValueBuilder(new IntPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new ShortDefaultValueBuilder());
      addDefaultValueBuilder(new ShortPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new LongDefaultValueBuilder());
      addDefaultValueBuilder(new LongPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new ByteDefaultValueBuilder());
      addDefaultValueBuilder(new BytePrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new BooleanDefaultValueBuilder());
      addDefaultValueBuilder(new BooleanPrimitiveDefaultValueBuilder());
      addDefaultValueBuilder(new PropertiesDefaultValueBuilder());
      addDefaultValueBuilder(new StringDefaultValueBuilder());
   }

   /**
    * Get the populator for a class
    * 
    * @param clazz the class
    * @return the populator
    */
   @SuppressWarnings("unchecked")
   protected <X> ManagedObjectPopulator<X> getPopulator(Class<X> clazz)
   {
      ManagedObjectBuilder builder = getBuilder(clazz);
      if (builder instanceof ManagedObjectPopulator)
         return (ManagedObjectPopulator) builder;
      ManagedObjectPopulator<X> mop = (ManagedObjectPopulator<X>) defaultManagedObjectPopulator;
      return mop;
   }

   protected Collection<?> getAsCollection(Object value)
   {
      if( value.getClass().isArray() )
         return Arrays.asList(value);
      else if (value instanceof Collection)
         return Collection.class.cast(value);
      return null;
   }

   /**
    * Look for ctor(Fields)
    * @param factory - the ManagedProperty implementation class
    * @param fields - the fields to pass to the ctor
    * @return the managed property
    */
   protected ManagedProperty getManagedProperty(Class<? extends ManagedProperty> factory, Fields fields)
   {
      return createManagedProperty(factory, fields);
   }

   protected <X extends Annotation> X getAnnotation(Class<X> annotationType,
      AnnotatedInfo info, MetaData metaData)
   {
      X annotation = null;
      if(metaData != null)
      {
         annotation = metaData.getAnnotation(annotationType);
         if(annotation == null && info instanceof MethodInfo)
         {
            MethodInfo mi = (MethodInfo) info;
            Signature mis = Signature.getSignature(mi);
            MetaData imetaData = metaData.getComponentMetaData(mis);
            if (imetaData != null)
               annotation = imetaData.getAnnotation(annotationType);
         }
         if (annotation == null && info instanceof PropertyInfo)
         {
            PropertyInfo pi = (PropertyInfo) info;
            if(pi.getGetter() != null)
            {
               Signature mis = new MethodSignature(pi.getGetter());
               MetaData imetaData = metaData.getComponentMetaData(mis);
               if (imetaData != null)
                  annotation = imetaData.getAnnotation(annotationType);
            }
            if(annotation == null && pi.getSetter() != null)
            {
               Signature mis = new MethodSignature(pi.getSetter());
               MetaData imetaData = metaData.getComponentMetaData(mis);
               if (imetaData != null)
                  annotation = imetaData.getAnnotation(annotationType);
            }
            if(annotation == null && pi.getFieldInfo() != null)
            {
               Signature fis = new FieldSignature(pi.getFieldInfo());
               MetaData imetaData = metaData.getComponentMetaData(fis);
               if (imetaData != null)
                  annotation = imetaData.getAnnotation(annotationType);
            }
         }
         if(annotation != null)
            log.trace("Loaded "+annotationType+" from MetaData");
      }
      if(annotation == null)
         annotation = info.getUnderlyingAnnotation(annotationType);
      return annotation;
   }

   /**
    * Build up a BeanInfo from the interface class and all interfaces it
    * implements.
    * 
    * @param iface - the interface for the managed object
    * @return the full BeanInfo for the iface
    */
   protected BeanInfo getBeanInfo(Class<?> iface)
   {
      BeanInfo ifaceBI = configuration.getBeanInfo(iface);
      Class<?>[] superIfaces = iface.getInterfaces();
      if(superIfaces != null && superIfaces.length > 0)
      {
         // Combine all properties
         Set<PropertyInfo> allProps = new HashSet<PropertyInfo>(ifaceBI.getProperties());
         // Combine all operations
         Set<MethodInfo> allMethods = new HashSet<MethodInfo>(ifaceBI.getMethods());
         for(Class<?> superIface : superIfaces)
         {
            BeanInfo cBI = configuration.getBeanInfo(superIface);
            Set<PropertyInfo> props = cBI.getProperties();
            if(props != null)
               allProps.addAll(props);
            Set<MethodInfo> methods = cBI.getMethods();
            if(methods != null)
               allMethods.addAll(methods);
         }

         ifaceBI.setProperties(allProps);
         ifaceBI.setMethods(allMethods);
      }
      return ifaceBI;
   }
}
