package com.cdmtech.proj.spider.encoding;

import org.apache.axis.Constants;
import org.apache.axis.message.*;
import org.apache.axis.encoding.Deserializer;
import org.apache.axis.encoding.DeserializerFactory;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.ValueReceiver;

import javax.xml.rpc.namespace.QName;
import org.xml.sax.*;

import java.lang.reflect.*;
import java.util.*;

/******************************************************************************
* Deserializes one and multi dimensional Object arrays, ArrayLists and Vectors.
* Works with com.cdmtech.proj.spider.encoding.ArrayDeserializer.
*
* Type Mapping:
*
*    <typeMapping
*    qname="SOAP-ENC:Array(YyYyYyYyYyYyYyYyYyYyYyYyYyYyYy)"
*    xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
*    languageSpecificType="java:XxXxXxXxXxXxXxXxXxXxXxXxXxXx"
*    serializer="com.cdmtech.proj.spider.encoding.ArraySerializer"
*    deserializer="com.cdmtech.proj.spider.encoding.ArrayDeserializer$Factory"
*    />
*
* Where XxXxXxXxXxXxXxXxXxXxXxXxXxXx is one of these Java Classes:
*    java.util.Vector
*    java.util.ArrayList
*    [Ljava.lang.Object;
*    [[Ljava.lang.Object;
*    [[[Ljava.lang.Object;
*    [[[[Ljava.lang.Object;
*    ...
*
* And where (YyYyYyYyYyYyYyYyYyYyYyYyYyYyYy) is one of the following
*    (Vector)
*    (ArrayList)
*    (1D)
*    (2D)
*    (3D)
*    (4D)
*    ...
*
******************************************************************************/
public class ArrayDeserializer extends Deserializer implements ValueReceiver
{

   /***************************************************************************
   * Inner factory class to instanciate an ArrayDeserializer.
   *
   ***************************************************************************/
   public static class Factory implements DeserializerFactory
   {
      public Deserializer getDeserializer(Class dummy)
      {
         return new ArrayDeserializer();
      }
   }

   private int currentChildIndex = 0;

   /***************************************************************************
   * Instanciates a List or Object array of the correct dimensions but
   * without any of its children.
   *
   ***************************************************************************/
   public void onStartElement(String namespace, String localName,
                              String qName, Attributes attributes,
                              DeserializationContext context)
      throws SAXException
   {
      // Get the type and array type values.
      String type = null;
      String arrayType = null;
      int length = attributes.getLength();

      for (int i = 0; i < length && (type == null || arrayType == null); i++)
      {
         if (attributes.getLocalName(i).equals("arrayType"))
         {
            arrayType = attributes.getValue(i);
         }
         else if (attributes.getLocalName(i).equals("type"))
         {
            type = attributes.getValue(i);
         }
      }

      // Instanciate the correct type and store a reference to it in value.
      if (type.equals("SOAP-ENC:Array(Vector)"))
      {
         value = new Vector();
      }
      else if (type.equals("SOAP-ENC:Array(ArrayList)"))
      {
         value = new ArrayList();
      }
      else if (type.startsWith("SOAP-ENC:Array(") && type.endsWith("D)"))
      {
         String delimitedDims = arrayType.substring
            (arrayType.indexOf('[') + 1, arrayType.indexOf(']'));
         StringTokenizer tokenizer = new StringTokenizer(delimitedDims, ", ");
         int[] dimensions = new int[tokenizer.countTokens()];
         int index = 0;

         while (tokenizer.hasMoreTokens())
         {
            dimensions[index++] = Integer.parseInt(tokenizer.nextToken());
         }

         value = Array.newInstance(java.lang.Object.class, dimensions);
      }

      // Store the Object array in context for later lookup by reference id.
      context.addObjectById(attributes.getValue("", "id"), value);
   }

   /***************************************************************************
   * Finds and instanciates the correct deserializer for the child.
   * Registers a callback with that deserializer, sending a position hint.
   *
   ***************************************************************************/
   public SOAPHandler onStartChild(String namespace, String localName,
                                   String prefix, Attributes attributes,
                                   DeserializationContext context)
      throws SAXException
   {

      // If the attribute nil="true" exists then child is null.
      for (int i = 0; i < Constants.URIS_SCHEMA_XSI.length; i++)
      {
         String nil =
            attributes.getValue(Constants.URIS_SCHEMA_XSI[i], "nil");

         if (nil != null && nil.equals("true"))
         {
            valueReady(null, new Integer(currentChildIndex++));
            return null;
         }
      }

      // Assume that the attributes given are where type info can be found.
      Attributes childAttributes = attributes;

      // But check if it's only a reference.
      String hrefID = attributes.getValue("", "href");

      if (hrefID != null)
      {
         Object reference = context.getObjectByRef(hrefID);

         if (reference instanceof SOAPBodyElement)
         {
            // If reference is to a SOAP element then use its attributes.
            childAttributes = ((SOAPBodyElement)reference).getAttributes();
         }
         else
         {
            // If reference is to another object type then it is the child.
            valueReady(reference, new Integer(currentChildIndex++));
            return null;
         }
      }

      // Get the type information.
      String type = null;

      for (int i= 0; i < Constants.URIS_SCHEMA_XSI.length && type == null; i++)
      {
         type = childAttributes.getValue(Constants.URIS_SCHEMA_XSI[i], "type");
      }

      QName qualifiedType = context.getQNameFromString(type);

      // Find the correct deserializer to use for the child's type.
      Deserializer deserializer =
         context.getTypeMappingRegistry().getDeserializer(qualifiedType);

      // Register valueReady() to get the deserialized child and position hint.
      deserializer.registerCallback(this, new Integer(currentChildIndex++));

      // Return the deserializer that can deserialize the child.
      return deserializer;
   }

   /***************************************************************************
   * Recieves the array's child element along with the position hint and
   * adds it to the array at the correct position.
   *
   ***************************************************************************/
   public void valueReady(Object arrayElement, Object positionHint)
   {
      if (value instanceof List)
      {
         ((List)value).add(arrayElement);
      }
      else
      {
         Array.set(value, ((Integer)positionHint).intValue(), arrayElement);
      }
   }
}
