/*
 * Copyright (c) 1997, 2022 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.jaxb.runtime.v2.schemagen;

import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbContainer;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbDistribution;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbEnvironmentModel;
import org.junit.Assert;
import org.junit.Test;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.XmlValue;
import javax.xml.XMLConstants;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbConcreteContainer;
import org.glassfish.jaxb.runtime.v2.schemagen.xmlschema.JaxbConcreteDeployment;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;

import junit.framework.TestCase;
import junit.textui.TestRunner;

public class MarshallingAbstractTest extends TestCase {
    public static void main(String[] args) {
        TestRunner.run(MarshallingAbstractTest.class);
    }

    @XmlSeeAlso({B.class, C.class})
    abstract static class A {
        //marshal/unmarshal of list elements containing elements of type = A will work without @XmlValue annotation...
        @XmlValue
        protected String value;
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "ClassType1")
    public static class B extends A {
        public B() {}
        public B(String value) {
            this.value = value;
        }
    }

    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "ClassType2")
    public static class C extends A {
        public C() {}
        public C(String value) {
            this.value = value;
        }
    }

    @XmlRootElement(name="root")
    @XmlAccessorType(XmlAccessType.FIELD)
    static class Mapping {
        @XmlElementWrapper(name = "list")
        @XmlElement(name="element")
        Collection<A> list = new ArrayList<>();
        A element1;
        A element2;
    }

    @Test
    public void testMarshalSingleElement() throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Mapping.class);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        StringWriter resultWriter = new StringWriter();
        Mapping mapping = new Mapping();
        mapping.element1 = new B("B1");
        mapping.element2 = new C("C1");
        marshaller.marshal(mapping, resultWriter);

        String expectedXml1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<root>\n"
                + "    <list/>\n"
                + "    <element1 xsi:type=\"ClassType1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">B1</element1>\n"
                + "    <element2 xsi:type=\"ClassType2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">C1</element2>\n"
                + "</root>\n";
        Assert.assertEquals(resultWriter.toString(), expectedXml1);
    }

    @Test
    public void testMarshalCollection() throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Mapping.class);
        Marshaller marshaller = jc.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        StringWriter resultWriter = new StringWriter();
        Mapping mapping = new Mapping();
        mapping.element1 = new B("B1");
        mapping.element2 = new C("C1");
        mapping.list.add(new B("B"));
        mapping.list.add(new C("C"));
        String expectedXml2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<root>\n"
                + "    <list>\n"
                + "        <element xsi:type=\"ClassType1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">B</element>\n"
                + "        <element xsi:type=\"ClassType2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">C</element>\n"
                + "    </list>\n"
                + "    <element1 xsi:type=\"ClassType1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">B1</element1>\n"
                + "    <element2 xsi:type=\"ClassType2\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">C1</element2>\n"
                + "</root>\n";
        marshaller.marshal(mapping, resultWriter);
        Assert.assertEquals(resultWriter.toString(), expectedXml2);
    }

    @Test
    public void testUnmarshalSingleElement() throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Mapping.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        //works without list..
        String sourceXml1 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<root>\n"
                + "    <element1 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType1\">B1</element1>\n"
                + "    <element2 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType2\">C1</element2>\n"
                + "</root>\n";
        JAXBElement<Mapping> element = unmarshaller.unmarshal(new StreamSource(new StringReader(sourceXml1)), Mapping.class);
        Assert.assertNotNull(element.getValue());
        Assert.assertEquals(B.class, element.getValue().element1.getClass());
        Assert.assertEquals(C.class, element.getValue().element2.getClass());
    }

    @Test
    public void testUnmarshalCollection() throws Exception {
        JAXBContext jc = JAXBContext.newInstance(Mapping.class);
        Unmarshaller unmarshaller = jc.createUnmarshaller();
        //don't work -> try to instantiate the abstract class
        String sourceXml2 = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<root>\n"
                + "    <list>\n"
                + "        <element xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType1\">B</element>\n"
                + "        <element xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType2\">C</element>\n"
                + "    </list>\n"
                + "    <element1 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType1\">B1</element1>\n"
                + "    <element2 xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:type=\"ClassType2\">C1</element2>\n"
                + "</root>\n";

        try {
            JAXBElement<Mapping> element = unmarshaller.unmarshal(new StreamSource(new StringReader(sourceXml2)), Mapping.class);
            Assert.assertEquals(2, element.getValue().list.size());
        }
        catch (Throwable e) {
            Assert.fail();
        }
    }

    @Test
    public void testXmlIDRefMarshal() throws Exception {

        JAXBContext cont = JAXBContext.newInstance(JaxbEnvironmentModel.class);
        JaxbEnvironmentModel envModel = new JaxbEnvironmentModel();
        JaxbDistribution dist = new JaxbDistribution();
        JaxbConcreteDeployment dep = new JaxbConcreteDeployment();
        dep.setContextRoot("Context-Root");
        dist.addDeployment(dep);
        envModel.setDistribution(dist);
        JaxbContainer container = new JaxbConcreteContainer();
        container.addDeployment(dep);
        envModel.setContainer(container);

        String sourceXsd = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
                "<xs:schema version=\"1.0\" xmlns:xs=\"http://www.w3.org/2001/XMLSchema\">\n" +
                "\n" +
                "  <xs:element name=\"environmentModel\" type=\"environmentModelType\"/>\n" +
                "\n" +
                "  <xs:complexType name=\"environmentModelType\">\n" +
                "    <xs:sequence>\n" +
                "      <xs:element name=\"container\" type=\"containerType\" minOccurs=\"0\"/>\n" +
                "      <xs:element name=\"distribution\" type=\"distributionType\" minOccurs=\"0\"/>\n" +
                "    </xs:sequence>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"containerType\" abstract=\"true\">\n" +
                "    <xs:sequence>\n" +
                "      <xs:element name=\"deployments\">\n" +
                "        <xs:complexType>\n" +
                "          <xs:sequence>\n" +
                "            <xs:element name=\"deployment\" type=\"xs:IDREF\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n" +
                "          </xs:sequence>\n" +
                "        </xs:complexType>\n" +
                "      </xs:element>\n" +
                "    </xs:sequence>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"deploymentType\" abstract=\"true\">\n" +
                "    <xs:sequence>\n" +
                "      <xs:element name=\"contextRoot\" type=\"xs:ID\"/>\n" +
                "    </xs:sequence>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"concreteDeploymentType\">\n" +
                "    <xs:complexContent>\n" +
                "      <xs:extension base=\"deploymentType\">\n" +
                "        <xs:sequence/>\n" +
                "      </xs:extension>\n" +
                "    </xs:complexContent>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"concreteContainerType\">\n" +
                "    <xs:complexContent>\n" +
                "      <xs:extension base=\"containerType\">\n" +
                "        <xs:sequence/>\n" +
                "      </xs:extension>\n" +
                "    </xs:complexContent>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"distributionType\">\n" +
                "    <xs:sequence>\n" +
                "      <xs:element name=\"deployments\">\n" +
                "        <xs:complexType>\n" +
                "          <xs:sequence>\n" +
                "            <xs:element name=\"deployment\" type=\"deploymentType\" minOccurs=\"0\" maxOccurs=\"unbounded\"/>\n" +
                "          </xs:sequence>\n" +
                "        </xs:complexType>\n" +
                "      </xs:element>\n" +
                "    </xs:sequence>\n" +
                "  </xs:complexType>\n" +
                "\n" +
                "  <xs:complexType name=\"mainTest\">\n" +
                "    <xs:sequence/>\n" +
                "  </xs:complexType>\n" +
                "</xs:schema>";
        SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = sf.newSchema(new StreamSource(new StringReader(sourceXsd)));

        Marshaller marshal = cont.createMarshaller();
        marshal.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshal.setSchema(schema);
        StringWriter resultWriter = new StringWriter();
        marshal.marshal(envModel, resultWriter);

        String expectedXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" +
                "<environmentModel>\n" +
                "    <container xsi:type=\"concreteContainerType\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
                "        <deployments>\n" +
                "            <deployment>Context-Root</deployment>\n" +
                "        </deployments>\n" +
                "    </container>\n" +
                "    <distribution>\n" +
                "        <deployments>\n" +
                "            <deployment xsi:type=\"concreteDeploymentType\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n" +
                "                <contextRoot>Context-Root</contextRoot>\n" +
                "            </deployment>\n" +
                "        </deployments>\n" +
                "    </distribution>\n" +
                "</environmentModel>\n";

        Assert.assertEquals(resultWriter.toString(), expectedXml);
    }

}
