/*
  * 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.test.xml;

import java.io.FileReader;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.util.Arrays;
import java.util.List;
import javax.xml.namespace.QName;
import org.jboss.test.xml.collections.Collections;
import org.jboss.xb.binding.Constants;
import org.jboss.xb.binding.JBossXBException;
import org.jboss.xb.binding.MappingObjectModelProvider;
import org.jboss.xb.binding.Unmarshaller;
import org.jboss.xb.binding.UnmarshallerFactory;
import org.jboss.xb.binding.XercesXsMarshaller;
import org.jboss.xb.binding.sunday.marshalling.MarshallerImpl;
import org.jboss.xb.binding.sunday.unmarshalling.SchemaBinding;
import org.jboss.xb.binding.sunday.unmarshalling.TypeBinding;
import org.jboss.xb.binding.sunday.unmarshalling.XsdBinder;

/**
 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
 * @version <tt>$Revision: 56874 $</tt>
 */
public class CollectionsUnitTestCase
   extends AbstractJBossXBTest
{
   private static final String LIST_TYPE_XSD = "<xsd:schema xmlns:xsd='" +
      Constants.NS_XML_SCHEMA +
      "'" +
      "  targetNamespace='http://jboss.org/test/xml/list'" +
      "  xmlns='http://jboss.org/test/xml/list'" +
      "  xmlns:jbxb='" +
      Constants.NS_JBXB +
      "'>" +
      "  <xsd:element name='e'>" +
      "    <xsd:annotation>" +
      "      <xsd:appinfo>" +
      "        <jbxb:class impl='" +
      ListWrapper.class.getName() +
      "'/>" +
      "      </xsd:appinfo>" +
      "    </xsd:annotation>" +
      "    <xsd:complexType>" +
      "      <xsd:annotation>" +
      "        <xsd:appinfo>" +
      "          <jbxb:characters>" +
      "            <jbxb:property name='field2'/>" +
      "          </jbxb:characters>" +
      "        </xsd:appinfo>" +
      "      </xsd:annotation>" +
      "      <xsd:simpleContent>" +
      "        <xsd:extension base='booleanList'>" +
      "          <xsd:attribute name='field1'>" +
      "            <xsd:simpleType>" +
      "              <xsd:list itemType='xsd:string'/>" +
      "            </xsd:simpleType>" +
      "          </xsd:attribute>" +
      "          <xsd:attribute name='field3'>" +
      "            <xsd:simpleType>" +
      "              <xsd:list itemType='xsd:QName'/>" +
      "            </xsd:simpleType>" +
      "          </xsd:attribute>" +
      "        </xsd:extension>" +
      "      </xsd:simpleContent>" +
      "    </xsd:complexType>" +
      "  </xsd:element>" +
      "  <xsd:simpleType name='booleanList'>" +
      "    <xsd:list itemType='xsd:boolean'/>" +
      "  </xsd:simpleType>" +
      "</xsd:schema>";

   private static final String LIST_TYPE_XML = "<e xmlns='http://jboss.org/test/xml/list'" +
      "  field1='str1 str2 str3'>true false true</e>";

   private static final String LIST_TYPE_QNAME_ARR_XML = "<e xmlns='http://jboss.org/test/xml/list'" +
      " xmlns:ns1='http://ns1' xmlns:ns2='http://ns2' xmlns:ns3='http://ns3'" +
      " field3='ns1:name1 ns2:name2 ns3:name3'/>";

   private static final String MULTIDIM_ARR_XML = "<arr xmlns='http://www.jboss.org/test/xml/arr' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'>" +
      "  <item>" +
      "    <item>s1</item>" +
      "  </item>" +
      "  <item>" +
      "    <item>s2</item>" +
      "    <item>s3</item>" +
      "  </item>" +
      "  <item>" +
      "    <item xsi:nil='1'/>" +
      "  </item>" +
      "  <item xsi:nil='1'/>" +
      "</arr>";

   private static final String[][] MULTIDIM_ARR = new String[][]
   {
      new String[]{"s1"},
      new String[]{"s2", "s3"},
      new String[]{null},
      null
   };

   public CollectionsUnitTestCase(String name)
   {
      super(name);
   }

/*
   protected void configureLogging()
   {
      enableTrace("org.jboss.xb.binding");
   }
*/

   public void testUnmarshalling() throws Exception
   {
      String xml = getFullPath("xml/collections.xml");
      Reader xmlReader = new FileReader(xml);
      unmarshalCollections(xmlReader);
   }

   public void testMarshalling() throws Exception
   {
      String ns = "http://www.jboss.org/test/xml/collections";

      MarshallerImpl marshaller = new MarshallerImpl();
      marshaller.addRootElement(new QName(ns, "collections"));
      marshaller.setRootTypeQName(new QName(ns, "collections"));
      marshaller.declareNamespace(null, ns);

      String xsd = getFullPath("xml/collections.xsd");
      StringWriter xml = new StringWriter();
      marshaller.marshal(xsd, new MappingObjectModelProvider(), Collections.getInstance(), xml);

      String marshalled = xml.getBuffer().toString();
      try
      {
         unmarshalCollections(new StringReader(marshalled));
      }
      catch(Throwable t)
      {
         fail("Failed to unmarshal:\n" + marshalled + "\n" + t.getMessage());
      }
   }

   public void testMultidimArrMarshallingXerces() throws Exception
   {
      StringWriter writer = new StringWriter();
      XercesXsMarshaller marshaller = new XercesXsMarshaller();
      marshaller.marshal(new StringReader(getMultiDimArrayXsd(false)),
         new MappingObjectModelProvider(),
         MULTIDIM_ARR,
         writer
      );
      String marshalled = writer.getBuffer().toString();
      assertXmlEqual(MULTIDIM_ARR_XML, marshalled);
   }

   public void testMultidimArrMarshallingSunday() throws Exception
   {
      StringWriter writer = new StringWriter();
      MarshallerImpl marshaller = new MarshallerImpl();
      marshaller.marshal(new StringReader(getMultiDimArrayXsd(false)), null, MULTIDIM_ARR, writer);
      String marshalled = writer.getBuffer().toString();
      assertXmlEqual(MULTIDIM_ARR_XML, marshalled);
   }

   public void testMultidimArrayUnmarshalling() throws Exception
   {
      Object o = unmarshalMDArray(false);
      assertTrue(o instanceof String[][]);
      String[][] arr = (String[][])o;
      assertMultidimArray(arr);
   }

   public void testAnnotatedMultiDimArray() throws Exception
   {
      Object o = unmarshalMDArray(true);
      assertTrue(o instanceof ArrayWrapper);
      String[][] arr = ((ArrayWrapper)o).arr;
      assertMultidimArray(arr);
   }

   public void testUnmarshalListType() throws Exception
   {
      SchemaBinding schema = XsdBinder.bind(new StringReader(LIST_TYPE_XSD), null);
      schema.setIgnoreUnresolvedFieldOrClass(false);

      Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
      Object o = unmarshaller.unmarshal(new StringReader(LIST_TYPE_XML), schema);
      assertNotNull(o);
      assertTrue(o instanceof ListWrapper);
      ListWrapper lists = (ListWrapper)o;
      assertEquals(Arrays.asList(new String[]{"str1", "str2", "str3"}), lists.field1);
      assertEquals(Arrays.asList(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE}), lists.field2);
   }

   public void testMarshallingListTypeXerces() throws Exception
   {
      ListWrapper lists = new ListWrapper();
      lists.field1 = Arrays.asList(new String[]{"str1", "str2", "str3"});
      lists.field2 = Arrays.asList(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE});

      StringWriter writer = new StringWriter();
      XercesXsMarshaller marshaller = new XercesXsMarshaller();
      MappingObjectModelProvider provider = new MappingObjectModelProvider();
      provider.mapFieldToElement(ListWrapper.class, "field2", "http://jboss.org/test/xml/list", "e", null);
      marshaller.marshal(new StringReader(LIST_TYPE_XSD), provider, lists, writer);

      String xml = writer.getBuffer().toString();
      assertXmlEqual(LIST_TYPE_XML, xml);
   }

   public void testMarshallingListTypeSunday() throws Exception
   {
      ListWrapper lists = new ListWrapper();
      lists.field1 = Arrays.asList(new String[]{"str1", "str2", "str3"});
      lists.field2 = Arrays.asList(new Boolean[]{Boolean.TRUE, Boolean.FALSE, Boolean.TRUE});

      SchemaBinding schema = XsdBinder.bind(new StringReader(LIST_TYPE_XSD), null);
      schema.setIgnoreUnresolvedFieldOrClass(false);

      StringWriter writer = new StringWriter();
      MarshallerImpl marshaller = new MarshallerImpl();
      marshaller.marshal(schema, null, lists, writer);

      String xml = writer.getBuffer().toString();
      assertXmlEqual(LIST_TYPE_XML, xml);
   }

   public void testUnmarshalListTypeToArrayField() throws Exception
   {
      String xml = LIST_TYPE_QNAME_ARR_XML;
      unmarshalQNameArray(xml);

   }

   public void testMarshallingListTypeArrayToListXerces() throws Exception
   {
      ListWrapper lists = new ListWrapper();
      lists.field3 =
         new QName[]{new QName("http://ns1", "name1"),
                     new QName("http://ns2", "name2"),
                     new QName("http://ns3", "name3")
         };

      StringWriter writer = new StringWriter();
      XercesXsMarshaller marshaller = new XercesXsMarshaller();
      MappingObjectModelProvider provider = new MappingObjectModelProvider();
      marshaller.marshal(new StringReader(LIST_TYPE_XSD), provider, lists, writer);

      String xml = writer.getBuffer().toString();
      unmarshalQNameArray(xml);
   }

   public void testMarshallingListTypeArrayToListSunday() throws Exception
   {
      ListWrapper lists = new ListWrapper();
      lists.field3 =
         new QName[]{new QName("http://ns1", "name1"),
                     new QName("http://ns2", "name2"),
                     new QName("http://ns3", "name3")
         };

      SchemaBinding schema = XsdBinder.bind(new StringReader(LIST_TYPE_XSD), null);
      schema.setIgnoreUnresolvedFieldOrClass(false);

      StringWriter writer = new StringWriter();
      MarshallerImpl marshaller = new MarshallerImpl();
      marshaller.marshal(schema, null, lists, writer);

      String xml = writer.getBuffer().toString();
      unmarshalQNameArray(xml);
   }

   // Private

   private void unmarshalQNameArray(String xml)
      throws JBossXBException
   {
      SchemaBinding schema = XsdBinder.bind(new StringReader(LIST_TYPE_XSD), null);
      schema.setIgnoreUnresolvedFieldOrClass(false);

      Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
      Object o = unmarshaller.unmarshal(new StringReader(xml), schema);
      assertNotNull(o);
      assertTrue(o instanceof ListWrapper);
      ListWrapper lists = (ListWrapper)o;
      assertNotNull(lists.field3);
      assertEquals(3, lists.field3.length);
      assertEquals(new QName("http://ns1", "name1"), lists.field3[0]);
      assertEquals(new QName("http://ns2", "name2"), lists.field3[1]);
      assertEquals(new QName("http://ns3", "name3"), lists.field3[2]);
   }

   private Object unmarshalMDArray(boolean annotations)
      throws JBossXBException
   {
      String xsd = getMultiDimArrayXsd(annotations);
      SchemaBinding schema = XsdBinder.bind(new StringReader(xsd), null);
      schema.setIgnoreUnresolvedFieldOrClass(false);
      Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
      return unmarshaller.unmarshal(new StringReader(MULTIDIM_ARR_XML), schema);
   }

   private String getMultiDimArrayXsd(boolean annotations)
   {
      StringBuffer xsdBuf = new StringBuffer()
         .append("<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'\n")
         .append("  targetNamespace='http://www.jboss.org/test/xml/arr'\n")
         .append("  xmlns='http://www.jboss.org/test/xml/arr'\n")
         .append("  xmlns:jbxb='http://www.jboss.org/xml/ns/jbxb'\n")
         .append("  elementFormDefault='qualified'\n")
         .append("  version='1.0'>\n")
         .append("  <xsd:complexType name='strArr'>\n")
         .append("    <xsd:sequence>\n")
         .append(
            "      <xsd:element name='item' type='xsd:string' minOccurs='0' maxOccurs='unbounded' nillable='1'/>\n"
         )
         .append("    </xsd:sequence>\n")
         .append("  </xsd:complexType>\n")
         .append("  <xsd:complexType name='mdStrArr'>\n");

      if(annotations)
      {
         xsdBuf
            .append("    <xsd:annotation>\n")
            .append("      <xsd:appinfo>\n")
            .append("        <jbxb:class impl='" + ArrayWrapper.class.getName() + "'/>\n")
            .append("      </xsd:appinfo>\n")
            .append("    </xsd:annotation>\n");
      }

      xsdBuf
         .append("    <xsd:sequence>\n")
         .append("      <xsd:element name='item' type='strArr' minOccurs='0' maxOccurs='unbounded' nillable='1'>\n");

      if(annotations)
      {
         xsdBuf
            .append("        <xsd:annotation>\n")
            .append("          <xsd:appinfo>\n")
            .append("            <jbxb:property name='arr'/>\n")
            .append("          </xsd:appinfo>\n")
            .append("        </xsd:annotation>\n");
      }

      xsdBuf
         .append("      </xsd:element>\n")
         .append("    </xsd:sequence>\n")
         .append("  </xsd:complexType>\n")
         .append("  <xsd:element name='arr' type='mdStrArr'/>\n")
         .append("</xsd:schema>");

      String xsd = xsdBuf.toString();
      return xsd;
   }

   private void assertMultidimArray(String[][] arr)
   {
      assertNotNull(arr);
      assertEquals(4, arr.length);
      assertTrue(arr[0] instanceof String[]);
      assertEquals(1, arr[0].length);
      assertEquals("s1", arr[0][0]);
      assertEquals(2, arr[1].length);
      assertEquals("s2", arr[1][0]);
      assertEquals("s3", arr[1][1]);
      assertTrue(arr[2] instanceof String[]);
      assertEquals(1, arr[2].length);
      assertNull(arr[2][0]);
      assertNull(arr[3]);
   }

   private void unmarshalCollections(Reader xmlReader) throws JBossXBException
   {
      String xsd = getFullPath("xml/collections.xsd");
      SchemaBinding schema = XsdBinder.bind(xsd);
      schema.setIgnoreUnresolvedFieldOrClass(false);
      String ns = "http://www.jboss.org/test/xml/collections";
      QName rootQName = new QName(ns, "collections");
      TypeBinding type = schema.getType(rootQName);
      assertNotNull(type);
      schema.addElement(rootQName, type);

      Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
      Collections collections;
      collections = (Collections)unmarshaller.unmarshal(xmlReader, schema);
      assertEquals(Collections.getInstance(), collections);
   }

   private String getFullPath(String name)
   {
      URL url = Thread.currentThread().getContextClassLoader().getResource(name);
      if(url == null)
      {
         fail("Resource not found: " + name);
      }
      return url.getFile();
   }

   // Inner

   public static final class ArrayWrapper
   {
      public String[][] arr;
   }

   public static final class ListWrapper
   {
      public List<String> field1;
      public List<Boolean> field2;
      public QName[] field3;
   }
}
