/*
 * 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: SOAPArrayDeserializer.java 3959 2007-07-20 14:44:19Z heiko.braun@jboss.com $

import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Iterator;
import java.util.StringTokenizer;

import javax.xml.namespace.QName;
import javax.xml.transform.Source;
import javax.xml.transform.dom.DOMSource;

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.AbstractDeserializerFactory;
import org.jboss.ws.core.binding.DeserializerSupport;
import org.jboss.ws.core.binding.SerializationContext;
import org.jboss.ws.core.binding.TypeMappingImpl;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.JavaUtils;
import org.w3c.dom.Element;

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

   private DeserializerSupport componentDeserializer;

   public Object deserialize(QName xmlName, QName xmlType, Source source, SerializationContext serContext) throws BindingException
   {
      log.debug("deserialize: [xmlName=" + xmlName + ",xmlType=" + xmlType + "]");

      try
      {
         ParameterMetaData paramMetaData = (ParameterMetaData)serContext.getProperty(ParameterMetaData.class.getName());

         Element soapElement = DOMUtils.sourceToElement(source);
         QName compXmlType = getComponentTypeFromAttribute(soapElement);
         paramMetaData.setSOAPArrayCompType(compXmlType);

         if (compXmlType == null)
            throw new WSException("Cannot obtain component xmlType: " + paramMetaData.getPartName());

         Class compJavaType = getJavaTypeForComponentType(compXmlType, serContext);

         // Get the component type deserializer factory
         log.debug("Get component deserializer for: [javaType=" + compJavaType.getName() + ",xmlType=" + compXmlType + "]");

         int[] arrDims = getDimensionsFromAttribute(soapElement);
         Object[] retArray = (Object[])Array.newInstance(compJavaType, arrDims);

         TypeMappingImpl typeMapping = serContext.getTypeMapping();
         AbstractDeserializerFactory compDeserializerFactory = (AbstractDeserializerFactory)typeMapping.getDeserializer(compJavaType, compXmlType);
         if (compDeserializerFactory == null)
         {
            log.warn("Cannot obtain component deserializer for: [javaType=" + compJavaType.getName() + ",xmlType=" + compXmlType + "]");
            compDeserializerFactory = (AbstractDeserializerFactory)typeMapping.getDeserializer(null, compXmlType);
         }

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

         // Get the component type deserializer
         componentDeserializer = (DeserializerSupport)compDeserializerFactory.getDeserializer();

         if (arrDims.length < 1 || 2 < arrDims.length)
            throw new WSException("Unsupported array dimensions: " + Arrays.asList(arrDims));

         Iterator it = DOMUtils.getChildElements(soapElement);
         if (arrDims.length == 1)
         {
            Object[] subArr = retArray;
            deserializeMemberValues(compXmlType, serContext, it, subArr);
         }
         if (arrDims.length == 2)
         {
            for (int i = 0; i < arrDims[0]; i++)
            {
               Object[] subArr = (Object[])retArray[i];
               deserializeMemberValues(compXmlType, serContext, it, subArr);
            }
         }

         log.debug("deserialized: " + retArray.getClass().getName());
         return retArray;
      }
      catch (RuntimeException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new BindingException(e);
      }
   }

   private void deserializeMemberValues(QName compXmlType, SerializationContext serContext, Iterator it, Object[] subArr) throws BindingException
   {
      QName compXmlName = new QName("item");

      int dim = subArr.length;
      for (int i = 0; i < dim; i++)
      {
         Object compValue = null;
         if (it.hasNext())
         {
            Element childElement = (Element)it.next();
            Source source = new DOMSource(childElement);
            compValue = componentDeserializer.deserialize(compXmlName, compXmlType, source, serContext);
            compValue = JavaUtils.getWrapperValueArray(compValue);
         }
         subArr[i] = compValue;
      }
   }

   private int[] getDimensionsFromAttribute(Element arrayElement)
   {
      QName attrQName = new QName(Constants.URI_SOAP11_ENC, "arrayType");
      QName arrayType = DOMUtils.getAttributeValueAsQName(arrayElement, attrQName);
      if (arrayType == null)
         throw new WSException("Cannot obtain attribute: " + attrQName);

      String localPart = arrayType.getLocalPart();
      int dimIndex = localPart.indexOf("[");

      String dimStr = localPart.substring(dimIndex);
      StringTokenizer st = new StringTokenizer(dimStr, "[,]");
      int[] arrDims = new int[st.countTokens()];
      for (int i = 0; st.hasMoreTokens(); i++)
         arrDims[i] = new Integer(st.nextToken()).intValue();

      return arrDims;
   }

   private QName getComponentTypeFromAttribute(Element arrayElement)
   {
      QName attrQName = new QName(Constants.URI_SOAP11_ENC, "arrayType");
      QName arrayType = DOMUtils.getAttributeValueAsQName(arrayElement, attrQName);
      if (arrayType == null)
         throw new WSException("Cannot obtain attribute: " + attrQName);

      String nsURI = arrayType.getNamespaceURI();
      String localPart = arrayType.getLocalPart();
      int dimIndex = localPart.indexOf("[");
      QName compXmlType = new QName(nsURI, localPart.substring(0, dimIndex));

      return compXmlType;
   }

   private Class getJavaTypeForComponentType(QName compXmlType, SerializationContext serContext)
   {
      TypeMappingImpl typeMapping = serContext.getTypeMapping();
      Class javaType = typeMapping.getJavaType(compXmlType);
      if (javaType == null)
         throw new WSException("Cannot obtain javaType for: " + compXmlType);

      return JavaUtils.getWrapperType(javaType);
   }
}
