/*
 * com.yodlee.soap.encoding.ser.NonBeanDeserializer
 *
 * $Author: balki $
 * $Date: 2005/03/19 $
 * $DateTime: 2005/03/19 04:18:37 $
 * $Id: //newarch/core/main/src/soap/com/yodlee/soap/encoding/ser/NonBeanDeserializer.java#14 $
 *
 * Copyright (c) 2002 Yodlee Incorporated. All Rights Reserved.
 *
 * This software is the confidential and proprietary information of
 * Yodlee, Inc. Use is subject to license terms.
 *
 */

package com.yodlee.soap.encoding.ser;

import com.yodlee.soap.encoding.EncodingHelper;
import com.yodlee.util.logging.MessageController;
import org.apache.axis.encoding.CallbackTarget;
import org.apache.axis.encoding.DeserializationContext;
import org.apache.axis.encoding.Deserializer;
import org.apache.axis.encoding.DeserializerImpl;
import org.apache.axis.message.SOAPHandler;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import java.util.HashMap;
import java.io.StringWriter;
import java.io.PrintWriter;

public class NonBeanDeserializer extends DeserializerImpl
{
    /** Fully qualified class name */
    private static final String FQCN = NonBeanDeserializer.class.getName ();

    /** RCS version information */
    public static final String RCS_ID = "$Id: //newarch/core/main/src/soap/com/yodlee/soap/encoding/ser/NonBeanDeserializer.java#14 $";

    private HashMap _valuesByMemberName = new HashMap ();
    private QName _typeQName;

    /**
     * Default constructore
     */
    public NonBeanDeserializer ()
    {
    }

    /**
     * Overrides the method in DeserializerImpl, so that we can find the QName of
     * this element and save it for later use. Method in super does lot of other
     * stuff. So calling it in the end
     *
     * This method is invoked after startElement when the element requires
     * deserialization (i.e. the element is not an href and the value is not nil.)
     * DeserializerImpl provides default behavior, which simply
     * involves obtaining a correct Deserializer and plugging its handler.
     * @param namespace is the namespace of the element
     * @param localName is the name of the element
     * @param prefix is the prefix of the element
     * @param attributes are the attributes on the element...used to get the type
     * @param context is the DeserializationContext
     */
    public void onStartElement (String namespace, String localName,
                                String prefix, Attributes attributes,
                                DeserializationContext context)
            throws SAXException
    {
        /**
         * The QName of this elment is determined from input parameters and
         * saved. This is needed later to determine helper class (look in
         * onEndElement() below)
         */
        _typeQName = context.getTypeFromAttributes (namespace,
                                                    localName,
                                                    attributes);

        if(_typeQName == null) {
            MessageController.log (FQCN,
                                   9999,
                                   "QName not available in the element " + localName,
                                   MessageController.HIGH);
        }
/*
        MessageController.debug (FQCN, "Enter NonBeanDeserializer::onStartElement() for " +
                                       _typeQName.toString());
*/

        /**
         * Call the super's onStartElement as it might have to do more things as
         * a part of the Desrialization process
         */
        super.onStartElement (namespace, localName, prefix, attributes, context);

        //MessageController.debug (FQCN, "Exit NonBeanDeserializer::onStartElement()");
    }

    /**
     * This method is invoked when an element start tag is encountered.
     * @param namespace is the namespace of the element
     * @param localName is the name of the element
     * @param prefix is the element's prefix
     * @param attributes are the attributes on the element...used to get the type
     * @param context is the DeserializationContext
     */
    public SOAPHandler onStartChild (String namespace,
                                     String localName,
                                     String prefix,
                                     Attributes attributes,
                                     DeserializationContext context)
            throws SAXException
    {
        //MessageController.debug (FQCN, "Enter NonBeanDeserializer::onStartChild()");

        /**
         * Try to determine the QNmae of the child element so that you can return
         * the correct Desrializer to be used in deserializing the child element.
         * Note that this can't determine the QName in case of children which are
         * complex types. This is because there will be only a refid here for complex
         * types.
         */
        QName typeQName = context.getTypeFromAttributes (namespace,
                                                         localName,
                                                         attributes);

        // Get the Desrializer to be used for this QName
        Deserializer dSer = context.getDeserializerForType (typeQName);

        /**
         * The dSer will be null in case of a  complex type child as explained above.
         * Therefore, if no deserializer, use the base DeserializerImpl.
         * DeserializerImpl will find the exact type when OnStartElement is called
         * for the child element and replaces the Deserializer (previously assigned in
         * onStartChild of the parent) to be used and also in the callback target
         */
        if (dSer == null)
            dSer = new DeserializerImpl ();

        /**
         * Register a CallbackTarget, the mechanism used by Axis to allow Deserializer of
         * a element to have access to the deserialized value of its children. The
         * input to this CallbackTarget include this object and a hint. The hint passed
         * here is the name of child element. Upon the deserialization of child element
         * the setValue() method of this class is invloked.
         */
        dSer.registerValueTarget (new CallbackTarget (this, localName));

        //MessageController.debug (FQCN, "Exit NonBeanDeserializer::onStartChild()");

        return (SOAPHandler) dSer;
    }


    /**
     * Overrides the onEndElment of DeserializerImpl.
     *
     * We now construct the value of the java type representing this xml type
     * element. By now we have already obtained the values of children elements
     * available in _valuesByMemberName map.
     *
     * onEndElement is called by endElement.  It is not called
     * if the element has an href.
     * @param namespace is the namespace of the element
     * @param localName is the local name of the element
     * @param context is the deserialization context
     */
    public void onEndElement (String namespace, String localName,
                              DeserializationContext context)
            throws SAXException
    {
        //MessageController.debug (FQCN, "Enter NonBeanDeserializer::onEndElement()");

        /**
         * Parent class, DeserializerImpl calls onStartElement, onStartChild and on EndElements
         * of the derived classes.
         *
         * All yodlee datatypes are desrialized using NonBeanDeserializer(this class)
         * even if the value is nil. Therefore, we reach here for any yodlee
         * datatype configured with NonBeanDeserializer even if it is nil-valued.
         *
         * Should just return, if the isNil member declared in the DeserializerImpl
         * is true. Otherwise, call the encoding helper class for this _typeQName to
         * get the deserialized value.
         */
        if(isNil == true) {
            return;
        }

        try {
            value = EncodingHelper.getObjectFromFields (_typeQName, _valuesByMemberName);
        } catch (RuntimeException e) {
            // unravel stack trace
            StringWriter sw = new StringWriter ();
            e.printStackTrace (new PrintWriter (sw));

            MessageController.log (FQCN,
                                   2564,
                                   "Encoding Helper class for unmarshalling xml type " + _typeQName +
                                   " threw Exception " + e.getClass() +
                                   " with this message : " + e.getMessage () +
                                   " and stacktrace " + sw.toString(),
                                   MessageController.HIGH);
            throw new SAXException ("Encoding Helper class for QName " +
                                    _typeQName.toString () +
                                   " threw Exception " + e.getClass() +
                                   " with this message : " + e.getMessage () +
                                   " and stacktrace " + sw.toString());
        }

        _valuesByMemberName.clear();
        _valuesByMemberName = null;
        
/*
        MessageController.debug (FQCN, "Exit NonBeanDeserializer::onEndElement() for " +
                                       _typeQName.toString());
*/
    }

    // Callback method called to set deserialized child value
    public void setValue (Object value, Object hint)
    {
/*
        MessageController.debug (FQCN, "In NonBeanDeserializer::setValue()");
        MessageController.debug (FQCN, "hint : " + hint.toString ());
        if (value != null) {
            MessageController.debug (FQCN, "value class name : " + value.getClass ().getName ());
        } else {
            MessageController.debug (FQCN, "value is null");
        }
*/
        _valuesByMemberName.put (hint, value);
    }

}
