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

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

import javax.xml.namespace.QName;
import javax.xml.transform.Result;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.binding.BindingException;
import org.jboss.ws.core.binding.BufferedStreamResult;
import org.jboss.ws.core.binding.SerializationContext;
import org.jboss.ws.core.binding.AbstractSerializerFactory;
import org.jboss.ws.core.binding.SerializerSupport;
import org.jboss.ws.core.binding.TypeMappingImpl;
import org.jboss.ws.core.soap.NameImpl;
import org.jboss.ws.core.soap.SOAPContentElement;
import org.jboss.ws.core.soap.XMLFragment;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.wsf.common.JavaUtils;
import org.w3c.dom.NamedNodeMap;

/**
 * A Serializer that can handle SOAP encoded arrays.
 *
 * @author Thomas.Diesler@jboss.org
 * @since 31-Oct-2005
 */
public class SOAPArraySerializer extends SerializerSupport
{
   // provide logging
   private static final Logger log = Logger.getLogger(SOAPArraySerializer.class);

   private ParameterMetaData paramMetaData;
   private SerializerSupport compSerializer;
   private NullValueSerializer nullSerializer;
   private boolean isArrayComponentType;
   private StringBuilder buffer;

   public SOAPArraySerializer() throws BindingException
   {
      nullSerializer = new NullValueSerializer();
   }

   public Result serialize(SOAPContentElement soapElement, SerializationContext serContext) throws BindingException
   {
      paramMetaData = soapElement.getParamMetaData();
      QName xmlName = soapElement.getElementQName();
      QName xmlType = soapElement.getXmlType();
      Object value = soapElement.getObjectValue();
      NamedNodeMap attributes = soapElement.getAttributes();
      return serialize(xmlName, xmlType, value, serContext, attributes);
   }

   public Result serialize(QName xmlName, QName xmlType, Object value, SerializationContext serContext, NamedNodeMap attributes) throws BindingException
   {
      log.debug("serialize: [xmlName=" + xmlName + ",xmlType=" + xmlType + ",valueType=" + value.getClass().getName() + "]");
      try
      {
         if (paramMetaData == null)
            throw new IllegalStateException("Use serialize(SOAPContenentElement, SerializationContext)");

         QName compXmlName = paramMetaData.getXmlName();
         QName compXmlType = paramMetaData.getSOAPArrayCompType();
         Class javaType = paramMetaData.getJavaType();

         Class compJavaType = javaType.getComponentType();
         isArrayComponentType = isArrayJavaType(compJavaType) && isArrayXmlType(compXmlType);
         while (compJavaType.getComponentType() != null && isArrayComponentType == false)
         {
            compJavaType = compJavaType.getComponentType();
            isArrayComponentType = isArrayJavaType(compJavaType) && isArrayXmlType(compXmlType);
         }

         TypeMappingImpl typeMapping = serContext.getTypeMapping();
         if (compXmlType == null)
         {
            compXmlType = typeMapping.getXMLType(compJavaType);
            paramMetaData.setSOAPArrayCompType(compXmlType);
         }

         if (compXmlType == null)
            throw new WSException("Cannot obtain component xmlType for: " + compJavaType);

         // Get the component type serializer factory
         log.debug("Get component serializer for: [javaType=" + compJavaType.getName() + ",xmlType=" + compXmlType + "]");
         AbstractSerializerFactory compSerializerFactory = (AbstractSerializerFactory)typeMapping.getSerializer(compJavaType, compXmlType);
         if (compSerializerFactory == null)
         {
            log.warn("Cannot obtain component serializer for: [javaType=" + compJavaType.getName() + ",xmlType=" + compXmlType + "]");
            compSerializerFactory = (AbstractSerializerFactory)typeMapping.getSerializer(null, compXmlType);
         }
         if (compSerializerFactory == null)
            throw new WSException("Cannot obtain component serializer for: " + compXmlType);

         // Get the component type serializer
         compSerializer = (SerializerSupport)compSerializerFactory.getSerializer();

         // Get the corresponding wrapper type
         if (JavaUtils.isPrimitive(value.getClass()))
            value = JavaUtils.getWrapperValueArray(value);

         String nodeName = new NameImpl(compXmlName).getQualifiedName();

         buffer = new StringBuilder("<" + nodeName + " xmlns:" + Constants.PREFIX_SOAP11_ENC + "='" + Constants.URI_SOAP11_ENC + "' ");

         if (!(value instanceof Object[]))
            throw new WSException("Unsupported array type: " + javaType);

         Object[] objArr = (Object[])value;
         String arrayDim = "" + objArr.length;

         // Get multiple array dimension
         Object[] subArr = (Object[])value;
         while (isArrayComponentType == false && subArr.length > 0 && subArr[0] instanceof Object[])
         {
            subArr = (Object[])subArr[0];
            arrayDim += "," + subArr.length;
         }

         compXmlType = serContext.getNamespaceRegistry().registerQName(compXmlType);
         compXmlName = serContext.getNamespaceRegistry().registerQName(compXmlName);
         String arrayType = Constants.PREFIX_SOAP11_ENC + ":arrayType='" + compXmlType.getPrefix() + ":" + compXmlType.getLocalPart() + "[" + arrayDim + "]'";

         buffer.append(arrayType);
         buffer.append(" xmlns:" + Constants.PREFIX_XSI + "='" + Constants.NS_SCHEMA_XSI + "'");
         if (compXmlType.getNamespaceURI().equals(Constants.URI_SOAP11_ENC) == false)
            buffer.append(" xmlns:" + compXmlType.getPrefix() + "='" + compXmlType.getNamespaceURI() + "'");
         if (compXmlName.getNamespaceURI().length() > 0 && compXmlName.getNamespaceURI().equals(compXmlType.getNamespaceURI()) == false)
            buffer.append(" xmlns:" + compXmlName.getPrefix() + "='" + compXmlName.getNamespaceURI() + "'");

         buffer.append(">");
         serializeArrayComponents(compXmlName, compXmlType, serContext, objArr);
         buffer.append("</" + nodeName + ">");
         String xmlFragment = buffer.toString();
         
         log.debug("serialized: " + xmlFragment);
         return new BufferedStreamResult(xmlFragment);
      }
      catch (RuntimeException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new BindingException(e);
      }
   }

   private void serializeArrayComponents(QName xmlName, QName xmlType, SerializationContext serContext, Object[] objArr) throws BindingException
   {
      for (Object compValue : objArr)
      {
         if (isArrayComponentType == false && compValue instanceof Object[])
         {
            serializeArrayComponents(xmlName, xmlType, serContext, (Object[])compValue);
         }
         else
         {
            SerializerSupport ser = compSerializer;

            // Null component value
            if (compValue == null)
            {
               ser = nullSerializer;
            }

            Result result = ser.serialize(new QName("item"), xmlType, compValue, serContext, null);
            buffer.append(new XMLFragment(result).toXMLString());
         }
      }
   }

   /** True for all array xmlTypes, i.e. nmtokens, base64Binary, hexBinary
    * 
    *  FIXME: This method should be removed as soon as we can reliably get the SOAP
    *  arrayType from wsdl + schema. 
    */
   private boolean isArrayXmlType(QName xmlType)
   {
      boolean isArrayType = Constants.TYPE_SOAP11_BASE64.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_SOAP11_BASE64.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_SOAP11_BASE64BINARY.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_SOAP11_HEXBINARY.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_SOAP11_NMTOKENS.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_LITERAL_BASE64BINARY.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_LITERAL_HEXBINARY.equals(xmlType);
      isArrayType = isArrayType || Constants.TYPE_LITERAL_NMTOKENS.equals(xmlType);
      return isArrayType;
   }

   /** True for all array javaTypes, i.e. String[], Byte[], byte[] 
    * 
    *  FIXME: This method should be removed as soon as we can reliably get the SOAP
    *  arrayType from wsdl + schema. 
    */
   private boolean isArrayJavaType(Class javaType)
   {
      boolean isBinaryType = String[].class.equals(javaType) || Byte[].class.equals(javaType) || byte[].class.equals(javaType);
      return isBinaryType;
   }

}
