/**
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.webservice.deployment;

// $Id: BeanXMLMetaData.java,v 1.1.2.9 2005/07/04 17:14:46 tdiesler Exp $

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.jboss.axis.utils.LinkedHashMap;
import org.jboss.util.xml.DOMUtils;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * Additional bean serialization meta data.
 *
 * Here is an example:
 *
 * <typeMapping
 *    qname='ns1:SequenceStruct2' xmlns:ns1='http://MarshallTest.org/xsd'
 *    type='java:org.jboss.ws4ee.test.marshalltest.rpcenc.SequenceStruct2'
 *    serializer='org.jboss.webservice.encoding.ser.MetaDataBeanSerializerFactory'
 *    deserializer='org.jboss.webservice.encoding.ser.MetaDataBeanDeserializerFactory'
 *    encodingStyle='http://schemas.xmlsoap.org/soap/encoding/'>
 *    <typeDesc>
 *       <elementDesc fieldName="varBase64Binary" xmlName="varBase64Binary" xmlType="xsd:base64Binary"/>
 *       <elementDesc fieldName="varHexBinary" xmlName="varHexBinary" xmlType="xsd:hexBinary"/>
 *       <elementDesc fieldName="varSoapString" xmlName="varSoapString" xmlType="soapenc:string"/>
 *       <elementDesc fieldName="varSoapBoolean" xmlName="varSoapBoolean" xmlType="soapenc:boolean"/>
 *       <elementDesc fieldName="varSoapFloat" xmlName="varSoapFloat" xmlType="soapenc:float"/>
 *       <elementDesc fieldName="varSoapDouble" xmlName="varSoapDouble" xmlType="soapenc:double"/>
 *       <elementDesc fieldName="varSoapDecimal" xmlName="varSoapDecimal" xmlType="soapenc:decimal"/>
 *       <elementDesc fieldName="varSoapInt" xmlName="varSoapInt" xmlType="soapenc:int"/>
 *       <elementDesc fieldName="varSoapShort" xmlName="varSoapShort" xmlType="soapenc:short"/>
 *       <elementDesc fieldName="varSoapByte" xmlName="varSoapByte" xmlType="soapenc:byte"/>
 *       <elementDesc fieldName="varSoapBase64" xmlName="varSoapBase64" xmlType="soapenc:base64"/>
 *       <elementDesc fieldName="varDateTimeArray" xmlName="varDateTimeArray" itemXmlType="xsd:datetime"/>
 *       <elementOrder>
 *          <element name="varString"/>
 *          <element name="varInteger"/>
 *          <element name="varInt"/>
 *          <element name="varLong"/>
 *          <element name="varShort"/>
 *          <element name="varDecimal"/>
 *          <element name="varFloat"/>
 *          <element name="varDouble"/>
 *          <element name="varBoolean"/>
 *          <element name="varByte"/>
 *          <element name="varQName"/>
 *          <element name="varDateTime"/>
 *          <element name="varSoapString"/>
 *          <element name="varSoapBoolean"/>
 *          <element name="varSoapFloat"/>
 *          <element name="varSoapDouble"/>
 *          <element name="varSoapDecimal"/>
 *          <element name="varSoapInt"/>
 *          <element name="varSoapShort"/>
 *          <element name="varSoapByte"/>
 *          <element name="varBase64Binary"/>
 *          <element name="varHexBinary"/>
 *          <element name="varSoapBase64"/>
 *          <element name="varSequenceStruct"/>
 *          <element name="varDateTimeArray"/>
 *       </elementOrder>
 *    </typeDesc>
 * </typeMapping>
 *
 * @author thomas.diesler@jboss.org
 * @since 18-June-2004
 */
public class BeanXMLMetaData
{
   private Map elementDescMap = new LinkedHashMap();

   // List<String> of field names
   private List elementOrderList = new ArrayList();

   // Hide constructoer
   private BeanXMLMetaData()
   {
   }

   /**
    * Parse the bean mets data from XML
    */
   public static BeanXMLMetaData parse(Element metaData)
   {
      BeanXMLMetaData beanMetaData = new BeanXMLMetaData();

      if (metaData != null)
      {
         ArrayList qnames = new ArrayList();

         // Parse element descriptions
         NodeList nlistElementDesc = metaData.getElementsByTagName("elementDesc");
         for (int i = 0; i < nlistElementDesc.getLength(); i++)
         {
            Element elDesc = (Element)nlistElementDesc.item(i);
            String fieldName = DOMUtils.getAttributeValue(elDesc, "fieldName");

            QName xmlName = DOMUtils.getAttributeValueAsQName(elDesc, "xmlName");
            if (xmlName == null)
               xmlName = new QName("", fieldName);

            QName xmlType = DOMUtils.getAttributeValueAsQName(elDesc, "xmlType");
            if (xmlType != null)
            {
               if (qnames.contains(xmlType))
                  throw new IllegalStateException("Duplicate type mapping definition: " + xmlType);

               qnames.add(xmlType);
            }

            ElementMetaData elMetaData = new ElementMetaData(fieldName, xmlName, xmlType);
            beanMetaData.elementDescMap.put(fieldName, elMetaData);

            boolean asAttribute = DOMUtils.getAttributeValueAsBoolean(elDesc, "asAttr");
            elMetaData.setAsAttribute(asAttribute);

            boolean asContent = DOMUtils.getAttributeValueAsBoolean(elDesc, "asContent");
            elMetaData.setAsContent(asContent);

            Integer minOccurs = DOMUtils.getAttributeValueAsInteger(elDesc, "minOccurs");
            elMetaData.setMinOccurs(minOccurs);

            QName itemXmlType = DOMUtils.getAttributeValueAsQName(elDesc, "itemXmlType");
            elMetaData.setItemXmlType(itemXmlType);
         }

         // Parse element order
         Element orderEl = DOMUtils.getFirstChildElement(metaData, "elementOrder");
         if (orderEl != null)
         {
            // copy the BeanPropertyDescriptors according to the meta data
            // elementOrder
            NodeList nlistElementOrder = orderEl.getElementsByTagName("element");
            for (int i = 0; i < nlistElementOrder.getLength(); i++)
            {
               Element el = (Element)nlistElementOrder.item(i);
               String fieldName = el.getAttribute("name");
               beanMetaData.elementOrderList.add(fieldName);
            }
         }
      }

      return beanMetaData;
   }

   public Iterator getElementMetaData()
   {
      return elementDescMap.values().iterator();
   }

   public List getElementOrder()
   {
      return new ArrayList(elementOrderList);
   }

   public void serializeAsXML(PrintWriter out)
   {
      String pad = "  ";
      out.println(pad + "<typeDesc>");
      Iterator itElDesc = elementDescMap.values().iterator();
      while (itElDesc.hasNext())
      {
         ElementMetaData elMetaData = (ElementMetaData)itElDesc.next();
         String prefixedXmlName = elMetaData.getPrefixedXmlName();
         out.print(pad + pad + "<elementDesc fieldName='" + elMetaData.getFieldName() + "' xmlName='" + prefixedXmlName + "'");

         if (elMetaData.getXmlType() != null)
         {
            String prefixedXmlType = elMetaData.getPrefixedXmlType();
            out.print(" xmlType='" + prefixedXmlType + "'");
         }

         if (elMetaData.isAsAttribute())
            out.print(" asAttr='true'");

         if (elMetaData.isAsContent())
            out.print(" asContent='true'");

         if (elMetaData.getMinOccurs() != null)
            out.print(" minOccurs='" + elMetaData.getMinOccurs() + "'");

         if (elMetaData.getItemXmlType() != null)
            out.print(" itemXmlType='" + elMetaData.getPrefixedItemXmlType() + "'");

         out.println("/>");
      }

      if (elementOrderList.size() > 0)
      {
         out.println(pad + pad + "<elementOrder>");
         Iterator itElOrder = elementOrderList.iterator();
         while (itElOrder.hasNext())
         {
            String fieldName = (String)itElOrder.next();
            out.println(pad + pad + pad + "<element name='" + fieldName + "'/>");
         }
         out.println(pad + pad + "</elementOrder>");
      }
      out.println(pad + "</typeDesc>");
   }

   /**
    * An XML element description
    */
   public static class ElementMetaData
   {

      private String fieldName;

      private QName xmlName;

      private QName xmlType;

      private QName itemXmlType;

      private boolean asAttribute;

      private boolean asContent;

      private Integer minOccurs;

      public ElementMetaData(String fieldName, QName xmlName, QName xmlType)
      {
         this.fieldName = fieldName;
         this.xmlName = xmlName;
         this.xmlType = xmlType;
      }

      public String getFieldName()
      {
         return fieldName;
      }

      public QName getXmlName()
      {
         return xmlName;
      }

      public String getPrefixedXmlName()
      {
         String retStr = null;
         if (xmlName != null)
         {
            retStr = xmlName.getLocalPart();
            if (xmlName.getPrefix().length() > 0)
               retStr = xmlName.getPrefix() + ":" + retStr;
         }
         return retStr;
      }

      public QName getXmlType()
      {
         return xmlType;
      }

      public String getPrefixedXmlType()
      {
         String retStr = null;
         if (xmlType != null)
         {
            retStr = xmlType.getLocalPart();
            if (xmlType.getPrefix().length() > 0)
               retStr = xmlType.getPrefix() + ":" + retStr;
         }
         return retStr;
      }

      public boolean isAsAttribute()
      {
         return asAttribute;
      }

      public void setAsAttribute(boolean asAttribute)
      {
         this.asAttribute = asAttribute;
      }

      public boolean isAsContent()
      {
         return asContent;
      }

      public void setAsContent(boolean asContent)
      {
         this.asContent = asContent;
      }

      public Integer getMinOccurs()
      {
         return minOccurs;
      }

      public void setMinOccurs(Integer minOccurs)
      {
         this.minOccurs = minOccurs;
      }

      public QName getItemXmlType()
      {
         return itemXmlType;
      }

      public String getPrefixedItemXmlType()
      {
         String retStr = null;
         if (itemXmlType != null)
         {
            retStr = itemXmlType.getLocalPart();
            if (itemXmlType.getPrefix().length() > 0)
               retStr = itemXmlType.getPrefix() + ":" + retStr;
         }
         return retStr;
      }

      /**
       * Solves mapping problems were a nested type in an array can not be properly determined
       */
      public void setItemXmlType(QName itemXmlType)
      {
         this.itemXmlType = itemXmlType;
      }
   }
}
