/*
 * Copyright 2001-2004 The Apache Software Foundation.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.axis.encoding;

import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Stack;

import javax.xml.namespace.QName;
import javax.xml.rpc.JAXRPCException;
import javax.xml.rpc.holders.QNameHolder;

import org.apache.axis.AxisEngine;
import org.apache.axis.AxisProperties;
import org.apache.axis.Constants;
import org.apache.axis.Message;
import org.apache.axis.MessageContext;
import org.apache.axis.attachments.Attachments;
import org.apache.axis.client.Call;
import org.apache.axis.components.encoding.XMLEncoder;
import org.apache.axis.components.encoding.XMLEncoderFactory;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.constants.Use;
import org.apache.axis.description.OperationDesc;
import org.apache.axis.description.TypeDesc;
import org.apache.axis.encoding.ser.ArraySerializer;
import org.apache.axis.encoding.ser.BaseSerializerFactory;
import org.apache.axis.encoding.ser.SimpleListSerializerFactory;
import org.apache.axis.handlers.soap.SOAPService;
import org.apache.axis.schema.SchemaVersion;
import org.apache.axis.soap.SOAPConstants;
import org.apache.axis.types.HexBinary;
import org.apache.axis.utils.IDKey;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Mapping;
import org.apache.axis.utils.Messages;
import org.apache.axis.utils.NSStack;
import org.apache.axis.utils.XMLUtils;
import org.apache.axis.utils.cache.MethodCache;
import org.apache.axis.wsdl.symbolTable.SchemaUtils;
import org.apache.axis.wsdl.symbolTable.SymbolTable;
import org.apache.axis.wsdl.symbolTable.Utils;
import org.apache.commons.logging.Log;
import org.w3c.dom.Attr;
import org.w3c.dom.CDATASection;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributesImpl;

/** Manage a serialization, including keeping track of namespace mappings
 * and element stacks.
 *
 * @author Glen Daniels (gdaniels@apache.org)
 * @author Rich Scheuerle <scheu@us.ibm.com>
 */
public class SerializationContext implements javax.xml.rpc.encoding.SerializationContext
{
    protected static Log log =
            LogFactory.getLog(SerializationContext.class.getName());

    // invariant member variable to track low-level logging requirements
    // we cache this once per instance lifecycle to avoid repeated lookups
    // in heavily used code.
    private final boolean debugEnabled = log.isDebugEnabled();

    private NSStack nsStack = null;
    private boolean writingStartTag = false;
    private boolean onlyXML = true;
    private int indent=0;
    private Stack elementStack = new Stack();
    private Writer writer;
    private int lastPrefixIndex = 1;
    private MessageContext msgContext;
    private QName currentXMLType;
    /** The item QName if we're serializing a literal array... */
    private QName itemQName;
    /** The item type if we're serializing a literal array... */
    private QName itemType;

    /** The SOAP context we're using */
    private SOAPConstants soapConstants = SOAPConstants.SOAP11_CONSTANTS;

    private static QName multirefQName = new QName("","multiRef");
    private static Class[] SERIALIZER_CLASSES =
            new Class[] {String.class, Class.class, QName.class};
    private static final String SERIALIZER_METHOD = "getSerializer";

    /**
     * Should I write out objects as multi-refs?
     *
     * !!! For now, this is an all-or-nothing flag.  Either ALL objects will
     * be written in-place as hrefs with the full serialization at the end
     * of the body, or we'll write everything inline (potentially repeating
     * serializations of identical objects).
     */
    private boolean doMultiRefs = false;
    
    /**
     * Should I disable the pretty xml completely.
     */ 
    private boolean disablePrettyXML = false;


    /**
     * Should I disable the namespace prefix optimization.
     */ 
    private boolean enableNamespacePrefixOptimization = false;

    /**
     * current setting for pretty
     */ 
    private boolean pretty = false;
    
    /**
     * Should I send an XML declaration?
     */
    private boolean sendXMLDecl = true;

    /**
     * Should I send xsi:type attributes?  By default, yes.
     */
    private boolean sendXSIType = true;

    /**
     * Send an element with an xsi:nil="true" attribute for null
     * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE).
     */
    private Boolean sendNull = Boolean.TRUE;

    /**
     * A place to hold objects we cache for multi-ref serialization, and
     * remember the IDs we assigned them.
     */
    private HashMap multiRefValues = null;
    private int multiRefIndex = -1;
    private boolean noNamespaceMappings = true;
    private QName writeXMLType;
    private XMLEncoder encoder = null;
    
    /** The flag whether the XML decl should be written */
    protected boolean startOfDocument = true;
 
    /** The encoding to serialize */
    private String encoding = XMLEncoderFactory.DEFAULT_ENCODING;

    class MultiRefItem {
        String id;
        QName xmlType;
        Boolean sendType;
        Object value;
        MultiRefItem(String id,
                     QName xmlType,
                     Boolean sendType, Object value) {
            this.id = id;
            this.xmlType = xmlType;
            this.sendType = sendType;
            this.value = value;
        }

    }
    /**
     * These three variables are necessary to process
     * multi-level object graphs for multi-ref serialization.
     * While writing out nested multi-ref objects (via outputMultiRef), we
     * will fill the secondLevelObjects vector
     * with any new objects encountered.
     * The outputMultiRefsFlag indicates whether we are currently within the
     * outputMultiRef() method (so that serialization() knows to update the
     * secondLevelObjects vector).
     * The forceSer variable is the trigger to force actual serialization of the indicated object.
     */
    private HashSet secondLevelObjects = null;
    private Object forceSer = null;
    private boolean outputMultiRefsFlag = false;

    /**
     * Which schema version are we using?
     */
    SchemaVersion schemaVersion = SchemaVersion.SCHEMA_2001;

    /**
     * A list of particular namespace -> prefix mappings we should prefer.
     * See getPrefixForURI() below.
     */
    HashMap preferredPrefixes = new HashMap();

    /**
     * Construct SerializationContext with associated writer
     * @param writer java.io.Writer
     */
    public SerializationContext(Writer writer)
    {
        this.writer = writer;
        initialize();
    }

    private void initialize() {
        // These are the preferred prefixes we'll use instead of the "ns1"
        // style defaults.  MAKE SURE soapConstants IS SET CORRECTLY FIRST!
        preferredPrefixes.put(soapConstants.getEncodingURI(),
                              Constants.NS_PREFIX_SOAP_ENC);
        preferredPrefixes.put(Constants.NS_URI_XML,
                              Constants.NS_PREFIX_XML);
        preferredPrefixes.put(schemaVersion.getXsdURI(),
                              Constants.NS_PREFIX_SCHEMA_XSD);
        preferredPrefixes.put(schemaVersion.getXsiURI(),
                              Constants.NS_PREFIX_SCHEMA_XSI);
        preferredPrefixes.put(soapConstants.getEnvelopeURI(),
                              Constants.NS_PREFIX_SOAP_ENV);
        nsStack = new NSStack(enableNamespacePrefixOptimization);
    }


    /**
     * Construct SerializationContext with associated writer and MessageContext
     * @param writer java.io.Writer
     * @param msgContext is the MessageContext
     */
    public SerializationContext(Writer writer, MessageContext msgContext)
    {
        this.writer = writer;
        this.msgContext = msgContext;

        if ( msgContext != null ) {
            soapConstants = msgContext.getSOAPConstants();

            // Use whatever schema is associated with this MC
            schemaVersion = msgContext.getSchemaVersion();

            Boolean shouldSendDecl = (Boolean)msgContext.getProperty(
                                                  AxisEngine.PROP_XML_DECL);
            if (shouldSendDecl != null)
                sendXMLDecl = shouldSendDecl.booleanValue();

            Boolean shouldSendMultiRefs =
                  (Boolean)msgContext.getProperty(AxisEngine.PROP_DOMULTIREFS);
            if (shouldSendMultiRefs != null)
                doMultiRefs = shouldSendMultiRefs.booleanValue();
            
            Boolean shouldDisablePrettyXML =
                  (Boolean)msgContext.getProperty(AxisEngine.PROP_DISABLE_PRETTY_XML);
            if (shouldDisablePrettyXML != null)
                disablePrettyXML = shouldDisablePrettyXML.booleanValue();
            
            Boolean shouldDisableNamespacePrefixOptimization =
                  (Boolean)msgContext.getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION);
            if (shouldDisableNamespacePrefixOptimization != null) {
                enableNamespacePrefixOptimization = shouldDisableNamespacePrefixOptimization.booleanValue();
            } else {
                enableNamespacePrefixOptimization = JavaUtils.isTrue(AxisProperties.getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION,
                                "true"));
            }
            boolean sendTypesDefault = sendXSIType;

            // A Literal use operation overrides the above settings. Don't
            // send xsi:type, and don't do multiref in that case.
            OperationDesc operation = msgContext.getOperation();
            if (operation != null) {
                if (operation.getUse() != Use.ENCODED) {
                    doMultiRefs = false;
                    sendTypesDefault = false;
                }
            } else {
                // A Literal use service also overrides the above settings. 
                SOAPService service = msgContext.getService();
                if (service != null) {
                    if (service.getUse() != Use.ENCODED) {
                        doMultiRefs = false;
                        sendTypesDefault = false;
                    }
                }
            }

            // The SEND_TYPE_ATTR and PROP_SEND_XSI options indicate
            // whether the elements should have xsi:type attributes.
            // Only turn this off is the user tells us to
            if ( !msgContext.isPropertyTrue(Call.SEND_TYPE_ATTR, sendTypesDefault ))
                sendXSIType = false ;

// Don't need this since the above isPropertyTrue should walk up to the engine's
// properties...?
//            
//            Boolean opt = (Boolean)optionSource.getOption(AxisEngine.PROP_SEND_XSI);
//            if (opt != null) {
//                sendXSIType = opt.booleanValue();
//            }
        } else {
            enableNamespacePrefixOptimization = JavaUtils.isTrue(AxisProperties.getProperty(AxisEngine.PROP_ENABLE_NAMESPACE_PREFIX_OPTIMIZATION,
                            "true"));
            disablePrettyXML = JavaUtils.isTrue(AxisProperties.getProperty(AxisEngine.PROP_DISABLE_PRETTY_XML,
                            "true"));
        }

        // Set up preferred prefixes based on current schema, soap ver, etc.
        initialize();
    }

    /**
     * Get whether the serialization should be pretty printed.
     * @return true/false
     */
    public boolean getPretty() {
        return pretty;
    }

    /**
     * Indicate whether the serialization should be pretty printed.
     * @param pretty true/false
     */
    public void setPretty(boolean pretty) {
        if(!disablePrettyXML) {
            this.pretty = pretty;
        }
    }

    /**
     * Are we doing multirefs?
     * @return true or false
     */
    public boolean getDoMultiRefs() {
        return doMultiRefs;
    }

    /**
     * Set whether we are doing multirefs
     */
    public void setDoMultiRefs (boolean shouldDo)
    {
        doMultiRefs = shouldDo;
    }

    /**
     * Set whether or not we should write XML declarations.
     * @param sendDecl true/false
     */
    public void setSendDecl(boolean sendDecl)
    {
        sendXMLDecl = sendDecl;
    }

    /**
     * Get whether or not to write xsi:type attributes.
     * @return true/false
     */
    public boolean shouldSendXSIType() {
        return sendXSIType;
    }

    /**
     * Get the TypeMapping we're using.
     * @return TypeMapping or null
     */
    public TypeMapping getTypeMapping()
    {
        // Always allow the default mappings
        if (msgContext == null)
            return DefaultTypeMappingImpl.getSingletonDelegate();

        String encodingStyle = msgContext.getEncodingStyle();
        if (encodingStyle == null)
            encodingStyle = soapConstants.getEncodingURI();
        return (TypeMapping) msgContext.
                        getTypeMappingRegistry().getTypeMapping(encodingStyle);
    }

    /**
     * Get the TypeMappingRegistry we're using.
     * @return TypeMapping or null
     */
    public TypeMappingRegistry getTypeMappingRegistry() {
        if (msgContext == null)
            return null;
        return msgContext.getTypeMappingRegistry();
    }

    /**
     * Get a prefix for a namespace URI.  This method will ALWAYS
     * return a valid prefix - if the given URI is already mapped in this
     * serialization, we return the previous prefix.  If it is not mapped,
     * we will add a new mapping and return a generated prefix of the form
     * "ns<num>".
     * @param uri is the namespace uri
     * @return prefix
     */
    public String getPrefixForURI(String uri)
    {
        return getPrefixForURI(uri, null, false);
    }

    /**
     * Get a prefix for the given namespace URI.  If one has already been
     * defined in this serialization, use that.  Otherwise, map the passed
     * default prefix to the URI, and return that.  If a null default prefix
     * is passed, use one of the form "ns<num>"
     */
    public String getPrefixForURI(String uri, String defaultPrefix)
    {
        return getPrefixForURI(uri, defaultPrefix, false);
    }

    /**
     * Get a prefix for the given namespace URI.  If one has already been
     * defined in this serialization, use that.  Otherwise, map the passed
     * default prefix to the URI, and return that.  If a null default prefix
     * is passed, use one of the form "ns<num>"
     */
    public String getPrefixForURI(String uri, String defaultPrefix, boolean attribute)
    {
        if ((uri == null) || (uri.length() == 0))
            return null;

        // If we're looking for an attribute prefix, we shouldn't use the
        // "" prefix, but always register/find one.
        String prefix = nsStack.getPrefix(uri, attribute);

        if (prefix == null) {
            prefix = (String)preferredPrefixes.get(uri);

            if (prefix == null) {
                if (defaultPrefix == null) {
                    prefix = "ns" + lastPrefixIndex++;
                    while(nsStack.getNamespaceURI(prefix)!=null) {
                        prefix = "ns" + lastPrefixIndex++;    
                    }
                } else {
                    prefix = defaultPrefix;
                }
            }

            registerPrefixForURI(prefix, uri);
        }

        return prefix;
    }

    /**
     * Register prefix for the indicated uri
     * @param prefix
     * @param uri is the namespace uri
     */
    public void registerPrefixForURI(String prefix, String uri)
    {
        if (debugEnabled) {
            log.debug(Messages.getMessage("register00", prefix, uri));
        }

        if ((uri != null) && (prefix != null)) {
            if (noNamespaceMappings) {
                nsStack.push();
                noNamespaceMappings = false;
            }
            String activePrefix = nsStack.getPrefix(uri,true);
            if(activePrefix == null || !activePrefix.equals(prefix)) {
                nsStack.add(uri, prefix);
            }
        }
    }

    /**
     * Return the current message
     */
    public Message getCurrentMessage()
    {
        if (msgContext == null)
            return null;
        return msgContext.getCurrentMessage();
    }

    /**
     * Get the MessageContext we're operating with
     */
    public MessageContext getMessageContext() {
        return msgContext;
    }

    /**
     * Returns this context's encoding style.  If we've got a message
     * context then we'll get the style from that; otherwise we'll
     * return a default.
     *
     * @return a <code>String</code> value
     */
    public String getEncodingStyle() {
        return msgContext == null ? Use.DEFAULT.getEncoding() : msgContext.getEncodingStyle();
    }

    /**
     * Returns whether this context should be encoded or not.
     *
     * @return a <code>boolean</code> value
     */
    public boolean isEncoded() {
        return Constants.isSOAP_ENC(getEncodingStyle());
    }

    /**
     * Convert QName to a string of the form <prefix>:<localpart>
     * @param qName
     * @return prefixed qname representation for serialization.
     */
    public String qName2String(QName qName, boolean writeNS)
    {
        String prefix = null;
        String namespaceURI = qName.getNamespaceURI();
        String localPart = qName.getLocalPart();
        
        if(localPart != null && localPart.length() > 0) {
            int index = localPart.indexOf(':');
            if(index!=-1){
                prefix = localPart.substring(0,index);
                if(prefix.length()>0 && !prefix.equals("urn")){
                    registerPrefixForURI(prefix, namespaceURI);
                    localPart = localPart.substring(index+1);
                } else {
                    prefix = null;
                }
            }
            localPart = Utils.getLastLocalPart(localPart);            
        }

        if (namespaceURI.length() == 0) {
            if (writeNS) {
                // If this is unqualified (i.e. prefix ""), set the default
                // namespace to ""
                String defaultNS = nsStack.getNamespaceURI("");
                if (defaultNS != null && defaultNS.length() > 0) {
                    registerPrefixForURI("", "");
                }
            }
        } else {
            prefix = getPrefixForURI(namespaceURI);
        }

        if ((prefix == null) || (prefix.length() == 0))
           return localPart;

        return prefix + ':' + localPart;
    }

    public String qName2String(QName qName)
    {
        return qName2String(qName, false);
    }

    /**
     * Convert attribute QName to a string of the form <prefix>:<localpart>
     * There are slightly different rules for attributes:
     *  - There is no default namespace
     *  - any attribute in a namespace must have a prefix
     *
     * @param qName QName
     * @return prefixed qname representation for serialization.
     */
    public String attributeQName2String(QName qName) {
        String prefix = null;
        String uri = qName.getNamespaceURI(); 
        if (uri.length() > 0) {
            prefix = getPrefixForURI(uri, null, true);
        }

        if ((prefix == null) || (prefix.length() == 0))
           return qName.getLocalPart();
        
        return prefix + ':' + qName.getLocalPart();
    }

    /**
     * Get the QName associated with the specified class.
     * @param cls Class of an object requiring serialization.
     * @return appropriate QName associated with the class.
     */
    public QName getQNameForClass(Class cls)
    {
        return getTypeMapping().getTypeQName(cls);
    }

    /**
     * Indicates whether the object should be interpretted as a primitive
     * for the purposes of multi-ref processing.  A primitive value
     * is serialized directly instead of using id/href pairs.  Thus
     * primitive serialization/deserialization is slightly faster.
     * @param value to be serialized
     * @return true/false
     */
    public boolean isPrimitive(Object value)
    {
        if (value == null) return true;

        Class javaType = value.getClass();

        if (javaType.isPrimitive()) return true;

        if (javaType == String.class) return true;
        if (Calendar.class.isAssignableFrom(javaType)) return true;
        if (Date.class.isAssignableFrom(javaType)) return true;
        if (HexBinary.class.isAssignableFrom(javaType)) return true;
        if (Element.class.isAssignableFrom(javaType)) return true;
        if (javaType == byte[].class) return true;

        // There has been discussion as to whether arrays themselves should
        // be regarded as multi-ref.
        // Here are the three options:
        //   1) Arrays are full-fledged Objects and therefore should always be
        //      multi-ref'd  (Pro: This is like java.  Con: Some runtimes don't
        //      support this yet, and it requires more stuff to be passed over the wire.)
        //   2) Arrays are not full-fledged Objects and therefore should
        //      always be passed as single ref (note the elements of the array
        //      may be multi-ref'd.) (Pro:  This seems reasonable, if a user
        //      wants multi-referencing put the array in a container.  Also
        //      is more interop compatible.  Con: Not like java serialization.)
        //   3) Arrays of primitives should be single ref, and arrays of
        //      non-primitives should be multi-ref.  (Pro: Takes care of the
        //      looping case.  Con: Seems like an obtuse rule.)
        //
        // Changing the code from (1) to (2) to see if interop fairs better.
        if (javaType.isArray()) return true;

        // Note that java.lang wrapper classes (i.e. java.lang.Integer) are
        // not primitives unless the corresponding type is an xsd type.
        // (If the wrapper maps to a soap encoded primitive, it can be nillable
        // and multi-ref'd).
        QName qName = getQNameForClass(javaType);
        if (qName != null && Constants.isSchemaXSD(qName.getNamespaceURI())) {
            if (SchemaUtils.isSimpleSchemaType(qName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive, the
     * javaType is the actual unwrapped object type.
     * xsi:type is set by using the javaType to
     * find an appopriate xmlType from the TypeMappingRegistry.
     * Null values and the xsi:type flag will be sent or not depending 
     * on previous configuration of this SerializationContext.
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value)
        throws IOException {
        serialize(elemQName, attributes, value, null, null, null, null);
    }

    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive, the
     * javaType is the actual unwrapped object type.
     * The xmlType is the QName of the type that is used to set
     * xsi:type.  If not specified, xsi:type is set by using the javaType to
     * find an appopriate xmlType from the TypeMappingRegistry.
     * Null values and the xsi:type flag will be sent or not depending 
     * on previous configuration of this SerializationContext.
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType is the qname of the type or null.
     * @deprecated use serialize(QName, Attributes, Object, QName, Class) instead
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value,
                          QName xmlType)
        throws IOException {
        serialize(elemQName, attributes, value, xmlType, null, null, null);
    }
    
    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive, the
     * javaType is the actual unwrapped object type.
     * The xmlType is the QName of the type that is used to set
     * xsi:type.  If not specified, xsi:type is set by using the javaType to
     * find an appopriate xmlType from the TypeMappingRegistry.
     * Null values and the xsi:type flag will be sent or not depending 
     * on previous configuration of this SerializationContext.
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType is the qname of the type or null.
     * @param javaType is the java type of the value
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value,
                          QName xmlType, Class javaType)
        throws IOException {
        serialize(elemQName, attributes, value, xmlType, javaType, null, null);
    }

    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive.
     * The xmlType (if specified) is the QName of the type that is used to set
     * xsi:type.
     * The sendNull flag indicates whether null values should be sent over the
     * wire (default is to send such values with xsi:nil="true").
     * The sendType flag indicates whether the xsi:type flag should be sent
     * (default is true).
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType is the qname of the type or null.
     * @param sendNull determines whether to send null values.
     * @param sendType determines whether to set xsi:type attribute.
     *
     * @deprecated use serialize(QName, Attributes, Object, QName,
     * Boolean, Boolean) instead.
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value,
                          QName xmlType,
                          boolean sendNull,
                          Boolean sendType)
        throws IOException
    {
        serialize( elemQName, attributes, value, xmlType, null, 
                   (sendNull) ? Boolean.TRUE : Boolean.FALSE, 
                   sendType);
    }

    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive.
     * The xmlType (if specified) is the QName of the type that is used to set
     * xsi:type.
     * The sendNull flag indicates whether to end an element with an xsi:nil="true" attribute for null
     * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE).
     * The sendType flag indicates whether the xsi:type flag should be sent
     * (default is true).
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType is the qname of the type or null.
     * @param sendNull determines whether to send null values.
     * @param sendType determines whether to set xsi:type attribute.
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value,
                          QName xmlType,
                          Boolean sendNull,
                          Boolean sendType)
        throws IOException 
    {
        serialize(elemQName, attributes, value, xmlType, null, sendNull, sendType);
        
    }
    
    /**
     * Serialize the indicated value as an element with the name
     * indicated by elemQName.
     * The attributes are additional attribute to be serialized on the element.
     * The value is the object being serialized.  (It may be serialized
     * directly or serialized as an mult-ref'd item)
     * The value is an Object, which may be a wrapped primitive.
     * The xmlType (if specified) is the QName of the type that is used to set
     * xsi:type.
     * The sendNull flag indicates whether to end an element with an xsi:nil="true" attribute for null
     * variables (if Boolean.TRUE), or nothing (if Boolean.FALSE).
     * The sendType flag indicates whether the xsi:type flag should be sent
     * (default is true).
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType is the qname of the type or null.
     * @param javaType is the java type of the value
     * @param sendNull determines whether to send null values.
     * @param sendType determines whether to set xsi:type attribute.
     */
    public void serialize(QName elemQName,
                          Attributes attributes,
                          Object value,
                          QName xmlType,
                          Class javaClass,
                          Boolean sendNull,
                          Boolean sendType)
        throws IOException
    {
        boolean sendXSITypeCache = sendXSIType;
        if (sendType != null) {
            sendXSIType = sendType.booleanValue();
        }
        boolean shouldSendType = shouldSendXSIType();

        try {
            Boolean sendNullCache = this.sendNull;
            if (sendNull != null) {
                this.sendNull = sendNull;
            } else {
                sendNull = this.sendNull;
            }

            if (value == null) {
                // If the value is null, the element is
                // passed with xsi:nil="true" to indicate that no object is present.
                if (this.sendNull.booleanValue()) {
                    AttributesImpl attrs = new AttributesImpl();
                    if (attributes != null && 0 < attributes.getLength())
                        attrs.setAttributes(attributes);
                    if (shouldSendType)
                        attrs = (AttributesImpl) setTypeAttribute(attrs, xmlType);
                    String nil = schemaVersion.getNilQName().getLocalPart();
                    attrs.addAttribute(schemaVersion.getXsiURI(), nil, "xsi:" + nil,
                                       "CDATA", "true");
                    startElement(elemQName, attrs);
                    endElement();
                }
                this.sendNull = sendNullCache;
                return;
            }

            Message msg= getCurrentMessage();
            if(null != msg){
                //Get attachments. returns null if no attachment support.
                Attachments attachments= getCurrentMessage().getAttachmentsImpl();

                if( null != attachments && attachments.isAttachment(value)){
                    //Attachment support and this is an object that should be treated as an attachment.

                    //Allow an the attachment to do its own serialization.
                    serializeActual(elemQName, attributes, value,
                                    xmlType, javaClass, sendType);

                    //No need to add to mulitRefs. Attachment data stream handled by
                    // the message;
                    this.sendNull = sendNullCache;
                    return;
                }
            }

            // If multi-reference is enabled and this object value is not a primitive
            // and we are not forcing serialization of the object, then generate
            // an element href (and store the object for subsequent outputMultiRef
            // processing).

            // NOTE : you'll notice that everywhere we register objects in the
            // multiRefValues and secondLevelObjects collections, we key them
            // using getIdentityKey(value) instead of the Object reference itself.
            // THIS IS IMPORTANT, and please make sure you understand what's
            // going on if you change any of this code.  It's this way to make
            // sure that individual Objects are serialized separately even if the
            // hashCode() and equals() methods have been overloaded to make two
            // Objects appear equal.

            if (doMultiRefs && isEncoded() &&
                    (value != forceSer) && !isPrimitive(value)) {
                if (multiRefIndex == -1)
                    multiRefValues = new HashMap();

                String id;

                // Look for a multi-ref descriptor for this Object.
                MultiRefItem mri = (MultiRefItem)multiRefValues.get(
                        getIdentityKey(value));
                if (mri == null) {
                    // Didn't find one, so create one, give it a new ID, and store
                    // it for next time.
                    multiRefIndex++;
                    id = "id" + multiRefIndex;
                    mri = new MultiRefItem (id, xmlType, sendType, value);
                    multiRefValues.put(getIdentityKey(value), mri);

                    /**
                     * If we're SOAP 1.2, we can "inline" the serializations,
                     * so put it out now, with it's ID.
                     */
                    if (soapConstants == SOAPConstants.SOAP12_CONSTANTS) {
                        AttributesImpl attrs = new AttributesImpl();
                        if (attributes != null && 0 < attributes.getLength())
                            attrs.setAttributes(attributes);
                        attrs.addAttribute("", Constants.ATTR_ID, "id", "CDATA",
                                           id);
                        serializeActual(elemQName, attrs, value, xmlType, javaClass, sendType);
                        this.sendNull = sendNullCache;
                        return;
                    }


                    /** If we're in the middle of writing out
                     * the multi-refs, we've already cloned the list of objects
                     * and so even though we add a new one to multiRefValues,
                     * it won't get serialized this time around.
                     *
                     * To deal with this, we maintain a list of "second level"
                     * Objects - ones that need serializing as a result of
                     * serializing the first level.  When outputMultiRefs() is
                     * nearly finished, it checks to see if secondLevelObjects
                     * is empty, and if not, it goes back and loops over those
                     * Objects.  This can happen N times depending on how deep
                     * the Object graph goes.
                     */
                    if (outputMultiRefsFlag) {
                        if (secondLevelObjects == null)
                            secondLevelObjects = new HashSet();
                        secondLevelObjects.add(getIdentityKey(value));
                    }
                } else {
                    // Found one, remember it's ID
                    id = mri.id;
                }

                // Serialize an HREF to our object
                AttributesImpl attrs = new AttributesImpl();
                if (attributes != null && 0 < attributes.getLength())
                    attrs.setAttributes(attributes);
                attrs.addAttribute("", soapConstants.getAttrHref(), soapConstants.getAttrHref(),
                                   "CDATA", '#' + id);

                startElement(elemQName, attrs);
                endElement();
                this.sendNull = sendNullCache;
                return;
            }

            // The forceSer variable is set by outputMultiRefs to force
            // serialization of this object via the serialize(...) call
            // below.  However, if the forced object contains a self-reference, we
            // get into an infinite loop..which is why it is set back to null
            // before the actual serialization.
            if (value == forceSer)
                forceSer = null;

            // Actually serialize the value.  (i.e. not an href like above)
            serializeActual(elemQName, attributes, value, xmlType, javaClass, sendType);
        } finally {
            sendXSIType = sendXSITypeCache;
        }
    }

    /**
     * Get an IDKey that represents the unique identity of the object.
     * This is used as a unique key into a HashMap which will
     * not give false hits on other Objects where hashCode() and equals()
     * have been overriden to match.
     *
     * @param value the Object to hash
     * @return a unique IDKey for the identity
     */
    private IDKey getIdentityKey(Object value) {
        return new IDKey(value);
    }

    /**
     * The serialize method uses hrefs to reference all non-primitive
     * values.  These values are stored and serialized by calling
     * outputMultiRefs after the serialize method completes.
     */
    public void outputMultiRefs() throws IOException
    {
        if (!doMultiRefs || (multiRefValues == null) ||
                soapConstants == SOAPConstants.SOAP12_CONSTANTS)
            return;
        outputMultiRefsFlag = true;
        AttributesImpl attrs = new AttributesImpl();
        attrs.addAttribute("","","","","");

        String encodingURI = soapConstants.getEncodingURI();
        // explicitly state that this attribute is not a root
        String prefix = getPrefixForURI(encodingURI);
        String root = prefix + ":root";
        attrs.addAttribute(encodingURI, Constants.ATTR_ROOT, root,
                           "CDATA", "0");

        // Make sure we put the encodingStyle on each multiref element we
        // output.
        String encodingStyle;
        if (msgContext != null) {
            encodingStyle = msgContext.getEncodingStyle();
        } else {
            encodingStyle = soapConstants.getEncodingURI();
        }
        String encStyle = getPrefixForURI(soapConstants.getEnvelopeURI()) +
                                          ':' + Constants.ATTR_ENCODING_STYLE;
        attrs.addAttribute(soapConstants.getEnvelopeURI(),
                           Constants.ATTR_ENCODING_STYLE,
                           encStyle,
                           "CDATA",
                           encodingStyle);

        // Make a copy of the keySet because it could be updated
        // during processing
        HashSet keys = new HashSet();
        keys.addAll(multiRefValues.keySet());
        Iterator i = keys.iterator();
        while (i.hasNext()) {
            while (i.hasNext()) {
                AttributesImpl attrs2 = new AttributesImpl(attrs);
                Object val = i.next();
                MultiRefItem mri = (MultiRefItem) multiRefValues.get(val);
                attrs2.setAttribute(0, "", Constants.ATTR_ID, "id", "CDATA",
                                   mri.id);

                forceSer = mri.value;

                // Now serialize the value.
                // The sendType parameter is defaulted for interop purposes.
                // Some of the remote services do not know how to
                // ascertain the type in these circumstances (though Axis does).
                serialize(multirefQName, attrs2, mri.value,
                          mri.xmlType,
                          null,
                          this.sendNull,
                          Boolean.TRUE);   // mri.sendType
            }

            // Done processing the iterated values.  During the serialization
            // of the values, we may have run into new nested values.  These
            // were placed in the secondLevelObjects map, which we will now
            // process by changing the iterator to locate these values.
            if (secondLevelObjects != null) {
                i = secondLevelObjects.iterator();
                secondLevelObjects = null;
            }
        }

        // Reset maps and flags
        forceSer = null;
        outputMultiRefsFlag = false;
        multiRefValues = null;
        multiRefIndex = -1;
        secondLevelObjects = null;
    }

    public void writeXMLDeclaration() throws IOException {
        writer.write("<?xml version=\"1.0\" encoding=\"");        
        writer.write(encoding);
        writer.write("\"?>");
        startOfDocument = false;        
    }
    
    /**
     * Writes (using the Writer) the start tag for element QName along with the
     * indicated attributes and namespace mappings.
     * @param qName is the name of the element
     * @param attributes are the attributes to write
     */
    public void startElement(QName qName, Attributes attributes)
        throws IOException
    {
        java.util.ArrayList vecQNames = null;
        if (debugEnabled) {
            log.debug(Messages.getMessage("startElem00",
                    "[" + qName.getNamespaceURI() + "]:" + qName.getLocalPart()));
        }

        if (startOfDocument && sendXMLDecl) {
            writeXMLDeclaration();
        }

        if (writingStartTag) {
            writer.write('>');
            if (pretty) writer.write('\n');
            indent++;
        }

        if (pretty) for (int i=0; i<indent; i++) writer.write(' ');
        String elementQName = qName2String(qName, true);
        writer.write('<');

        writer.write(elementQName);
        
        if (writeXMLType != null) {
            attributes = setTypeAttribute(attributes, writeXMLType);
            writeXMLType = null;
        }

        if (attributes != null) {
            for (int i = 0; i < attributes.getLength(); i++) {
                String qname = attributes.getQName(i);
                writer.write(' ');

                String prefix = "";
                String uri = attributes.getURI(i);
                if (uri != null && uri.length() > 0) {
                    if (qname.length() == 0) {
                        // If qname isn't set, generate one
                        prefix = getPrefixForURI(uri);
                    } else {
                        // If it is, make sure the prefix looks reasonable.
                        int idx = qname.indexOf(':');
                        if (idx > -1) {
                            prefix = qname.substring(0, idx);
                            prefix = getPrefixForURI(uri,
                                                     prefix, true);
                        }
                    }
                    if (prefix.length() > 0) {
                        qname = prefix + ':' + attributes.getLocalName(i);
                    } else {
                        qname = attributes.getLocalName(i);
                    }
                } else {
                   qname = attributes.getQName(i);
                    if(qname.length() == 0)
                        qname = attributes.getLocalName(i);
                }

                if (qname.startsWith("xmlns")) {
                  if (vecQNames == null) vecQNames = new ArrayList();
                  vecQNames.add(qname);
                }
                writer.write(qname);
                writer.write("=\"");
                
                getEncoder().writeEncoded(writer, attributes.getValue(i));
                
                writer.write('"');
            }
        }

        if (noNamespaceMappings) {
            nsStack.push();
        } else {
            for (Mapping map=nsStack.topOfFrame(); map!=null; map=nsStack.next()) {
                if (!(map.getNamespaceURI().equals(Constants.NS_URI_XMLNS) && map.getPrefix().equals("xmlns")) &&
                    !(map.getNamespaceURI().equals(Constants.NS_URI_XML) && map.getPrefix().equals("xml")))
                {                
                    StringBuffer sb = new StringBuffer("xmlns");
                    if (map.getPrefix().length() > 0) {
                        sb.append(':');
                        sb.append(map.getPrefix());
                    }
                    if ((vecQNames==null) || (vecQNames.indexOf(sb.toString())==-1)) {
                        writer.write(' ');
                        sb.append("=\"");
                        sb.append(map.getNamespaceURI());
                        sb.append('"');
                        writer.write(sb.toString());
                    }
                }
            }

            noNamespaceMappings = true;
        }

        writingStartTag = true;

        elementStack.push(elementQName);

        onlyXML=true;
    }

    /**
     * Writes the end element tag for the open element.
     **/
    public void endElement()
        throws IOException
    {
        String elementQName = (String)elementStack.pop();

        if (debugEnabled) {
            log.debug(Messages.getMessage("endElem00", "" + elementQName));
        }

        nsStack.pop();

        if (writingStartTag) {
            writer.write("/>");
            if (pretty) writer.write('\n');
            writingStartTag = false;
            return;
        }

        if (onlyXML) {
            indent--;
            if (pretty) for (int i=0; i<indent; i++) writer.write(' ');
        }
        writer.write("</");
        writer.write(elementQName);
        writer.write('>');
        if (pretty) if (indent>0) writer.write('\n');
        onlyXML=true;
    }

    /**
     * Convenience operation to write out (to Writer) the characters
     * in p1 starting at index p2 for length p3.
     * @param p1 character array to write
     * @param p2 starting index in array
     * @param p3 length to write
     */
    public void writeChars(char [] p1, int p2, int p3)
        throws IOException
    {
        if (startOfDocument && sendXMLDecl) {
            writeXMLDeclaration();
        }
        
        if (writingStartTag) {
            writer.write('>');
            writingStartTag = false;
        }
        writeSafeString(String.valueOf(p1,p2,p3));
        onlyXML=false;
    }

    /**
     * Convenience operation to write out (to Writer) the String
     * @param string is the String to write.
     */
    public void writeString(String string)
        throws IOException
    {
        if (startOfDocument && sendXMLDecl) {
            writeXMLDeclaration();
        }
        
        if (writingStartTag) {
            writer.write('>');
            writingStartTag = false;
        }
        writer.write(string);
        onlyXML=false;
    }

    /**
     * Convenience operation to write out (to Writer) the String
     * properly encoded with xml entities (like &amp)
     * @param string is the String to write.
     */
    public void writeSafeString(String string)
        throws IOException
    {
        if (startOfDocument && sendXMLDecl) {
            writeXMLDeclaration();
        }
        
        if (writingStartTag) {
            writer.write('>');
            writingStartTag = false;
        }
        
        getEncoder().writeEncoded(writer, string);
        onlyXML=false;
    }

    /**
     * Output a DOM representation to a SerializationContext
     * @param el is a DOM Element
     */
    public void writeDOMElement(Element el)
        throws IOException
    {
        if (startOfDocument && sendXMLDecl) {
            writeXMLDeclaration();
        }
        
        // If el is a Text element, write the text and exit
        if (el instanceof org.apache.axis.message.Text) {            
            writeSafeString(((Text)el).getData());
            return;
        }   
        
        AttributesImpl attributes = null;
        NamedNodeMap attrMap = el.getAttributes();

        if (attrMap.getLength() > 0) {
            attributes = new AttributesImpl();
            for (int i = 0; i < attrMap.getLength(); i++) {
                Attr attr = (Attr)attrMap.item(i);
                String tmp = attr.getNamespaceURI();
                if ( tmp != null && tmp.equals(Constants.NS_URI_XMLNS) ) {
                    String prefix = attr.getLocalName();
                    if (prefix != null) {
                        if (prefix.equals("xmlns"))
                            prefix = "";
                        String nsURI = attr.getValue();
                        registerPrefixForURI(prefix, nsURI);
                    }
                    continue;
                }

                attributes.addAttribute(attr.getNamespaceURI(),
                                        attr.getLocalName(),
                                        attr.getName(),
                                        "CDATA", attr.getValue());
            }
        }

        String namespaceURI = el.getNamespaceURI();
        String localPart = el.getLocalName();
        if(namespaceURI == null || namespaceURI.length()==0)
            localPart = el.getNodeName();
        QName qName = new QName(namespaceURI, localPart);

        startElement(qName, attributes);

        NodeList children = el.getChildNodes();
        for (int i = 0; i < children.getLength(); i++) {
            Node child = children.item(i);
            if (child instanceof Element) {
                writeDOMElement((Element)child);
            } else if (child instanceof CDATASection) {
                writeString("<![CDATA[");
                writeString(((Text)child).getData());
                writeString("]]>");
            } else if (child instanceof Comment) {
                writeString("<!--");
                writeString(((CharacterData)child).getData());
                writeString("-->");
            } else if (child instanceof Text) {
                writeSafeString(((Text)child).getData());
            }
        }

        endElement();
    }

    /**
     * Convenience method to get the Serializer for a specific
     * java type
     * @param javaType is Class for a type to serialize
     * @return Serializer
     */
    public final Serializer getSerializerForJavaType(Class javaType) {
        SerializerFactory serF = null;
        Serializer ser = null;
        try {
            serF = (SerializerFactory) getTypeMapping().getSerializer(javaType);
            if (serF != null) {
                ser = (Serializer) serF.getSerializerAs(Constants.AXIS_SAX);
            }
        } catch (JAXRPCException e) {
        }

        return ser;
    }

    /**
     * Obtains the type attribute that should be serialized and returns the new list of Attributes
     * @param attributes of the qname
     * @param type is the qname of the type
     * @return new list of Attributes
     */
    public Attributes setTypeAttribute(Attributes attributes, QName type)
    {
        SchemaVersion schema = SchemaVersion.SCHEMA_2001;
        if (msgContext != null) {
            schema = msgContext.getSchemaVersion();
        }

        if (type == null ||
             type.getLocalPart().indexOf(SymbolTable.ANON_TOKEN) >= 0 ||
            ((attributes != null) &&
             (attributes.getIndex(schema.getXsiURI(),
                                "type") != -1)))
            return attributes;

        AttributesImpl attrs = new AttributesImpl();
        if (attributes != null && 0 < attributes.getLength() )
            attrs.setAttributes(attributes);

        String prefix = getPrefixForURI(schema.getXsiURI(),
                                           "xsi");

        attrs.addAttribute(schema.getXsiURI(),
                           "type",
                           prefix + ":type",
                           "CDATA", attributeQName2String(type));
        return attrs;
    }

    /**
     * Invoked to do the actual serialization of the qName (called by serialize above).
     * additional attributes that will be serialized with the qName.
     * @param elemQName is the QName of the element
     * @param attributes are additional attributes
     * @param value is the object to serialize
     * @param xmlType (optional) is the desired type QName.
     * @param sendType indicates whether the xsi:type attribute should be set.
     */
    private void serializeActual(QName elemQName,
                                Attributes attributes,
                                Object value,
                                QName xmlType,
                                Class javaClass,
                                Boolean sendType)
        throws IOException
    {
        boolean shouldSendType = (sendType == null) ? shouldSendXSIType() :
            sendType.booleanValue();

        if (value != null) {
            TypeMapping tm = getTypeMapping();

            if (tm == null) {
                throw new IOException(
                        Messages.getMessage("noSerializer00",
                                             value.getClass().getName(),
                                             "" + this));
            }

            // Set currentXMLType to the one desired one.
            // Note for maxOccurs usage this xmlType is the
            // type of the component not the type of the array.
            currentXMLType = xmlType;

            // if we're looking for xsd:anyType, accept anything...
            if (Constants.equals(Constants.XSD_ANYTYPE,xmlType)){
                xmlType = null;
                shouldSendType = true;
            }

            // Try getting a serializer for the prefered xmlType
            QNameHolder actualXMLType = new QNameHolder();
                        
            Class javaType = getActualJavaClass(xmlType, javaClass, value);
                        
            Serializer ser = getSerializer(javaType, xmlType,
                                           actualXMLType);

            if ( ser != null ) {
                // Send the xmlType if indicated or if
                // the actual xmlType is different than the
                // prefered xmlType
                if (shouldSendType ||
                    (xmlType != null &&
                     (!xmlType.equals(actualXMLType.value)))) {

                    if(!isEncoded()) {
                        if (Constants.isSOAP_ENC(actualXMLType.value.getNamespaceURI())) {
                            // Don't write SOAP_ENC types (i.e. Array) if we're not using encoding
                        } else if (javaType.isPrimitive() && javaClass != null && JavaUtils.getWrapperClass(javaType) == javaClass) {
                            // Don't write xsi:type when serializing primitive wrapper value as primitive type.
                        }
                        else {
                            if(!(javaType.isArray() && xmlType != null && Constants.isSchemaXSD(xmlType.getNamespaceURI())) ) {
                                writeXMLType = actualXMLType.value;
                            }
                        }
                    } else {
                        writeXMLType = actualXMLType.value;
                    }
                }

                // -----------------
                // NOTE: I have seen doc/lit tests that use
                // the type name as the element name in multi-ref cases
                // (for example <soapenc:Array ... >)
                // In such cases the xsi:type is not passed along.
                // -----------------
                // The multiref QName is our own fake name.
                // It may be beneficial to set the name to the
                // type name, but I didn't see any improvements
                // in the interop tests.
                //if (name.equals(multirefQName) && type != null)
                //    name = type;
                ser.serialize(elemQName, attributes, value, this);
                return;
            }
            throw new IOException(Messages.getMessage("noSerializer00",
                    value.getClass().getName(), "" + tm));
        }
        // !!! Write out a generic null, or get type info from somewhere else?
    }
    
    /**
     * Returns the java class for serialization. 
     * If the xmlType is xsd:anyType or javaType is array or javaType is java.lang.Object
     * the java class for serialization is the class of obj.
     * If the obj is not array and the obj's class does not match with the javaType,
     * the java class for serialization is the javaType.
     * Otherwise, the java class for serialization is the obj's class.
     * 
     * @param xmlType    the qname of xml type
     * @param javaType   the java class from serializer 
     * @param obj        the object to serialize
     * @return the java class for serialization
     */
    private Class getActualJavaClass(QName xmlType, Class javaType, Object obj) {
        Class cls = obj.getClass();
        
        if ((xmlType != null 
                    && Constants.isSchemaXSD(xmlType.getNamespaceURI()) && "anyType".equals(xmlType.getLocalPart()))
                || (javaType != null 
                        && (javaType.isArray() || javaType == Object.class))) {
            return cls;
        }
        
        if (javaType != null && !javaType.isAssignableFrom(cls) && !cls.isArray()) {
            return javaType;
        }
        
        return cls;
    }

    private Serializer getSerializerFromClass(Class javaType, QName qname) {
        
        Serializer serializer = null;
        try {
            Method method = 
                MethodCache.getInstance().getMethod(javaType,
                                                    SERIALIZER_METHOD,
                                                    SERIALIZER_CLASSES);
            if (method != null) {
                serializer = (Serializer) method.invoke(null,
                    new Object[] {getEncodingStyle(), javaType, qname});
            }
       } catch (NoSuchMethodException e) {
       } catch (IllegalAccessException e) {
       } catch (InvocationTargetException e) {
       }
       return serializer;
    }

    /**
     * Get the currently prefered xmlType
     * @return QName of xmlType or null
     */
    public QName getCurrentXMLType() {
        return currentXMLType;
    }

    /**
     * Walk the interfaces of a class looking for a serializer for that
     * interface.  Include any parent interfaces in the search also.
     *
     */
    private SerializerFactory getSerializerFactoryFromInterface(Class javaType,
                                                                QName xmlType,
                                                                TypeMapping tm)
    {
        SerializerFactory  serFactory  = null ;
        Class [] interfaces = javaType.getInterfaces();
        if (interfaces != null) {
            for (int i = 0; i < interfaces.length; i++) {
                Class iface = interfaces[i];
                serFactory = (SerializerFactory) tm.getSerializer(iface,
                                                                  xmlType);
                if (serFactory == null)
                    serFactory = getSerializerFactoryFromInterface(iface, xmlType, tm);
                if (serFactory != null)
                    break;

            }
        }
        return serFactory;
    }

    /**
     * getSerializer
     * Attempts to get a serializer for the indicated javaType and xmlType.
     * @param javaType is the type of the object
     * @param xmlType is the preferred qname type.
     * @param actualXMLType is set to a QNameHolder or null.
     *                     If a QNameHolder, the actual xmlType is returned.
     * @return found class/serializer or null
     **/
    private Serializer getSerializer(Class javaType, QName xmlType,
                                     QNameHolder actualXMLType) {
        SerializerFactory  serFactory  = null ;
        TypeMapping tm = getTypeMapping();
        if (actualXMLType != null) {
            actualXMLType.value = null;
        }

        while (javaType != null) {
            // check type mapping
            serFactory = (SerializerFactory) tm.getSerializer(javaType, xmlType);
            if (serFactory != null) {
                break;
            }

            // check the class for serializer
            Serializer serializer = getSerializerFromClass(javaType, xmlType);
            if (serializer != null) {
                if (actualXMLType != null) {
                    TypeDesc typedesc = TypeDesc.getTypeDescForClass(javaType);
                    if (typedesc != null) {
                        actualXMLType.value = typedesc.getXmlType();
                    }
                }
                return serializer;
            }

            // Walk my interfaces...
            serFactory = getSerializerFactoryFromInterface(javaType, xmlType, tm);
            if (serFactory != null) {
                break;
            }

            // Finally, head to my superclass
            javaType = javaType.getSuperclass();
        }

        // Using the serialization factory, create a serializer
        Serializer ser = null;
        if ( serFactory != null ) {
            ser = (Serializer) serFactory.getSerializerAs(Constants.AXIS_SAX);

            if (actualXMLType != null) {
                // Get the actual qname xmlType from the factory.
                // If not found via the factory, fall back to a less
                // performant solution.
                if (serFactory instanceof BaseSerializerFactory) {
                    actualXMLType.value =
                        ((BaseSerializerFactory) serFactory).getXMLType();
                }
                boolean encoded = isEncoded();
                if (actualXMLType.value == null ||
                        (!encoded &&
                        (actualXMLType.value.equals(Constants.SOAP_ARRAY) ||
                        actualXMLType.value.equals(Constants.SOAP_ARRAY12)))) {
                    actualXMLType.value = tm.getXMLType(javaType,
                                                        xmlType,
                                                        encoded);
                }
            }
        }

        return ser;
    }

    public String getValueAsString(Object value, QName xmlType, Class javaClass) throws IOException {
        Class cls = value.getClass();
        cls = getActualJavaClass(xmlType, javaClass, value);
        
        Serializer ser = getSerializer(cls, xmlType, null);
        
        // The java type is an array, but we need a simple type.
        if (ser instanceof ArraySerializer)
        {
            SimpleListSerializerFactory factory =
                new SimpleListSerializerFactory(cls, xmlType);
            ser = (Serializer)
                factory.getSerializerAs(getEncodingStyle());
        }

        if (!(ser instanceof SimpleValueSerializer)) {
            throw new IOException(
                    Messages.getMessage("needSimpleValueSer",
                                         ser.getClass().getName()));
        }
        SimpleValueSerializer simpleSer = (SimpleValueSerializer)ser;
        return simpleSer.getValueAsString(value, this);
    }

    public void setWriteXMLType(QName type) {
        writeXMLType = type;
    }

    public XMLEncoder getEncoder() {
        if(encoder == null) {
            encoder = XMLUtils.getXMLEncoder(encoding);
        }
        return encoder;
    }

    /**
     * get the encoding for the serialization
     * @return
     */
    public String getEncoding() {
        return encoding;
    }

    /**
     * set the encoding for the serialization
     */
    public void setEncoding(String encoding) {
        this.encoding = encoding;
    }

    public QName getItemQName() {
        return itemQName;
    }

    public void setItemQName(QName itemQName) {
        this.itemQName = itemQName;
    }

    public QName getItemType() {
        return itemType;
    }

    public void setItemType(QName itemType) {
        this.itemType = itemType;
    }
}