/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights 
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Axis" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.jboss.axis.encoding;

import org.jboss.axis.Constants;
import org.jboss.axis.Part;
import org.jboss.axis.message.EnvelopeHandler;
import org.jboss.axis.message.SAX2EventRecorder;
import org.jboss.axis.message.SAXOutputter;
import org.jboss.axis.message.SOAPElementAxisImpl;
import org.jboss.axis.message.SOAPHandler;
import org.jboss.axis.soap.SOAPConstants;
import org.jboss.axis.utils.Messages;
import org.jboss.logging.Logger;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import javax.xml.namespace.QName;
import java.io.StringWriter;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Vector;

/**
 * The Deserializer base class.
 *
 * @author Glen Daniels (gdaniels@allaire.com)
 *         Re-architected for JAX-RPC Compliance by
 * @author Rich Scheuerle (sche@us.ibm.com)
 */

public class DeserializerImpl extends SOAPHandler
        implements javax.xml.rpc.encoding.Deserializer, Deserializer, Callback
{
   private static Logger log = Logger.getLogger(DeserializerImpl.class.getName());

   protected Object value = null;

   // isEnded is set when the endElement is called
   protected boolean isEnded = false;

   protected Vector targets = null;

   protected QName defaultType = null;

   boolean componentsReadyFlag = false;

   /**
    * A set of sub-deserializers whose values must complete before our
    * value is complete.
    */
   private HashSet activeDeserializers = new HashSet();

   public DeserializerImpl()
   {
   }

   /**
    * JAX-RPC compliant method which returns mechanism type.
    */
   public String getMechanismType()
   {
      return Constants.AXIS_SAX;
   }

   /**
    * Get the deserialized value.
    *
    * @return Object representing deserialized value or null
    */
   public Object getValue()
   {
      return value;
   }

   /**
    * Set the deserialized value.
    *
    * @param value Object representing deserialized value
    */
   public void setValue(Object value)
   {
      this.value = value;
   }

   /**
    * If the deserializer has component values (like ArrayDeserializer)
    * this method gets the specific component via the hint.
    * The default implementation returns null.
    *
    * @return Object representing deserialized value or null
    */
   public Object getValue(Object hint)
   {
      return null;
   }

   /**
    * If the deserializer has component values (like ArrayDeserializer)
    * this method sets the specific component via the hint.
    * The default implementation does nothing.
    *
    * @param hint Object representing deserialized value or null
    */
   public void setChildValue(Object value, Object hint) throws SAXException
   {
   }

   public void setValue(Object value, Object hint) throws SAXException
   {
      if (hint instanceof Deserializer)
      {
         // This one's done
         activeDeserializers.remove(hint);

         // If we're past the end of our XML, and this is the last one,
         // our value has been assembled completely.
         if (componentsReady())
         {
            // Got everything we need, call valueComplete()
            valueComplete();
         }
      }
   }

   /**
    * In some circumstances an element may not have
    * a type attribute, but a default type qname is known from
    * information in the container.  For example,
    * an element of an array may not have a type= attribute,
    * so the default qname is the component type of the array.
    * This method is used to communicate the default type information
    * to the deserializer.
    */
   public void setDefaultType(QName qName)
   {
      defaultType = qName;
   }

   public QName getDefaultType()
   {
      return defaultType;
   }

   /**
    * For deserializers of non-primitives, the value may not be
    * known until later (due to multi-referencing).  In such
    * cases the deserializer registers Target object(s).  When
    * the value is known, the set(value) will be invoked for
    * each Target registered with the Deserializer.  The Target
    * object abstracts the function of setting a target with a
    * value.  See the Target interface for more info.
    *
    * @param target
    */
   public void registerValueTarget(Target target)
   {
      if (targets == null)
         targets = new Vector();

      targets.addElement(target);
   }

   /**
    * Get the Value Targets of the Deserializer.
    *
    * @return Vector of Target objects or null
    */
   public Vector getValueTargets()
   {
      return targets;
   }

   /**
    * Remove the Value Targets of the Deserializer.
    */
   public void removeValueTargets()
   {
      if (targets != null)
      {
         targets.clear();
         targets = null;
      }
   }

   /**
    * Move someone else's targets to our own (see DeserializationContext)
    * <p/>
    * The DeserializationContext only allows one Deserializer to
    * wait for a unknown multi-ref'ed value.  So to ensure
    * that all of the targets are updated, this method is invoked
    * to copy the Target objects to the waiting Deserializer.
    *
    * @param other is the Deserializer to copy targets from.
    */
   public void moveValueTargets(Deserializer other)
   {
      if ((other == null) || (other.getValueTargets() == null))
         return;

      if (targets == null)
         targets = new Vector();

      Enumeration e = other.getValueTargets().elements();
      while (e.hasMoreElements())
      {
         targets.addElement(e.nextElement());
      }
      other.removeValueTargets();
   }

   /**
    * Some deserializers (ArrayDeserializer) require
    * all of the component values to be known before the
    * value is complete.
    * (For the ArrayDeserializer this is important because
    * the elements are stored in an ArrayList, and all values
    * must be known before the ArrayList is converted into the
    * expected array.
    * <p/>
    * This routine is used to indicate when the components are ready.
    * The default (true) is useful for most Deserializers.
    */
   public boolean componentsReady()
   {
      return (componentsReadyFlag ||
              (!isHref && isEnded && activeDeserializers.isEmpty()));
   }

   /**
    * The valueComplete() method is invoked when the
    * end tag of the element is read.  This results
    * in the setting of all registered Targets (see
    * registerValueTarget).
    * Note that the valueComplete() only processes
    * the Targets if componentReady() returns true.
    * So if you override componentReady(), then your
    * specific Deserializer will need to call valueComplete()
    * when your components are ready (See ArrayDeserializer)
    */
   public void valueComplete() throws SAXException
   {
      if (componentsReady())
      {
         if (targets != null)
         {
            Enumeration e = targets.elements();
            while (e.hasMoreElements())
            {
               Target target = (Target)e.nextElement();
               target.set(value);
               if (log.isDebugEnabled())
               {
                  log.debug(Messages.getMessage("setValueInTarget00",
                          "" + value, "" + target));
               }
            }
            // Don't need targets any more, so clear them
            removeValueTargets();
         }
      }
   }

   public void addChildDeserializer(Deserializer dSer)
   {
      // Keep track of our active deserializers.  This enables us to figure
      // out whether or not we're really done in the case where we get to
      // our end tag, but still have open hrefs for members.
      activeDeserializers.add(dSer);

      // In concert with the above, we make sure each field deserializer
      // lets us know when it's done so we can take it off our list.
      dSer.registerValueTarget(new CallbackTarget(this, dSer));
   }

   protected boolean isHref = false;
   protected boolean isNil = false;  // xsd:nil attribute is set to true
   protected String id = null;  // Set to the id of the element

   /**
    * Subclasses may override these
    */

   /**
    * This method is invoked when an element start tag is encountered.
    * DeserializerImpl provides default behavior, which involves the following:
    * - directly handling the deserialization of a nill value
    * - handling the registration of the id value.
    * - handling the registration of a fixup if this element is an href.
    * - calling onStartElement to do the actual deserialization if not nill or href cases.
    *
    * @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
    *                   <p/>
    *                   Normally a specific Deserializer (FooDeserializer) should extend DeserializerImpl.
    *                   Here is the flow that will occur in such cases:
    *                   1) DeserializerImpl.startElement(...) will be called and do the id/href/nill stuff.
    *                   2) If real deserialization needs to take place DeserializerImpl.onStartElement will be
    *                   invoked, which will attempt to install the specific Deserializer (FooDeserializer)
    *                   3) The FooDeserializer.startElement(...) will be called to do the Foo specific stuff.
    *                   This results in a call to FooDeserializer.onStartElement(...) if startElement was
    *                   not overridden.
    *                   4) The onChildElement(...) method is called for each child element.  Nothing occurs
    *                   if not overridden.  The FooDeserializer.onStartChild(...) method should return
    *                   the deserializer for the child element.
    *                   5) When the end tag is reached, the endElement(..) method is invoked.  The default
    *                   behavior is to handle hrefs/ids, call onEndElement and then call the Deserializer
    *                   valueComplete method.
    *                   <p/>
    *                   So the methods that you potentially want to override are:
    *                   onStartElement, onStartChild, componentsReady, setValue(object, hint)
    *                   You probably should not override startElement or endElement.
    *                   If you need specific behaviour at the end of the element consider overriding
    *                   onEndElement.
    *                   <p/>
    *                   See the pre-existing Deserializers for more information.
    */
   public void startElement(String namespace, String localName,
                            String prefix, Attributes attributes,
                            DeserializationContext context)
           throws SAXException
   {
      super.startElement(namespace, localName, prefix, attributes, context);

      // If the nil attribute is present and true, set the value to null
      // and return since there is nothing to deserialize.
      if (context.isNil(attributes))
      {
         value = null;
         isNil = true;
         return;
      }

      SOAPConstants soapConstants = context.getMessageContext().getSOAPConstants();

      // If this element has an id, then associate the value with the id.
      // (Prior to this association, the MessageElement of the element is
      // associated with the id. Failure to replace the MessageElement at this
      // point will cause an infinite loop during deserialization if the
      // current element contains child elements that cause an href back to this id.)
      // Also note that that endElement() method is responsible for the final
      // association of this id with the completed value.
      id = attributes.getValue("id");
      if (id != null)
      {
         context.addObjectById(id, value);
         if (log.isDebugEnabled())
         {
            log.debug(Messages.getMessage("deserInitPutValueDebug00", "" + value, id));
         }
         context.registerFixup("#" + id, this);
      }

      String href = attributes.getValue(soapConstants.getAttrHref());
      if (href != null)
      {
         isHref = true;

         Object ref = context.getObjectByRef(href);
         if (log.isDebugEnabled())
         {
            log.debug(Messages.getMessage("gotForID00",
                    new String[]{"" + ref, href, (ref == null ? "*null*" : ref.getClass().toString())}));
         }

         if (ref == null)
         {
            // Nothing yet... register for later interest.
            context.registerFixup(href, this);
            return;
         }

         if (ref instanceof SOAPElementAxisImpl)
         {
            context.replaceElementHandler(new EnvelopeHandler(this));

            SAX2EventRecorder r = context.getRecorder();
            context.setRecorder(null);
            ((SOAPElementAxisImpl)ref).publishToHandler((DefaultHandler)context);
            context.setRecorder(r);
         }
         else
         {

            if (!href.startsWith("#") && defaultType != null && ref instanceof Part)
            {
               //For attachments this is the end of the road-- invoke deserializer
               Deserializer dser = context.getDeserializerForType(defaultType);
               if (null != dser)
               {
                  dser.startElement(namespace, localName,
                          prefix, attributes,
                          context);
                  ref = dser.getValue();
               }
            }

            // If the ref is not a MessageElement, then it must be an
            // element that has already been deserialized.  Use it directly.
            value = ref;
            componentsReadyFlag = true;
            valueComplete();
         }

      }
      else
      {
         isHref = false;
         onStartElement(namespace, localName, prefix, attributes,
                 context);
      }
   }

   /**
    * 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
   {
      // If I'm the base class, try replacing myself with an
      // appropriate deserializer gleaned from type info.
      if (this.getClass().equals(DeserializerImpl.class))
      {
         QName type = context.getTypeFromAttributes(namespace,
                 localName,
                 attributes);

         // If no type is specified, use the defaultType if available.
         // xsd:string is used if no type is provided.
         if (type == null)
         {
            type = defaultType;
            if (type == null)
            {
               type = Constants.XSD_STRING;
            }
         }

         if (log.isDebugEnabled())
         {
            log.debug(Messages.getMessage("gotType00", "Deser", "" + type));
         }

         // We know we're deserializing, but we don't have
         // a specific deserializer.  So create one using the
         // attribute type qname.
         if (type != null)
         {
            Deserializer dser = context.getDeserializerForType(type);
            if (dser != null)
            {
               // Move the value targets to the new deserializer
               dser.moveValueTargets(this);
               context.replaceElementHandler((SOAPHandler)dser);
               // And don't forget to give it the start event...
               boolean isRef = context.isProcessingRef();
               context.setProcessingRef(true);
               dser.startElement(namespace, localName, prefix,
                       attributes, context);
               context.setProcessingRef(isRef);
            }
            else
            {
               throw new SAXException(Messages.getMessage("noDeser00", "" + type));
            }
         }
      }
   }

   /**
    * onStartChild is called on each child element.
    * The default behavior supplied by DeserializationImpl is to do nothing.
    * A specific deserializer may perform other tasks.  For example a
    * BeanDeserializer will construct a deserializer for the indicated
    * property and return it.
    *
    * @param namespace  is the namespace of the child element
    * @param localName  is the local name of the child element
    * @param prefix     is the prefix used on the name of the child element
    * @param attributes are the attributes of the child element
    * @param context    is the deserialization context.
    * @return is a Deserializer to use to deserialize a child (must be
    *         a derived class of SOAPHandler) or null if no deserialization should
    *         be performed.
    */
   public SOAPHandler onStartChild(String namespace, String localName,
                                   String prefix, Attributes attributes,
                                   DeserializationContext context)
           throws SAXException
   {
      return null;
   }


   /**
    * endElement is called when the end element tag is reached.
    * It handles href/id information for multi-ref processing
    * and invokes the valueComplete() method of the deserializer
    * which sets the targets with the deserialized value.
    *
    * @param namespace is the namespace of the child element
    * @param localName is the local name of the child element
    * @param context   is the deserialization context
    */
   public final void endElement(String namespace, String localName,
                                DeserializationContext context)
           throws SAXException
   {
      super.endElement(namespace, localName, context);

      isEnded = true;
      if (!isHref)
      {
         onEndElement(namespace, localName, context);
      }

      // Time to call valueComplete to copy the value to
      // the targets.  First a call is made to componentsReady
      // to ensure that all components are ready.
      if (componentsReady())
      {
         valueComplete();
      }

      // If this element has an id, then associate the value with the id.
      // Subsequent hrefs to the id will obtain the value directly.
      // This is necessary for proper multi-reference deserialization.
      if (id != null)
      {
         context.addObjectById(id, value);
         if (log.isDebugEnabled())
         {
            log.debug(Messages.getMessage("deserPutValueDebug00", "" + value, id));
         }
      }
   }

   /**
    * onEndElement is called by endElement.  It is not called
    * if the element has an href.
    *
    * @param namespace is the namespace of the child element
    * @param localName is the local name of the child element
    * @param context   is the deserialization context
    */
   public void onEndElement(String namespace, String localName,
                            DeserializationContext context)
           throws SAXException
   {
      // If we only have SAX events, but someone really wanted a
      // value, try sending them the contents of this element
      // as a String...
      // ??? Is this the right thing to do here?

      if (this.getClass().equals(DeserializerImpl.class) &&
              targets != null &&
              !targets.isEmpty())
      {

         StringWriter writer = new StringWriter();
         SerializationContextImpl serContext =
                 new SerializationContextImpl(writer,
                         context.getMessageContext());
         serContext.setSendDecl(false);

         SAXOutputter so = null;
         so = new SAXOutputter(serContext);
         context.getCurElement().publishContents(so);
         if (!isNil)
         {
            value = writer.getBuffer().toString();
         }
      }
   }

}
