/*
* 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.core.binding;

// $Id: TypeMappingImpl.java 3959 2007-07-20 14:44:19Z heiko.braun@jboss.com $

import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URI;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.xml.namespace.QName;
import javax.xml.rpc.encoding.DeserializerFactory;
import javax.xml.rpc.encoding.SerializerFactory;
import javax.xml.rpc.encoding.TypeMapping;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.core.jaxrpc.binding.Base64DeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.Base64SerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.CalendarDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.CalendarSerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.DateDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.DateSerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.HexDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.HexSerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.QNameDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.QNameSerializerFactory;
import org.jboss.ws.core.jaxrpc.binding.SimpleDeserializerFactory;
import org.jboss.ws.core.jaxrpc.binding.SimpleSerializerFactory;
import org.jboss.ws.core.utils.HashCodeUtil;
import org.jboss.wsf.common.JavaUtils;

/**
 * This is the representation of a type mapping.
 * This TypeMapping implementation supports the literal encoding style.
 *
 * The TypeMapping instance maintains a tuple of the type
 * {XML typeQName, Java Class, SerializerFactory, DeserializerFactory}.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 10-Oct-2004
 */
public abstract class TypeMappingImpl implements TypeMapping
{
   // provide logging
   private static final Logger log = Logger.getLogger(TypeMappingImpl.class);

   // Map<KeyPair,FactoryPair>
   private Map<KeyPair, FactoryPair> tupleMap = new LinkedHashMap<KeyPair, FactoryPair>();

   private Map<Integer, List<KeyPair>> keyPairCache = new ConcurrentHashMap<Integer, List<KeyPair>>();

   /**
    * Gets the DeserializerFactory registered for the specified pair of Java type and XML data type.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @return Registered DeserializerFactory or null if there is no registered factory
    */
   public DeserializerFactory getDeserializer(Class javaType, QName xmlType)
   {
      FactoryPair fPair = getFactoryPair(new IQName(xmlType), javaType);
      return (fPair != null ? fPair.getDeserializerFactory() : null);
   }

   /**
    * Gets the SerializerFactory registered for the specified pair of Java type and XML data type.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @return Registered SerializerFactory or null if there is no registered factory
    */
   public SerializerFactory getSerializer(Class javaType, QName xmlType)
   {
      FactoryPair fPair = getFactoryPair(new IQName(xmlType), javaType);
      return (fPair != null ? fPair.getSerializerFactory() : null);
   }

   /**
    * Returns the encodingStyle URIs (as String[]) supported by this TypeMapping instance.
    * A TypeMapping that contains only encoding style independent serializers and deserializers
    * returns null from this method.
    *
    * @return Array of encodingStyle URIs for the supported encoding styles
    */
   public abstract String[] getSupportedEncodings();

   /**
    * Sets the encodingStyle URIs supported by this TypeMapping instance. A TypeMapping that contains only encoding
    * independent serializers and deserializers requires null as the parameter for this method.
    *
    * @param encodingStyleURIs Array of encodingStyle URIs for the supported encoding styles
    */
   public abstract void setSupportedEncodings(String[] encodingStyleURIs);

   /**
    * Checks whether or not type mapping between specified XML type and Java type is registered.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @return boolean; true if type mapping between the specified XML type and Java type is registered; otherwise false
    */
   public boolean isRegistered(Class javaType, QName xmlType)
   {
      return getFactoryPair(new IQName(xmlType), javaType) != null;
   }

   /**
    * Registers SerializerFactory and DeserializerFactory for a specific type mapping between an XML type and Java type.
    * This method replaces any existing registered SerializerFactory DeserializerFactory instances.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @param sf SerializerFactory
    * @param df DeserializerFactory
    * @throws javax.xml.rpc.JAXRPCException If any error during the registration
    */
   public void register(Class javaType, QName xmlType, SerializerFactory sf, DeserializerFactory df)
   {
      if (log.isTraceEnabled())
         log.trace("register: TypeMappingImpl@"  + hashCode() + " [xmlType=" + xmlType + ",javaType=" + javaType.getName() + ",sf=" + sf + ",df=" + df + "]");
      
      registerInternal(javaType, new IQName(xmlType), sf, df);
      keyPairCache.clear();
   }

   private void registerInternal(Class javaType, IQName xmlType, SerializerFactory sf, DeserializerFactory df)
   {
      if (javaType == null)
         throw new IllegalArgumentException("javaType cannot be null for: " + xmlType);
      if (xmlType == null)
         throw new IllegalArgumentException("xmlType cannot be null for: " + javaType);

      KeyPair kPair = new KeyPair(xmlType, javaType);
      FactoryPair fPair = new FactoryPair(sf, df);
      tupleMap.put(kPair, fPair);
   }

   /**
    * Removes the DeserializerFactory registered for the specified pair of Java type and XML data type.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @throws javax.xml.rpc.JAXRPCException If there is error in removing the registered DeserializerFactory
    */
   public void removeDeserializer(Class javaType, QName xmlType)
   {
      FactoryPair fPair = getFactoryPair(new IQName(xmlType), javaType);
      if (fPair != null)
         fPair.setDeserializerFactory(null);
   }

   /**
    * Removes the SerializerFactory registered for the specified pair of Java type and XML data type.
    * @param javaType Class of the Java type
    * @param xmlType QName of the XML type
    * @throws javax.xml.rpc.JAXRPCException If there is error in removing the registered SerializerFactory
    */
   public void removeSerializer(Class javaType, QName xmlType)
   {
      FactoryPair fPair = getFactoryPair(new IQName(xmlType), javaType);
      if (fPair != null)
         fPair.setSerializerFactory(null);
   }

   /** Get the list of registered XML types */
   public List<QName> getRegisteredXmlTypes()
   {
      List<QName> types = new ArrayList<QName>();
      for (KeyPair keyPair : getKeyPairs(null, null))
      {
         types.add(keyPair.getXmlType().toQName());
      }
      return types;
   }

   /** Get the list of registered Java types */
   public List<Class> getRegisteredJavaTypes()
   {
      List<Class> types = new ArrayList<Class>();
      for (KeyPair keyPair : getKeyPairs(null, null))
      {
         types.add(keyPair.getJavaType());
      }
      return types;
   }

   /** Get the Class that was registered last for this xmlType */
   public Class getJavaType(QName xmlType)
   {
      Class javaType = null;

      List keyPairList = getKeyPairs(new IQName(xmlType), null);
      int size = keyPairList.size();
      if (size > 0)
      {
         KeyPair kPair = (KeyPair)keyPairList.get(size - 1);
         javaType = kPair.getJavaType();
      }

      return javaType;
   }

   /**
    * Get all of the Classes registered for this xmlType.
    */
   public List<Class> getJavaTypes(QName xmlType)
   {
      List<KeyPair> keyPairList = getKeyPairs( new IQName(xmlType), null);
      List<Class> classes = new ArrayList<Class>(keyPairList.size());

      for (KeyPair current : keyPairList)
      {
         classes.add(current.getJavaType());
      }

      return classes;
   }

   /**
    * Get the Class that was registered last for this xmlType
    * If there are two Java Types registered for the xmlType
    * return the primitive type rather than the wrapper,
    * if available
    */
   public Class getJavaType(QName xmlType,boolean getPrimitive)
   {
      //Lets get the primitive type if available
      Class javaType = null;

      List keyPairList = getKeyPairs(new IQName(xmlType), null);
      int size = keyPairList.size();
      if (size == 2 && getPrimitive)
      {
         KeyPair kPair1 = (KeyPair)keyPairList.get(0);
         Class javaType1 = kPair1.getJavaType();
         KeyPair kPair2 = (KeyPair)keyPairList.get(1);
         Class javaType2 = kPair2.getJavaType();
         if(javaType2.isPrimitive() && !javaType1.isPrimitive())
            javaType =  javaType2;
         else
         if(javaType1.isPrimitive() && !javaType2.isPrimitive())
            javaType =  javaType1;
         else
            javaType = javaType2; //Fallback on the most latest
      }
      else
         return getJavaType(xmlType);

      return javaType;
   }

   /** Get the Class name that was registered last for this xmlType */
   public String getJavaTypeName(QName xmlType)
   {
      Class javaType = getJavaType(xmlType);
      return (javaType != null ? javaType.getName() : null);
   }

   /** Get the QName that was registered last for this javaType */
   public QName getXMLType(Class javaType)
   {
      QName xmlType = null;

      List keyPairList = getKeyPairs(null, javaType);
      int size = keyPairList.size();
      if (size > 0)
      {
         KeyPair kPair = (KeyPair)keyPairList.get(size - 1);
         xmlType = kPair.getXmlType().toQName();
      }

      return xmlType;
   }

   /** Get the QNames that was registered last for this javaType */
   public List<QName> getXMLTypes(Class javaType)
   {
      List<QName> xmlTypes = new ArrayList<QName>();

      for (KeyPair kPair : getKeyPairs(null, javaType))
      {
         xmlTypes.add(kPair.getXmlType().toQName());
      }
      return xmlTypes;
   }

   /**
    * Get the QName that was registered last for this javaType
    * @param javaType class for which XML Type is needed
    * @param tryAssignable  If the xmlType is not registered for javaType
    *                                           should a base class type be checked?
    *
    */
   public QName getXMLType(Class javaType, boolean tryAssignable)
   {
      if(tryAssignable) return getXMLType(javaType);

      QName xmlType = null;

      List keyPairList = getKeyPairs(null, javaType, tryAssignable);
      int size = keyPairList.size();
      if (size > 0)
      {
         KeyPair kPair = (KeyPair)keyPairList.get(size - 1);
         xmlType = kPair.getXmlType().toQName();
      }

      return xmlType;
   }

   /**
    * Get the serializer/deserializer factory pair for the given xmlType, javaType
    * Both xmlType, javaType may be null. In that case, this implementation still
    * returns a FactoryPair if there is only one possible match.
    *
    * @param xmlType can be null
    * @param javaType can be null
    */
   private List<KeyPair> getKeyPairs(IQName xmlType, Class javaType)
   {
      Integer cacheId = cacheIdFor(javaType, xmlType);

      List<KeyPair> keyPairList = keyPairCache.get(cacheId);
      if(null == keyPairList)
      {
         keyPairList = getKeyPairsInternal(xmlType, javaType);
         keyPairCache.put(cacheId, keyPairList);
      }

      return keyPairList;
   }

   private Integer cacheIdFor(Class javaType, IQName xmlType) {
      int result = HashCodeUtil.SEED;
      int nullHash = HashCodeUtil.hash(result, "null");
      result = javaType!= null ? HashCodeUtil.hash(result, javaType.getName()) : HashCodeUtil.hash(result, nullHash);
      result = xmlType!= null ? HashCodeUtil.hash(result, xmlType.hashCode()): HashCodeUtil.hash(result, nullHash);
      return new Integer(result);
   }

   private List<KeyPair> getKeyPairsInternal(IQName xmlType, Class javaType)
   {
      List<KeyPair> keyPairList = new ArrayList<KeyPair>();

      // Getting the exact matching pair
      if (xmlType != null && javaType != null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (xmlType.equals(entry.getXmlType()) && entry.getJavaType() == javaType)
            {
               keyPairList.add(entry);
            }
         }
         // No exact match, try assignable
         if (keyPairList.size() == 0)
         {
            for (KeyPair entry : tupleMap.keySet())
            {
               if (xmlType.equals(entry.getXmlType()) && JavaUtils.isAssignableFrom(entry.getJavaType(), javaType))
               {
                  keyPairList.add(entry);
               }
            }
         }
      }

      // Getting the pair for a given xmlType
      else if (xmlType != null && javaType == null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (xmlType.equals(entry.getXmlType()))
            {
               keyPairList.add(entry);
            }
         }
      }

      // Getting the pair for a given javaType
      else if (xmlType == null && javaType != null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (entry.getJavaType() == javaType)
            {
               keyPairList.add(entry);
            }
         }
         // No exact match, try assignable
         if (keyPairList.size() == 0)
         {
            for (KeyPair entry : tupleMap.keySet())
            {
               if (JavaUtils.isAssignableFrom(entry.getJavaType(), javaType))
               {
                  keyPairList.add(entry);
               }
            }
         }
      }

      // Getting the all pairs
      else if (xmlType == null && javaType == null)
      {
         keyPairList.addAll(tupleMap.keySet());
      }

      return keyPairList;
   }

    private List<KeyPair> getKeyPairs(IQName xmlType, Class javaType, boolean tryAssignable)
    {
      Integer cacheId = cacheIdFor(javaType, xmlType);

      List<KeyPair> keyPairList = keyPairCache.get(cacheId);
      if(null == keyPairList)
      {
         keyPairList = getKeyPairsInternal(xmlType, javaType, tryAssignable);
         keyPairCache.put(cacheId, keyPairList);
      }

      return keyPairList;
    }

   /**
    * Get the serializer/deserializer factory pair for the given xmlType, javaType
    * Both xmlType, javaType may be null. In that case, this implementation still
    * returns a FactoryPair if there is only one possible match.
    * <br>Note: This method does not try for the base class, if no keypair exists for the
    * javaType in question.
    */
   private List<KeyPair> getKeyPairsInternal(IQName xmlType, Class javaType, boolean tryAssignable)
   {
      if(tryAssignable) return getKeyPairs( xmlType, javaType );

      List<KeyPair> keyPairList = new ArrayList<KeyPair>();

      // Getting the exact matching pair
      if (xmlType != null && javaType != null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (xmlType.equals(entry.getXmlType()) && entry.getJavaType() == javaType)
            {
               keyPairList.add(entry);
            }
         }
      }

      // Getting the pair for a given xmlType
      else if (xmlType != null && javaType == null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (xmlType.equals(entry.getXmlType()))
            {
               keyPairList.add(entry);
            }
         }
      }

      // Getting the pair for a given javaType
      else if (xmlType == null && javaType != null)
      {
         for (KeyPair entry : tupleMap.keySet())
         {
            if (entry.getJavaType() == javaType)
            {
               keyPairList.add(entry);
            }
         }
      }

      // Getting the all pairs
      else if (xmlType == null && javaType == null)
      {
         keyPairList.addAll(tupleMap.keySet());
      }

      return keyPairList;
   }

   /**
    * Get the serializer/deserializer factory pair for the given xmlType, javaType
    * Both xmlType, javaType may be null. In that case, this implementation still
    * returns a FactoryPair that was last registered
    */
   private FactoryPair getFactoryPair(IQName xmlType, Class javaType)
   {
      FactoryPair fPair = null;

      List<KeyPair> keyPairList = getKeyPairs(xmlType, javaType);
      int size = keyPairList.size();
      if (size > 0)
      {
         KeyPair kPair = keyPairList.get(size - 1);
         fPair = (FactoryPair)tupleMap.get(kPair);
      }

      return fPair;
   }

   protected void registerStandardLiteralTypes()
   {
      register(BigDecimal.class, Constants.TYPE_LITERAL_DECIMAL, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(BigInteger.class, Constants.TYPE_LITERAL_POSITIVEINTEGER, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(BigInteger.class, Constants.TYPE_LITERAL_NEGATIVEINTEGER, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(BigInteger.class, Constants.TYPE_LITERAL_NONPOSITIVEINTEGER, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(BigInteger.class, Constants.TYPE_LITERAL_NONNEGATIVEINTEGER, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(BigInteger.class, Constants.TYPE_LITERAL_UNSIGNEDLONG, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(BigInteger.class, Constants.TYPE_LITERAL_INTEGER, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(Date.class, Constants.TYPE_LITERAL_DATETIME, new DateSerializerFactory(), new DateDeserializerFactory());

      register(Calendar.class, Constants.TYPE_LITERAL_DATE, new CalendarSerializerFactory(), new CalendarDeserializerFactory());
      register(Calendar.class, Constants.TYPE_LITERAL_TIME, new CalendarSerializerFactory(), new CalendarDeserializerFactory());
      register(Calendar.class, Constants.TYPE_LITERAL_DATETIME, new CalendarSerializerFactory(), new CalendarDeserializerFactory());

      register(QName.class, Constants.TYPE_LITERAL_QNAME, new QNameSerializerFactory(), new QNameDeserializerFactory());

      register(String.class, Constants.TYPE_LITERAL_ANYSIMPLETYPE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_DURATION, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_GDAY, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_GMONTH, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_GMONTHDAY, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_GYEAR, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_GYEARMONTH, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_ID, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_LANGUAGE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_NAME, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_NCNAME, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_NMTOKEN, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_NORMALIZEDSTRING, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_TOKEN, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(String.class, Constants.TYPE_LITERAL_STRING, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(String[].class, Constants.TYPE_LITERAL_NMTOKENS, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(URI.class, Constants.TYPE_LITERAL_ANYURI, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(boolean.class, Constants.TYPE_LITERAL_BOOLEAN, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Boolean.class, Constants.TYPE_LITERAL_BOOLEAN, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(byte.class, Constants.TYPE_LITERAL_BYTE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Byte.class, Constants.TYPE_LITERAL_BYTE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(Byte[].class, Constants.TYPE_LITERAL_HEXBINARY, new HexSerializerFactory(), new HexDeserializerFactory());
      register(byte[].class, Constants.TYPE_LITERAL_HEXBINARY, new HexSerializerFactory(), new HexDeserializerFactory());

      register(Byte[].class, Constants.TYPE_LITERAL_BASE64BINARY, new Base64SerializerFactory(), new Base64DeserializerFactory());
      register(byte[].class, Constants.TYPE_LITERAL_BASE64BINARY, new Base64SerializerFactory(), new Base64DeserializerFactory());

      register(double.class, Constants.TYPE_LITERAL_DOUBLE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Double.class, Constants.TYPE_LITERAL_DOUBLE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(float.class, Constants.TYPE_LITERAL_FLOAT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Float.class, Constants.TYPE_LITERAL_FLOAT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(int.class, Constants.TYPE_LITERAL_UNSIGNEDSHORT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Integer.class, Constants.TYPE_LITERAL_UNSIGNEDSHORT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(int.class, Constants.TYPE_LITERAL_INT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Integer.class, Constants.TYPE_LITERAL_INT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(long.class, Constants.TYPE_LITERAL_UNSIGNEDINT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Long.class, Constants.TYPE_LITERAL_UNSIGNEDINT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(long.class, Constants.TYPE_LITERAL_LONG, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Long.class, Constants.TYPE_LITERAL_LONG, new SimpleSerializerFactory(), new SimpleDeserializerFactory());

      register(short.class, Constants.TYPE_LITERAL_UNSIGNEDBYTE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Short.class, Constants.TYPE_LITERAL_UNSIGNEDBYTE, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(short.class, Constants.TYPE_LITERAL_SHORT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
      register(Short.class, Constants.TYPE_LITERAL_SHORT, new SimpleSerializerFactory(), new SimpleDeserializerFactory());
   }

   /** A tuple of the type {XML typeQName, Java Class, SerializerFactory, DeserializerFactory}.
    */
   private static class KeyPair
   {
      private IQName xmlType;
      private Class javaType;

      public KeyPair(IQName xmlType, Class javaType)
      {
         this.javaType = javaType;
         this.xmlType = xmlType;
      }

      public Class getJavaType()
      {
         return javaType;
      }

      public IQName getXmlType()
      {
         return xmlType;
      }

      public boolean equals(Object o)
      {
         if (this == o) return true;
         if (!(o instanceof KeyPair)) return false;

         final KeyPair keyPair = (KeyPair)o;

         if (!javaType.equals(keyPair.javaType)) return false;
         if (!xmlType.equals(keyPair.xmlType)) return false;

         return true;
      }

      public int hashCode()
      {
         int result;
         result = xmlType.hashCode();
         result = 29 * result + javaType.hashCode();
         return result;
      }

      public String toString()
      {
         return "[xmlType=" + xmlType + ",javaType=" + javaType.getName() + "]";
      }
   }

   /** A tuple of the type {XML typeQName, Java Class, SerializerFactory, DeserializerFactory}.
    */
   public static class FactoryPair
   {
      private SerializerFactory serializerFactory;
      private DeserializerFactory deserializerFactory;

      FactoryPair(SerializerFactory sf, DeserializerFactory df)
      {
         this.deserializerFactory = df;
         this.serializerFactory = sf;
      }

      public DeserializerFactory getDeserializerFactory()
      {
         return deserializerFactory;
      }

      public SerializerFactory getSerializerFactory()
      {
         return serializerFactory;
      }

      public void setDeserializerFactory(DeserializerFactory df)
      {
         this.deserializerFactory = df;
      }

      public void setSerializerFactory(SerializerFactory sf)
      {
         this.serializerFactory = sf;
      }
   }

   /**
    * A duck typed QName that relies on internalized Strings.<p>
    * Taken from the {@link javax.xml.namespace.QName} docs:<br>
    * The value of a QName contains a Namespace URI, local part and prefix.
    * The prefix is included in QName to retain lexical information when present in an XML input source.
    * The prefix is NOT used in QName.equals(Object) or to compute the QName.hashCode().
    * Equality and the hash code are defined using only the Namespace URI and local part.
    * If not specified, the Namespace URI is set to "" (the empty string).
    * If not specified, the prefix is set to "" (the empty string).
    */
   private final class IQName
   {
      public String namespace;
      public String localPart;
      public String prefix;
      public int hash;

      public IQName(QName name)
      {
         namespace = name.getNamespaceURI() != null ? name.getNamespaceURI().intern() : "".intern();
         localPart = name.getLocalPart() != null ? name.getLocalPart().intern() : "".intern();
         prefix = name.getPrefix() != null ? name.getPrefix().intern() : "".intern();
         hash = name.hashCode();
      }

      public boolean equals(Object object) {
         if(!(object instanceof IQName))
            throw new IllegalArgumentException("Cannot compare IQName to " + object);

         IQName iqn = (IQName)object;
         return (iqn.namespace == this.namespace && iqn.localPart == this.localPart);
      }

      public QName toQName()
      {
         QName qname;

         if(null == namespace)
            qname = new QName(localPart);
         else if(null == prefix)
            qname = new QName(namespace, localPart);
         else
            qname = new QName(namespace, localPart, prefix);

         return qname;
      }

      /**
       * This implementation currently represents a QName as: "{" + Namespace URI + "}" + local part.
       * If the Namespace URI .equals(""), only the local part is returned.
       */
      public String toString() {
         String ns = "".equals(namespace) ? namespace : "{"+namespace+"}";
         return ns+localPart;
      }

      public int hashCode()
      {
         return this.hash;
      }
   }
}
