/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, 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.ws.tools.schema;

import java.beans.BeanInfo;
import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import javax.ejb.SessionBean;
import javax.xml.namespace.QName;

import org.apache.xerces.xs.XSComplexTypeDefinition;
import org.apache.xerces.xs.XSConstants;
import org.apache.xerces.xs.XSElementDeclaration;
import org.apache.xerces.xs.XSModelGroup;
import org.apache.xerces.xs.XSObjectList;
import org.apache.xerces.xs.XSParticle;
import org.apache.xerces.xs.XSTerm;
import org.apache.xerces.xs.XSTypeDefinition;
import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.jaxrpc.LiteralTypeMapping;
import org.jboss.ws.core.jaxrpc.ParameterWrapping;
import org.jboss.ws.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.ws.metadata.jaxrpcmapping.JavaXmlTypeMapping;
import org.jboss.ws.metadata.jaxrpcmapping.PackageMapping;
import org.jboss.ws.metadata.jaxrpcmapping.VariableMapping;
import org.jboss.ws.metadata.wsdl.WSDLUtils;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSComplexTypeDefinition;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSElementDeclaration;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSModel;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSModelGroup;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSParticle;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSSimpleTypeDefinition;
import org.jboss.ws.metadata.wsdl.xmlschema.JBossXSTypeDefinition;
import org.jboss.ws.metadata.wsdl.xmlschema.WSSchemaUtils;
import org.jboss.ws.metadata.wsdl.xsd.SchemaUtils;
import org.jboss.ws.tools.interfaces.SchemaCreatorIntf;
import org.jboss.xb.binding.NamespaceRegistry;
import org.jboss.xb.binding.SimpleTypeBindings;

/**
 *  Implementation for creation of schema types
 *  @author <mailto:Anil.Saldhana@jboss.org>Anil Saldhana
 *  @since   Aug 31, 2005
 */
public class SchemaTypeCreator implements SchemaCreatorIntf
{
   protected Logger log = Logger.getLogger(SchemaTypeCreator.class);
   protected WSDLUtils utils = WSDLUtils.getInstance();

   protected SchemaUtils schemautils = SchemaUtils.getInstance();
   protected WSSchemaUtils sutils = null;

   protected LiteralTypeMapping typeMapping = new LiteralTypeMapping();

   // Zero or more namespace definitions
   private NamespaceRegistry namespaces = null;

   private JavaWsdlMapping javaWsdlMapping = new JavaWsdlMapping();

   protected String xsNS = Constants.NS_SCHEMA_XSD;

   private int maxPrefix = 1;

   protected JBossXSModel xsModel = null;

   //A Map of custom package->namespace mapping provided by the user
   protected Map<String, String> packageNamespaceMap = new HashMap<String, String>();

   public SchemaTypeCreator()
   {
      xsModel = new JBossXSModel();
      namespaces = xsModel.getNamespaceRegistry();
      sutils = WSSchemaUtils.getInstance(namespaces, null);
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#addPackageNamespaceMapping(
    java.lang.String,java.lang.String)
    */
   public void addPackageNamespaceMapping(String pkgname, String ns)
   {
      if (pkgname == null)
         throw new IllegalArgumentException("Illegal Null Argument:pkgname");
      if (ns == null)
         throw new IllegalArgumentException("Illegal Null Argument:ns");
      packageNamespaceMap.put(pkgname, ns);

   }

   public JBossXSTypeDefinition generateType(QName xmlType, Class javaType)
   {
      return generateType(xmlType, javaType, null);
   }

   public JBossXSTypeDefinition generateType(QName xmlType, Class javaType, Map<String, QName> elementNames)
   {
      return getType(xmlType, javaType, elementNames);
   }

   public QName getXMLSchemaType(Class javaType)
   {
      QName xmlt = schemautils.getToolsOverrideInTypeMapping(javaType);
      //Check if it is already registered
      if (xmlt == null)
         xmlt = typeMapping.getXMLType(javaType, false);
      return xmlt;
   }

   public JavaWsdlMapping getJavaWsdlMapping()
   {
      return javaWsdlMapping;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#getXSModel()
    */
   public JBossXSModel getXSModel()
   {
      return xsModel;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#getCustomNamespaceMap()
    */
   public HashMap getCustomNamespaceMap()
   {
      HashMap<String, String> map = null;
      if (namespaces != null)
      {
         Iterator iter = namespaces.getRegisteredPrefixes();
         while (iter != null && iter.hasNext())
         {
            String prefix = (String)iter.next();
            if (prefix.startsWith("ns"))
            {
               if (map == null)
                  map = new HashMap<String, String>();
               map.put(prefix, namespaces.getNamespaceURI(prefix));
            }
         }
      }
      return map;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#getPackageNamespaceMap()
    */
   public Map<String, String> getPackageNamespaceMap()
   {
      return this.packageNamespaceMap;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#getTypeMapping()
    */
   public LiteralTypeMapping getTypeMapping()
   {
      return typeMapping;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#getJavaType(javax.xml.namespace.QName)
    */
   public Class getJavaType(QName xmlType)
   {
      Class retType = typeMapping.getJavaType(xmlType);
      if (retType == null)
         throw new IllegalArgumentException("Unsupported type: " + xmlType);
      return retType;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#setXSModel(org.jboss.ws.wsdl.xmlschema.JBossXSModel)
    */
   public void setXSModel(JBossXSModel xsm)
   {
      xsModel = xsm;
   }

   /**
    * If there is a type being used from a different namespace, a custom
    * prefix will be needed
    * @param nsuri
    * @return New Prefix
    */
   public String allocatePrefix(String nsURI)
   {
      String prefix = namespaces.getPrefix(nsURI);
      return (prefix == null) ? namespaces.registerURI(nsURI, null) : prefix;
   }

   /* (non-Javadoc)
    * @see org.jboss.ws.tools.schema.SchemaCreatorIntf#setPackageNamespaceMap(java.util.Map)
    */
   public void setPackageNamespaceMap(Map<String, String> packageNamespaceMap)
   {
      this.packageNamespaceMap = packageNamespaceMap;
   }

   //PRIVATE METHODS
   private JBossXSTypeDefinition checkTypeExistsInXSModel(QName xmlt)
   {
      JBossXSTypeDefinition ct = null;
      if (xmlt != null && xsModel != null)
         ct = getXSTypeDefinitionIfPresent(xmlt);
      return ct;
   }

   private JBossXSTypeDefinition getType(QName xmlType, Class javaType, Map<String, QName> elementNames)
   {
      JBossXSTypeDefinition ct = null;
      boolean registered = false;

      // Step 1: Check if the javaType is registered in the typeMapping
      if (xmlType == null)
      {
         xmlType = getXMLSchemaType(javaType);
         registered = xmlType != null;
      }
      else
      {
         registered = typeMapping.isRegistered(javaType, xmlType);
      }

      // Step 2: if the javatype is registered, search the xsmodel to obtain the type
      if (registered)
         ct = checkTypeExistsInXSModel(xmlType);

      // If the type is still unavailable, see xmlt represents schema basic type
      if (ct == null && xmlType != null && Constants.NS_SCHEMA_XSD.equals(xmlType.getNamespaceURI()) && !javaType.isArray())
         ct = schemautils.getSchemaBasicType(xmlType.getLocalPart());

      if (ct == null && Exception.class.isAssignableFrom(javaType))
         ct = this.getComplexTypeForJavaException(xmlType, javaType);

      // Step 3: If the type does not exist in the xsmodel, create a new type
      if (ct == null)
         ct = generateNewType(xmlType, javaType, elementNames);

      return ct;
   }

   private JBossXSTypeDefinition getXSTypeDefinitionIfPresent(QName qname)
   {
      return (JBossXSTypeDefinition)xsModel.getTypeDefinition(qname.getLocalPart(), qname.getNamespaceURI());
   }

   private JBossXSTypeDefinition generateNewType(QName xmlType, Class javaType, Map<String, QName> elementNames)
   {
      //Step 1: Take care of superclass (if any)::Generate Type for the base class, if any
      Class superclass = javaType.getSuperclass();
      JBossXSTypeDefinition baseType = null;
      List<XSParticle> particles = new ArrayList<XSParticle>();
      if (superclass != null && !utils.checkIgnoreClass(superclass))
      {
         baseType = generateType(null, superclass);
         if (baseType != null)
         {
            addBaseTypeParts(baseType, particles);
         }
      }
      //Check if the javaType is an array
      if (javaType.isArray())
         return handleArray(xmlType, javaType);

      String name;
      String namespace;
      if (xmlType != null)
      {
         name = xmlType.getLocalPart();
         namespace = getNamespace(javaType, xmlType.getNamespaceURI());
      }
      else
      {
         name = WSDLUtils.getJustClassName(javaType);
         namespace = getNamespace(javaType, null);
      }

      // Check if it is a JAX-RPC enumeration
      Class valueType = getEnumerationValueType(javaType);
      if (valueType != null)
         return handleJAXRPCEnumeration(name, namespace, javaType, valueType);

      // Generate and register the complex type before building it's particles
      // This solves circular reference problems
      JBossXSComplexTypeDefinition complexType = sutils.createXSComplexTypeDefinition(name, baseType, particles, namespace);
      QName registerQName = new QName(namespace, name);
      typeMapping.register(javaType, registerQName, null, null);
      xsModel.addXSComplexTypeDefinition(complexType);

      //Step 2: Generate XSParticles for all the public fields
      particles.addAll(getXSParticlesForPublicFields(namespace, javaType, elementNames));

      //Step 3: Generate XSParticles for the properties
      try
      {
         particles.addAll(introspectJavaProperties(namespace, javaType, elementNames));
      }
      catch (IntrospectionException e)
      {
         log.error("Problem in introspection of the Java Type during type generation", e);
         throw new WSException(e);
      }

      if (elementNames instanceof LinkedHashMap)
         particles = sortNamedOuterParticles(particles, (LinkedHashMap<String, QName>)elementNames);
      else Collections.sort((List)particles);

      registerJavaTypeMapping(registerQName, javaType, "complexType", particles, elementNames);

      JBossXSModelGroup group = (JBossXSModelGroup)complexType.getParticle().getTerm();
      group.setParticles(particles);

      if (log.isDebugEnabled())
         log.debug("generateNewType: " + sutils.write(complexType));
      return complexType;
   }

   private JBossXSTypeDefinition handleJAXRPCEnumeration(String name, String namespace, Class<?> javaType, Class<?> valueType)
   {
      JBossXSTypeDefinition enumType = generateType(null, valueType);
      JBossXSSimpleTypeDefinition simpleType = new JBossXSSimpleTypeDefinition();
      simpleType.setBaseType(enumType);
      simpleType.setName(name);
      simpleType.setNamespace(namespace);
      try
      {
         Method getValue = javaType.getMethod("getValue");
         for (Field field : javaType.getFields())
         {
            if (Modifier.isStatic(field.getModifiers()) && field.getType().equals(javaType))
            {
               Object ret = getValue.invoke(field.get(null));
               String item = SimpleTypeBindings.marshal(enumType.getName(), ret, new NamespaceRegistry());
               simpleType.addLexicalEnumeration(item);
            }
         }
      }
      catch (Exception e)
      {
         throw new WSException("JAX-RPC Enumeration type did not conform to expectations");
      }

      xsModel.addXSTypeDefinition(simpleType);
      registerJavaTypeMapping(new QName(namespace, name), javaType, "simpleType", new ArrayList<XSParticle>(), null);

      return simpleType;
   }

   private Class<?> getEnumerationValueType(Class javaType)
   {
      try
      {
         javaType.getConstructor();
         return null;
      }
      catch (NoSuchMethodException e)
      {
      }

      for (Method method : javaType.getMethods())
      {
         if (Modifier.isStatic(method.getModifiers()) && method.getName().equals("fromValue") && method.getParameterTypes().length == 1)
            return method.getParameterTypes()[0];
      }

      return null;
   }

   private List<XSParticle> sortNamedOuterParticles(List<XSParticle> particles, LinkedHashMap<String, QName> elementNames)
   {
      Map<String, XSParticle> index = new LinkedHashMap<String, XSParticle>();
      List<XSParticle> newList = new ArrayList<XSParticle>();
      for (XSParticle particle : particles)
      {
         XSTerm term = particle.getTerm();
         if (term instanceof XSElementDeclaration)
            index.put(((XSElementDeclaration)term).getName(), particle);
         else newList.add(particle);
      }

      // Follow the order of elementNames
      for (QName name : elementNames.values())
      {
         XSParticle found = index.get(name.getLocalPart());
         if (found != null)
         {
            index.remove(name.getLocalPart());
            newList.add(found);
         }
      }

      // Add everything else
      for (XSParticle particle : index.values())
      {
         newList.add(particle);
      }

      return newList;
   }

   private void registerJavaTypeMapping(QName registerQName, Class javaType, String scope, List<XSParticle> particles, Map<String, QName> elementNames)
   {
      QName qname = new QName(registerQName.getNamespaceURI(), registerQName.getLocalPart(), "typeNS");

      // Add package mapping if needed
      String packageName = javaWsdlMapping.getPackageNameForNamespaceURI(qname.getNamespaceURI());
      if (packageName == null && javaType.getPackage() != null)
      {
         PackageMapping packageMapping = new PackageMapping(javaWsdlMapping);
         packageMapping.setNamespaceURI(qname.getNamespaceURI());
         packageMapping.setPackageType(javaType.getPackage().getName());
         javaWsdlMapping.addPackageMapping(packageMapping);
      }

      JavaXmlTypeMapping javaXmlTypeMapping = new JavaXmlTypeMapping(javaWsdlMapping);
      javaXmlTypeMapping.setJavaType(javaType.getName());
      javaXmlTypeMapping.setQNameScope(scope);
      javaXmlTypeMapping.setRootTypeQName(qname);

      // JSR-109 specifies an element name as a string, not a qname, so we need to strip the namespace
      Map<String, String> reversedNames = null;
      if (elementNames != null)
      {
         reversedNames = new HashMap<String, String>();
         for (String variable : elementNames.keySet())
            reversedNames.put(elementNames.get(variable).getLocalPart(), variable);
      }

      addVariableMappings(javaType, javaXmlTypeMapping, particles, reversedNames);

      javaWsdlMapping.addJavaXmlTypeMappings(javaXmlTypeMapping);
   }

   private void addVariableMappings(Class javaType, JavaXmlTypeMapping javaXmlTypeMapping, List<XSParticle> particles, Map<String, String> reversedNames)
   {
      for (XSParticle particle : particles)
      {
         XSTerm term = particle.getTerm();
         if (term.getType() == XSConstants.MODEL_GROUP)
         {
            XSModelGroup group = (XSModelGroup)term;
            XSObjectList list = group.getParticles();
            ArrayList<XSParticle> baseParticles = new ArrayList<XSParticle>();
            for (int i = 0; i < list.getLength(); i++)
               baseParticles.add((XSParticle)list.item(i));

            addVariableMappings(javaType, javaXmlTypeMapping, baseParticles, null);

            continue;
         }

         String name = term.getName();
         String variableName = name;
         if (reversedNames != null && reversedNames.get(name) != null)
            variableName = reversedNames.get(name);

         VariableMapping mapping = new VariableMapping(javaXmlTypeMapping);

         mapping.setJavaVariableName(variableName);
         mapping.setXmlElementName(name);
         mapping.setDataMember(utils.doesPublicFieldExist(javaType, variableName));

         if (isAlreadyMapped(javaXmlTypeMapping, mapping) == false)
         {
            javaXmlTypeMapping.addVariableMapping(mapping);
         }
      }
   }

   private boolean isAlreadyMapped(JavaXmlTypeMapping jxtm, VariableMapping vm)
   {
      String javaVariableName = vm.getJavaVariableName();
      String attributeName = vm.getXmlAttributeName();
      String elementName = vm.getXmlElementName();

      for (VariableMapping current : jxtm.getVariableMappings())
      {
         boolean matched = checkStringEquality(javaVariableName, current.getJavaVariableName());

         if (matched)
         {
            matched = checkStringEquality(attributeName, current.getXmlAttributeName());
         }

         if (matched)
         {
            matched = checkStringEquality(elementName, current.getXmlElementName());
         }

         if (matched)
         {
            return true;
         }
      }

      return false;
   }

   private List<XSParticle> getXSParticlesForPublicFields(String typeNamespace, Class javaType, Map<String, QName> elementNames)
   {
      List<XSParticle> particles = new ArrayList<XSParticle>();

      for (Field field : utils.getPublicFields(javaType))
      {
         // Skip collections
         if (Collection.class.isAssignableFrom(field.getType()))
         {
            log.warn("JAX-RPC does not allow collection types skipping field: " + javaType.getName() + "." + field.getName());
            continue;
         }

         JBossXSParticle particle = createFieldParticle(typeNamespace, field.getName(), field.getType(), elementNames);
         particles.add(particle);
      }

      return particles;
   }

   private JBossXSTypeDefinition handleArray(QName xmlType, Class javaType)
   {
      Class componentType = javaType.getComponentType();
      boolean isComponentArray = componentType.isArray();

      // Do not allow overrides i.e. byte[][] should not be base64Binary[]
      JBossXSTypeDefinition xst = (isComponentArray) ? handleArray(null, componentType) : generateType(null, componentType);

      String name;
      String namespace;

      if (xmlType != null)
      {
         name = xmlType.getLocalPart();
         namespace = getNamespace(componentType, xmlType.getNamespaceURI());
      }
      else
      {
         if (isComponentArray == false)
         {
            name = utils.getJustClassName(componentType.getName()) + ".Array";
            namespace = getNamespace(componentType, null);
         }
         else
         {
            name = xst.getName() + ".Array";
            namespace = getNamespace(componentType, xst.getNamespace());
         }
      }

      JBossXSParticle xsp = new JBossXSParticle();
      xsp.setTerm(sutils.createXSElementDeclaration("value", xst, !componentType.isPrimitive()));
      xsp.setMaxOccurs(-1);
      List<XSParticle> particles = new ArrayList<XSParticle>();
      particles.add(xsp);

      JBossXSComplexTypeDefinition complex = sutils.createXSComplexTypeDefinition(name, null, particles, namespace);
      xsModel.addXSComplexTypeDefinition(complex);
      typeMapping.register(javaType, new QName(namespace, name), null, null);

      return complex;
   }

   private List<XSParticle> introspectJavaProperties(String typeNamespace, Class javaType, Map<String, QName> elementNames) throws IntrospectionException
   {
      List<XSParticle> xsparts = new ArrayList<XSParticle>();

      Class superClass = javaType.getSuperclass();
      BeanInfo beanInfo = Introspector.getBeanInfo(javaType, superClass);
      PropertyDescriptor[] props = beanInfo.getPropertyDescriptors();
      int len = props != null ? props.length : 0;

      for (int i = 0; i < len && SessionBean.class.isAssignableFrom(javaType) == false; i++)
      {
         PropertyDescriptor prop = props[i];
         String fieldname = prop.getName();
         Class fieldType = prop.getPropertyType();

         if (prop instanceof IndexedPropertyDescriptor && fieldType == null)
         {
            log.warn("Indexed Properties without non-indexed accessors are not supported skipping: " + javaType.getName() + "." + fieldname);
            continue;
         }

         // Skip magic work around property used in ParameterWrapping
         if (fieldType.equals(ParameterWrapping.WrapperType.class))
            continue;

         // Skip collections
         if (Collection.class.isAssignableFrom(fieldType))
         {
            log.warn("JAX-RPC does not allow collection types skipping: " + javaType.getName() + "." + fieldname);
            continue;
         }

         //Check if the property conflicts with a public member variable
         if (utils.doesPublicFieldExist(javaType, fieldname))
            throw new WSException("Class " + javaType.getName() + " has a public field & property :" + fieldname);

         JBossXSParticle particle = createFieldParticle(typeNamespace, fieldname, fieldType, elementNames);
         xsparts.add(particle);
      }

      return xsparts;
   }

   private JBossXSParticle createFieldParticle(String typeNamespace, String fieldName, Class fieldType, Map<String, QName> elementNames)
   {
      // There should be some override mechanism, but we want byte[] to resolve to base64binary (the default)
      boolean isArray = fieldType.isArray() && fieldType != byte[].class;
      if (isArray)
         fieldType = fieldType.getComponentType();

      //  Do not allow byte[][] to become base64binary[]
      XSTypeDefinition xst = (isArray && fieldType.isArray()) ? handleArray(null, fieldType) : generateType(null, fieldType);

      String elementNamespace = null;
      if (elementNames != null)
      {
         QName name = elementNames.get(fieldName);
         if (name != null)
         {
            fieldName = name.getLocalPart();
            elementNamespace = name.getNamespaceURI();
            if (typeNamespace.equals(elementNamespace))
               elementNamespace = null;
         }
      }

      JBossXSElementDeclaration xsel;
      if (elementNamespace == null || elementNamespace.length() == 0)
      {
         xsel = sutils.createXSElementDeclaration(fieldName, xst, !fieldType.isPrimitive());
      }
      else
      {
         // If an element has a different namespace than the owning complexType,
         // then it must be created as a global element in another schema, and then
         // referenced in the complex type.
         xsel = sutils.createGlobalXSElementDeclaration(fieldName, xst, elementNamespace);
         xsModel.addXSElementDeclaration(xsel);
      }

      JBossXSParticle particle = sutils.createXSParticle(typeNamespace, isArray, xsel);
      return particle;
   }

   private void addBaseTypeParts(XSTypeDefinition baseType, List xsparts)
   {
      if (baseType == null)
         throw new IllegalArgumentException("Illegal Null Argument:baseType");
      if (XSTypeDefinition.COMPLEX_TYPE == baseType.getTypeCategory())
      {
         XSTypeDefinition btype = baseType.getBaseType();
         if (btype != null)
            addBaseTypeParts(btype, xsparts); //Recurse
         //Just add the particles from this basetype as a ModelGroup sequence
         XSParticle part = ((XSComplexTypeDefinition)baseType).getParticle();
         XSTerm term = part.getTerm();
         if (term instanceof XSModelGroup)
         {
            JBossXSParticle p = new JBossXSParticle();
            p.setTerm(term);
            xsparts.add(p);
         }
      }
   }

   private String getNamespace(Class javaType, String defaultNS)
   {
      String retNS = defaultNS;
      if (javaType.isPrimitive() && retNS == null)
      {
         retNS = Constants.NS_JBOSSWS_URI + "/primitives";
      }
      else
      {
         while (javaType.isArray())
         {
            javaType = javaType.getComponentType();
         }

         Package javaPackage = javaType.getPackage();
         if (javaPackage != null)
         {
            String packageName = javaPackage.getName();
            String ns = packageNamespaceMap.get(packageName);
            if (ns != null)
            {
               retNS = ns;
            }
            else if (retNS == null)
            {
               retNS = utils.getTypeNamespace(packageName);
            }

            allocatePrefix(retNS);
         }
         else if (retNS == null)
         {
            throw new WSException("Cannot determine namespace, Class had no package");
         }
      }
      return retNS;
   }

   private JBossXSComplexTypeDefinition getComplexTypeForJavaException(QName xmlType, Class javaType)
   {
      if (!Exception.class.isAssignableFrom(javaType))
         throw new IllegalArgumentException("Type is not an excpetion");
      if (RuntimeException.class.isAssignableFrom(javaType))
         throw new IllegalArgumentException("JAX-RPC violation, the following exception extends RuntimeException: " + javaType.getName());

      String name;
      String namespace;
      if (xmlType != null)
      {
         namespace = getNamespace(javaType, xmlType.getNamespaceURI());
         name = xmlType.getLocalPart();
      }
      else
      {
         namespace = getNamespace(javaType, null);
         name = WSDLUtils.getJustClassName(javaType);
      }

      List<XSParticle> particles = new ArrayList<XSParticle>(0);
      List<Class> types = new ArrayList<Class>(0);
      JBossXSComplexTypeDefinition complexType = new JBossXSComplexTypeDefinition();
      complexType.setName(name);
      complexType.setNamespace(namespace);

      xsModel.addXSComplexTypeDefinition(complexType);
      xsModel.addXSElementDeclaration(sutils.createGlobalXSElementDeclaration(name, complexType, namespace));
      typeMapping.register(javaType, new QName(namespace, name), null, null);
      generateExceptionParticles(namespace, javaType, types, particles);

      registerJavaTypeMapping(new QName(namespace, name), javaType, "complexType", particles, null);

      Class superClass = javaType.getSuperclass();
      if (!Exception.class.equals(superClass) || Throwable.class.equals(superClass))
      {
         JBossXSTypeDefinition baseType = generateType(null, superClass);
         complexType.setBaseType(baseType);
      }


      boolean found = hasConstructor(javaType, types);
      boolean noarg = found && types.size() == 0;

      if (!found || noarg)
      {
         // Look for a message constructor if a matching constructor could not be found.
         // We also prefer message constructors over a noarg constructor
         ArrayList<Class> newTypes = new ArrayList<Class>(types);
         newTypes.add(0, String.class);
         found = hasConstructor(javaType, newTypes);
         if (found)
         {
            insertBaseParticle(particles, "message", String.class, namespace);
         }
         else
         {
            // If we have a default (0 argument) constructor, fall back to it
            if (!noarg)
               throw new IllegalArgumentException("Could not locate a constructor with the following types: " + javaType + ' ' + types);
         }
      }

      complexType.setParticle(createGroupParticle(namespace, particles));

      return complexType;
   }

   private void insertBaseParticle(List<XSParticle> particles, String name, Class type, String targetNS)
   {
      if (particles.size() == 0)
      {
         particles.add(getXSParticle(name, type, targetNS));
         return;
      }

      XSParticle particle = particles.get(0);
      XSTerm term = particle.getTerm();
      if (term.getType() == XSConstants.MODEL_GROUP)
      {
         JBossXSModelGroup group = (JBossXSModelGroup)term;
         XSObjectList list = group.getParticles();
         ArrayList<XSParticle> baseParticles = new ArrayList<XSParticle>();
         for (int i = 0; i < list.getLength(); i++)
            baseParticles.add((XSParticle)list.item(i));

         insertBaseParticle(baseParticles, name, type, targetNS);

         if (baseParticles.size() > list.getLength())
            group.setParticles(baseParticles);
      }
      else
      {
         particles.add(0, getXSParticle(name, type, targetNS));
      }
   }

   private boolean hasConstructor(Class javaType, List<Class> types)
   {
      boolean found = true;

      try
      {
         javaType.getConstructor(types.toArray(new Class[0]));
      }
      catch (NoSuchMethodException e)
      {
         found = false;
      }

      return found;
   }

   private void generateExceptionParticles(String typeNamespace, Class javaType, List<Class> types, List<XSParticle> particles)
   {
      /*
       * JAX-RPC 1.1 states that properties of an exception are determined by
       * the combination of a public getter, and a matching constructor. Since
       * Reflection can not tell us parameter names, the only way to locate the
       * propper constructor is by type. Since there can be multiple properties
       * with the same type, we must somehow establish a proper order of the
       * constructor. Code declaration is not a possibility since this
       * information is also not retained. This leaves us with only one option,
       * sorting.
       *
       * So the matching constructor must be sorted by two indicies in the
       * following order: 1. declaring class, super type first 2. property name
       * in ascending order
       */
      if (Exception.class.equals(javaType))
         return;

      Class superClass = javaType.getSuperclass();
      if (!Exception.class.equals(superClass))
      {
         List<XSParticle> superParticles = new ArrayList<XSParticle>(0);
         generateExceptionParticles(typeNamespace, superClass, types, superParticles);
         particles.add(createGroupParticle(typeNamespace, superParticles));
      }

      TreeMap<String, Class> sortedGetters = new TreeMap<String, Class>();
      for (Method method : javaType.getDeclaredMethods())
      {
         int modifiers = method.getModifiers();
         if ((!Modifier.isPublic(modifiers)) || Modifier.isStatic(modifiers))
            continue;

         String name = method.getName();
         Class returnType = method.getReturnType();
         if (name.startsWith("get") && returnType != void.class)
         {
            name = Introspector.decapitalize(name.substring(3));
            sortedGetters.put(name, returnType);
         }
      }

      for (String name : sortedGetters.keySet())
      {
         Class type = sortedGetters.get(name);
         types.add(type);
         JBossXSParticle particle = createFieldParticle(typeNamespace, name, type, null);
         particles.add(particle);
      }
   }

   private JBossXSParticle createGroupParticle(String targetNS, List<XSParticle> memberParticles)
   {
      JBossXSParticle groupParticle = new JBossXSParticle(null, targetNS);
      JBossXSModelGroup group = new JBossXSModelGroup();
      group.setParticles(memberParticles, false);
      groupParticle.setTerm(group);

      return groupParticle;
   }

   private JBossXSParticle getXSParticle(String name, Class fieldType, String targetNS)
   {
      XSTypeDefinition xstype = null;
      boolean isArray = fieldType.isArray();

      if (isArray)
         fieldType = fieldType.getComponentType();

      xstype = this.generateType(null, fieldType);
      boolean isNillable = !fieldType.isPrimitive();
      JBossXSElementDeclaration xsel = (JBossXSElementDeclaration)sutils.createXSElementDeclaration(name, xstype, isNillable);

      return sutils.createXSParticle(targetNS, isArray, xsel);
   }

   private boolean checkStringEquality(String str1, String str2)
   {
      if (str1 == null && str2 == null)
         return true;
      if (str1 == null && str2 != null)
         return false;
      if (str1 != null && str2 == null)
         return false;
      return str1.equals(str2);
   }
}