/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.config.type;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;

import javax.enterprise.context.spi.CreationalContext;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.InjectionTarget;

import org.w3c.dom.Node;

import com.caucho.config.ConfigException;
import com.caucho.config.DependencyBean;
import com.caucho.config.TagName;
import com.caucho.config.annotation.DisableConfig;
import com.caucho.config.annotation.NonEL;
import com.caucho.config.attribute.AddAttribute;
import com.caucho.config.attribute.Attribute;
import com.caucho.config.attribute.CreateAttribute;
import com.caucho.config.attribute.ProgramAttribute;
import com.caucho.config.attribute.PropertyAttribute;
import com.caucho.config.attribute.SetterAttribute;
import com.caucho.config.attribute.TextAttribute;
import com.caucho.config.inject.InjectManager;
import com.caucho.config.inject.InjectionTargetBuilder;
import com.caucho.config.inject.ManagedBeanImpl;
import com.caucho.config.inject.OwnerCreationalContext;
import com.caucho.config.program.ConfigProgram;
import com.caucho.config.program.PropertyStringProgram;
import com.caucho.config.types.RawString;
import com.caucho.config.xml.XmlBeanAttribute;
import com.caucho.config.xml.XmlBeanConfig;
import com.caucho.config.xml.XmlConfigContext;
import com.caucho.util.L10N;
import com.caucho.vfs.Dependency;
import com.caucho.vfs.PersistentDependency;
import com.caucho.xml.QName;
import com.caucho.xml.QNode;

/**
 * Represents an inline bean type for configuration.
 */
public class InlineBeanType<T> extends ConfigType<T>
{
  private static final L10N L = new L10N(InlineBeanType.class);
  private static final Logger log
    = Logger.getLogger(InlineBeanType.class.getName());
  
  private static final String RESIN_NS
    = "http://caucho.com/ns/resin";

  public static final QName TEXT = new QName("#text");
  public static final QName VALUE = new QName("value");

  private static final Object _introspectLock = new Object();

  private final Class<T> _beanClass;
  
  private ConcurrentHashMap<QName,Attribute> _nsAttributeMap
    = new ConcurrentHashMap<QName,Attribute>();
  
  private HashMap<String,Attribute> _attributeMap
    = new HashMap<String,Attribute>();

  private Constructor<T> _stringConstructor;
  
  private Method _valueOf;
  private Method _setParent;
  private Method _replaceObject;
  private Method _setConfigLocation;
  private Method _setConfigUriLocation;
  private Method _setConfigNode;
  
  private Attribute _addText;
  private Attribute _addProgram;
  private Attribute _addContentProgram;
  private Attribute _addBean; // add(Object)
  private Attribute _setProperty;
  
  private boolean _isEL;

  private HashMap<Class<?>,Attribute> _addMethodMap
    = new HashMap<Class<?>,Attribute>();

  private Attribute _addCustomBean;
  private AnnotatedType<T> _annotatedType;
  private ManagedBeanImpl<T> _bean;
  private InjectionTarget<T> _injectionTarget;

  //private ArrayList<ConfigProgram> _injectList;
  private ArrayList<ConfigProgram> _initList;
  
  private boolean _isIntrospecting;
  private boolean _isIntrospected;
  private ArrayList<InlineBeanType<?>> _pendingChildList
    = new ArrayList<InlineBeanType<?>>();

  public InlineBeanType(Class<T> beanClass)
  {
    _beanClass = beanClass;
    
    if (EnvBean.class.isAssignableFrom(beanClass))
      setEnvBean(true);
  }

  /**
   * Returns the given type.
   */
  @Override
  public Class<T> getType()
  {
    return _beanClass;
  }
  
  @Override
  public boolean isEL()
  {
    return _isEL;
  }

  protected void setAddCustomBean(Attribute addCustomBean)
  {
    _addCustomBean = addCustomBean;
  }

  protected void setAddAnnotation(Attribute addAnnotation)
  {
  }

  /**
   * Creates a new instance
   */
  @Override
  public Object create(Object parent, QName name)
  {
    try {
      if (_injectionTarget == null) {
        if (_beanClass.isInterface())
          throw new ConfigException(L.l("{0} cannot be instantiated because it is an interface",
                                        _beanClass.getName()));

        AnnotatedType<T> type = getAnnotatedType();

        InjectManager cdiManager
          = InjectManager.create(_beanClass.getClassLoader());
        
        InjectionTargetBuilder<T> builder
          = new InjectionTargetBuilder<T>(cdiManager, type);

        // server/2m09
        // builder.setGenerateInterception(false);

        _injectionTarget = builder;

        // _bean.getInjectionPoints();
      }

      InjectionTarget<T> injection = _injectionTarget;
      CreationalContext<T> env = new OwnerCreationalContext<T>(_bean);

      T bean = injection.produce(env);
      injection.inject(bean, env);

      if (_setParent != null
          && parent != null
          && _setParent.getParameterTypes()[0].isAssignableFrom(parent.getClass())) {
        try {
          _setParent.invoke(bean, parent);
        } catch (IllegalArgumentException e) {
          throw ConfigException.create(_setParent,
                                       L.l("{0}: setParent value of '{1}' is not valid",
                                           bean, parent),
                                           e);
        } catch (Exception e) {
          throw ConfigException.create(_setParent, e);
        }
      }

      return bean;
    } catch (Exception e) {
      throw ConfigException.create(e);
    }
  }
  
  private AnnotatedType<T> getAnnotatedType()
  {
    if (_annotatedType == null) {
      InjectManager cdiManager
        = InjectManager.create(_beanClass.getClassLoader());
  
      _annotatedType = cdiManager.createAnnotatedType(_beanClass);
    }
    
    return _annotatedType;
  }

  /**
   * Returns a constructor with a given number of arguments
   */
  @Override
  public Constructor<T> getConstructor(int count)
  {
    for (Constructor<?> ctor : _beanClass.getConstructors()) {
      if (ctor.getParameterTypes().length == count)
        return (Constructor<T>) ctor;
    }
    
    throw new ConfigException(L.l("{0} does not have any constructor with {1} arguments",
                                  this, count));
  }

  /**
   * Called before the children are configured.
   */
  @Override
  public void beforeConfigure(XmlConfigContext env, Object bean, Node node)
  {
    super.beforeConfigure(env, bean, node);

    if (_setConfigNode != null) {
      try {
        _setConfigNode.invoke(bean, node);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }
    }

    if (_setConfigLocation != null && node instanceof QNode) {
      String filename = ((QNode) node).getFilename();
      int line = ((QNode) node).getLine();

      try {
        // _setConfigLocation.invoke(bean, filename, line);
        _setConfigLocation.invoke(bean, filename, line);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }
    }

    if (_setConfigUriLocation != null && node instanceof QNode) {
      String uri = ((QNode) node).getBaseURI();
      int line = ((QNode) node).getLine();

      try {
        _setConfigUriLocation.invoke(bean, uri, line);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }
    }

    if (bean instanceof DependencyBean) {
      DependencyBean dependencyBean = (DependencyBean) bean;
      
      ArrayList<Dependency> dependencyList = env.getDependencyList();

      if (dependencyList != null) {
        for (Dependency depend : dependencyList) {
          dependencyBean.addDependency((PersistentDependency) depend);
        }
      }
    }
  }

  /**
   * Returns the attribute based on the given name.
   */
  @Override
  public Attribute getAttribute(QName name)
  {
    Attribute attr = _nsAttributeMap.get(name);

    if (attr == null) {
      attr = getAttributeImpl(name);

      if (attr != null)
        _nsAttributeMap.put(name, attr);
    }

    return attr;
  }

  protected Attribute getAttributeImpl(QName name)
  {
    // server/2r10 vs jms/2193
    // attr = _attributeMap.get(name.getLocalName().toLowerCase(Locale.ENGLISH));

    Attribute attr = _attributeMap.get(name.getLocalName());

    if (attr != null)
      return attr;

    String uri = name.getNamespaceURI();

    if (uri == null || ! uri.startsWith("urn:java"))
      return null;

    Class<?> cl = createClass(name);

    if (cl != null) {
      attr = getAddAttribute(cl);

      if (attr != null)
        return attr;
    }
    
    // server/13jm - environment beans have priority over the custom bean
    ConfigType<?> envBean = TypeFactory.getFactory().getEnvironmentType(name);
    
    if (envBean != null && envBean.isEnvBean())
      return null;

    if (_addCustomBean != null) {
      return _addCustomBean;
    }
    else if (_addBean != null) {
      return _addBean;
    }

    return null;
  }

  @Override
  public Attribute getAddBeanAttribute(QName qName)
  {
    return _addBean;
  }

  /**
   * Returns any add attributes to add arbitrary content
   */
  @Override
  public Attribute getAddAttribute(Class<?> cl)
  {
    if (cl == null)
      return null;

    Attribute attr = _addMethodMap.get(cl);

    if (attr != null) {
      return attr;
    }
    
    for (Class<?> iface : cl.getInterfaces()) {
      attr = getAddAttribute(iface);

      if (attr != null)
        return attr;
    }

    return getAddAttribute(cl.getSuperclass());
  }

  private Class<?> createClass(QName name)
  {
    String uri = name.getNamespaceURI();

    if (uri.equals(RESIN_NS)) {
      return createResinClass(name.getLocalName());
    }

    if (! uri.startsWith("urn:java:"))
      return null;

    String pkg = uri.substring("urn:java:".length());

    return TypeFactory.loadClass(pkg, name.getLocalName());
  }

  private Class<?> createResinClass(String name)
  {
    Class<?> cl = TypeFactory.loadClass("ee", name);

    return cl;
  }

  /**
   * Returns the program attribute.
   */
  @Override
  public Attribute getProgramAttribute()
  {
    if (_setProperty != null)
      return _setProperty;
    else
      return _addProgram;
  }

  /**
   * Returns the content program attribute (program excluding if, choose).
   */
  @Override
  public Attribute getContentProgramAttribute()
  {
    return _addContentProgram;
  }

  /**
   * Initialize the type
   */
  @Override
  public void inject(Object bean)
  {
    introspectInject();
    
    /*
    for (int i = 0; i < _injectList.size(); i++)
      _injectList.get(i).inject(bean, null);
      */
  }

  /**
   * Initialize the type
   */
  @Override
  public void init(Object bean)
  {
    introspectInject();
    
    for (int i = 0; i < _initList.size(); i++)
      _initList.get(i).inject(bean, null);
  }

  /**
   * Return true if the object is replaced
   */
  @Override
  public boolean isReplace()
  {
    return _replaceObject != null;
  }
  
  /**
   * Replace the type with the generated object
   */
  @Override
  public Object replaceObject(Object bean)
  {
    if (_replaceObject != null) {
      try {
        return _replaceObject.invoke(bean);
      } catch (Exception e) {
        throw ConfigException.create(_replaceObject, e);
      }
    }
    else
      return bean;
  }
  
  /**
   * Converts the string to the given value.
   */
  public Object valueOf(String text)
  {
    if (_valueOf != null) {
      try {
        return _valueOf.invoke(null, text);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }
    }
    else if (_stringConstructor != null) {
      try {
        return _stringConstructor.newInstance(text);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }
    }
    else if (_addText != null) {
      Object bean = create(null, TEXT);
      _addText.setText(bean, TEXT, text);

      inject(bean);
      init(bean);
      
      return bean;
    }
    else if (_addProgram != null || _addContentProgram != null) {
      Object bean = create(null, TEXT);

      inject(bean);
      
      try {
        ConfigProgram program = new PropertyStringProgram(TEXT, text);
        
        if (_addProgram != null)
          _addProgram.setValue(bean, TEXT, program);
        else
          _addContentProgram.setValue(bean, TEXT, program);
      } catch (Exception e) {
        throw ConfigException.create(e);
      }

      init(bean);

      return bean;
    }
    else if ("".equals(text.trim())) {
      Object bean = create(null, TEXT);

      inject(bean);
      init(bean);
      
      return bean;
    }

    throw new ConfigException(L.l("Can't convert to '{0}' from '{1}'.",
                                  _beanClass.getName(), text));
  }

  public boolean isConstructableFromString()
  {
    return _valueOf != null ||
           _stringConstructor != null ||
           _addText != null ||
           _addProgram != null ||
           _addContentProgram != null;
  }

  /**
   * Converts the string to the given value.
   */
  @Override
  public Object valueOf(Object value)
  {
    if (value == null)
      return null;
    else if (value instanceof String)
      return valueOf((String) value);
    else if (_beanClass.isAssignableFrom(value.getClass()))
      return value;
    else if (value.getClass().getName().startsWith("java.lang."))
      return valueOf(String.valueOf(value));
    else
      return value;
  }

  //
  // Introspection
  //

  /**
   * Introspect the bean for configuration
   */
  @Override
  public void introspect()
  {
    // long startTime = System.currentTimeMillis();
    synchronized (_introspectLock) {
      if (_isIntrospecting)
        return;

      _isIntrospecting = true;

      try {
        // ioc/20h4 - after to deal with recursion
        introspectParent();

        //Method []methods = _beanClass.getMethods();
        if (! _isIntrospected) {
          _isIntrospected = true;

          _isEL = ! _beanClass.isAnnotationPresent(NonEL.class);
          
          try {
            // System.out.println("INTROSPECT: " + _beanClass);
            Method []methods = _beanClass.getDeclaredMethods();

            introspectMethods(methods);
          } catch (NoClassDefFoundError e) {
            log.fine(_beanClass + " " + e);
          }
        }
      } finally {
        _isIntrospecting = false;
      }
    }

    introspectComplete();
    //long endTime = System.currentTimeMillis();
  }

  private void introspectComplete()
  {
    ArrayList<InlineBeanType<?>> childList
      = new ArrayList<InlineBeanType<?>>(_pendingChildList);

    // ioc/20h4
    for (InlineBeanType<?> child : childList) {
      child.introspectParent();
      child.introspectComplete();
    }
  }
  
  private boolean isIntrospecting()
  {
    if (_isIntrospecting)
      return true;

    Class<?> parentClass = _beanClass.getSuperclass();
    
    if (parentClass != null) {
      ConfigType<?> parentType = TypeFactory.getType(parentClass);

      if (parentType instanceof InlineBeanType<?>) {
        InlineBeanType<?> parentBean = (InlineBeanType<?>) parentType;

        return parentBean.isIntrospecting();
      }
    }

    return false;
  }

  private void introspectParent()
  {
    Class<?> parentClass = _beanClass.getSuperclass();
    
    if (parentClass != null) {
      ConfigType<?> parentType = TypeFactory.getType(parentClass);

      if (parentType instanceof InlineBeanType<?>) {
        InlineBeanType<?> parentBean = (InlineBeanType<?>) parentType;

        if (! parentBean._isIntrospected)
          parentBean.introspect();

        // ioc/20h4
        if (parentBean.isIntrospecting()) {
          if (! parentBean._pendingChildList.contains(this))
            parentBean._pendingChildList.add(this);
          return;
        }

        if (_setParent == null)
          _setParent = parentBean._setParent;

        if (_replaceObject == null)
          _replaceObject = parentBean._replaceObject;

        if (_setConfigLocation == null)
          _setConfigLocation = parentBean._setConfigLocation;

        if (_setConfigUriLocation == null)
          _setConfigUriLocation = parentBean._setConfigUriLocation;

        if (_setConfigNode == null)
          _setConfigNode = parentBean._setConfigNode;

        if (_addText == null)
          _addText = parentBean._addText;

        if (_addProgram == null)
          _addProgram = parentBean._addProgram;

        if (_addContentProgram == null)
          _addContentProgram = parentBean._addContentProgram;

        if (_setProperty == null)
          _setProperty = parentBean._setProperty;

        if (_addCustomBean == null)
          _addCustomBean = parentBean._addCustomBean;

        for (Map.Entry<QName,Attribute> entry : parentBean._nsAttributeMap.entrySet()) {
          if (_nsAttributeMap.get(entry.getKey()) == null)
            _nsAttributeMap.put(entry.getKey(), entry.getValue());
        }

        for (Map.Entry<String,Attribute> entry : parentBean._attributeMap.entrySet()) {
          if (_attributeMap.get(entry.getKey()) == null)
            _attributeMap.put(entry.getKey(), entry.getValue());
        }

        _addMethodMap.putAll(parentBean._addMethodMap);
      }
    }
  }

  /**
   * Introspect the beans methods for setters
   */
  public void introspectMethods(Method []methods)
  {
    Constructor<?> []constructors = _beanClass.getConstructors();

    _stringConstructor = findConstructor(constructors, String.class);

    HashMap<String,Method> createMap = new HashMap<String,Method>(8);
    fillCreateMap(createMap, methods);

    HashMap<String,Method> setterMap = new HashMap<String,Method>(8);
    fillSetterMap(setterMap, methods);

    for (Method method : methods) {
      if (method.getAnnotation(DisableConfig.class) != null)
        continue;
      
      Class<?> []paramTypes = method.getParameterTypes();

      String name = method.getName();

      if ("replaceObject".equals(name) && paramTypes.length == 0) {
        _replaceObject = method;
        _replaceObject.setAccessible(true);
        continue;
      }

      if ("valueOf".equals(name)
          && paramTypes.length == 1
          && String.class.equals(paramTypes[0])
          && Modifier.isStatic(method.getModifiers())) {
        _valueOf = method;
        _valueOf.setAccessible(true);
        continue;
      }

      if (Modifier.isStatic(method.getModifiers()))
        continue;

      if (! Modifier.isPublic(method.getModifiers()))
        continue;

      if ((name.equals("addBuilderProgram") || name.equals("addProgram"))
          && paramTypes.length == 1
          && paramTypes[0].equals(ConfigProgram.class)) {
        ConfigType<?> type = TypeFactory.getType(paramTypes[0]);

        _addProgram = new ProgramAttribute(method, type);
      }
      else if (name.equals("addContentProgram")
          && paramTypes.length == 1
          && paramTypes[0].equals(ConfigProgram.class)) {
        ConfigType<?> type = TypeFactory.getType(paramTypes[0]);

        _addContentProgram = new ProgramAttribute(method, type);
      }
      else if ((name.equals("setConfigLocation")
          && paramTypes.length == 2
          && paramTypes[0].equals(String.class)
          && paramTypes[1].equals(int.class))) {
        _setConfigLocation = method;
      }
      else if ((name.equals("setConfigUriLocation")
          && paramTypes.length == 2
          && paramTypes[0].equals(String.class)
          && paramTypes[1].equals(int.class))) {
        _setConfigUriLocation = method;
      }
      else if ((name.equals("setConfigNode")
          && paramTypes.length == 1
          && paramTypes[0].equals(Node.class))) {
        _setConfigNode = method;
      }
      else if ((name.equals("addCustomBean")
          && paramTypes.length == 1
          && paramTypes[0].equals(XmlBeanConfig.class))) {
        ConfigType<?> customBeanType
          = TypeFactory.getType(XmlBeanConfig.class);

        _addCustomBean = new XmlBeanAttribute(method, customBeanType);
      }
      else if ((name.equals("addAnnotation")
          && paramTypes.length == 1
          && paramTypes[0].equals(Annotation.class))) {
        ConfigType<?> customBeanType
          = TypeFactory.getType(XmlBeanConfig.class);

        _addCustomBean = new XmlBeanAttribute(method, customBeanType);
      }
      else if (name.equals("setProperty")
          && paramTypes.length == 2
          && paramTypes[0].equals(String.class)) {
        ConfigType<?> type = TypeFactory.getType(paramTypes[1]);

        PropertyAttribute attr = new PropertyAttribute(method, type);

        _setProperty = attr;
      }
      else if (name.equals("setParent")
          && paramTypes.length == 1) {
        // XXX: use annotation
        _setParent = method;
      }
      else if (name.equals("add")
               && paramTypes.length == 1) {
        ConfigType<?> type = TypeFactory.getType(paramTypes[0]);

        Attribute addAttr = new AddAttribute(method, type);

        _addMethodMap.put(paramTypes[0], addAttr);

        if (paramTypes[0].equals(Bean.class)) { 
          _addBean = addAttr;
        }
      }
      else if ((name.startsWith("set") || name.startsWith("add"))
          && paramTypes.length == 1
          && createMap.get(name.substring(3)) == null) {
        String className = name.substring(3);
        String xmlName = toXmlName(name.substring(3));

        TagName tagName = method.getAnnotation(TagName.class);

        if (tagName != null) {
          for (String propName : tagName.value()) {
            addProp(propName, method);
          }
        }
        else
          addProp(xmlName, method);

        addProp(toCamelName(className), method);
      }
      else if ((name.startsWith("create")
          && paramTypes.length == 0
          && ! void.class.equals(method.getReturnType()))) {
        Class<?> type = method.getReturnType();

        Method setter = setterMap.get(name.substring(6));

        CreateAttribute attr = new CreateAttribute(method, type, setter);

        String xmlName = toXmlName(name.substring(6));

        TagName tagName = method.getAnnotation(TagName.class);

        if (tagName != null) {
          for (String propName : tagName.value()) {
            addProp(propName, attr);
          }
        }
        else {
          addProp(xmlName, attr);
        }
      }
    }
  }
  
  private void addProp(String propName, 
                       Method method)
  {
    Attribute attr;
    
    Class<?> []paramTypes = method.getParameterTypes();
    Class<?> type = paramTypes[0];
    
    if (propName.equals("text")
        && (type.equals(String.class)
            || type.equals(RawString.class))) {
      attr = new TextAttribute(method, type);
      _addText = attr;
      _attributeMap.put("#text", attr);
    }
    else
      attr = new SetterAttribute(method, type);
    
    addProp(propName, attr);
  }
  
  private void addProp(String propName, Attribute attr)
  {
    Attribute oldAttr = _attributeMap.get(propName);
    
    if (oldAttr == null) {
      _attributeMap.put(propName, attr);
    }
    else if (attr.equals(oldAttr)) {
    }
    else if (oldAttr.isConfigurable() && ! attr.isConfigurable()) {
    }
    else if (attr.isConfigurable() && ! oldAttr.isConfigurable()) {
      _attributeMap.put(propName, attr);
    }
    else if (attr.isAssignableFrom(oldAttr)) {
    }
    else if (oldAttr.isAssignableFrom(attr)) {
      _attributeMap.put(propName, attr);
    }
    else {
      log.fine(L.l("{0}: conflicting attribute for '{1}' between {2} and {3}",
                   this, propName, attr, oldAttr));    
    }
    
    // server/2e28 vs jms/2193
    // _attributeMap.put(className, attr);

    if (propName.equals("value")) {
      _attributeMap.put("#text", attr);

      // server/12aa
      if (_addText == null)
        _addText = attr;
    }
  }


  /**
   * Introspect the bean for configuration
   */
  private void introspectInject()
  {
    synchronized (_introspectLock) {
      if (_initList != null)
        return;

      ArrayList<ConfigProgram> initList = new ArrayList<ConfigProgram>();
    
      InjectionTargetBuilder.introspectInit(initList, getAnnotatedType());
      // InjectionTargetBuilder.introspectInit(initList, _beanClass);
      
      _initList = initList;
    }
  }

  private static Constructor findConstructor(Constructor<?> []constructors,
                                             Class<?> ...types)
  {
    for (Constructor<?> ctor : constructors) {
      Class<?> []paramTypes = ctor.getParameterTypes();

      if (isMatch(paramTypes, types))
        return ctor;
    }

    return null;
  }

  private static boolean isMatch(Class<?> []aTypes, Class<?> []bTypes)
  {
    if (aTypes.length != bTypes.length)
      return false;

    for (int i = aTypes.length - 1; i >= 0; i--) {
      if (! aTypes[i].equals(bTypes[i]))
        return false;
    }

    return true;
  }


  private void fillCreateMap(HashMap<String,Method> createMap,
                             Method []methods)
  {
    for (Method method : methods) {
      String name = method.getName();

      if (name.startsWith("create")
          && ! name.equals("create")
          && method.getParameterTypes().length == 0) {
        createMap.put(name.substring("create".length()), method);
      }
    }
  }

  private void fillSetterMap(HashMap<String,Method> setterMap,
                             Method []methods)
  {
    for (Method method : methods) {
      String name = method.getName();

      if (name.length() > 3
          && (name.startsWith("add") || name.startsWith("set"))
          && method.getParameterTypes().length == 1) {
        setterMap.put(name.substring("set".length()), method);
      }
    }
  }

  private String toXmlName(String name)
  {
    StringBuilder sb = new StringBuilder();

    for (int i = 0; i < name.length(); i++) {
      char ch = name.charAt(i);

      if (Character.isUpperCase(ch)
          && i > 0
          && (Character.isLowerCase(name.charAt(i - 1))
              || (i + 1 < name.length()
                  && Character.isLowerCase(name.charAt(i + 1))))) {
        sb.append('-');
      }

      sb.append(Character.toLowerCase(ch));
    }

    return sb.toString();
  }

  private String toCamelName(String name)
  {
    return Introspector.decapitalize(name);
  }

  @Override
  public String toString()
  {
    return getClass().getSimpleName() + "[" + _beanClass.getName() + "]";
  }
}
