
/*   Copyright 2004 The Apache Software Foundation
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package ValidatingXSRTests.detailed;

import com.foo.sample.HeadingDocument;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.impl.validator.ValidatingXMLStreamReader;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.openuri.test.location.LocationDocument;
import org.openuri.test.mixedContent.LetterDocument;
import org.openuri.test.mixedContent.NoMixedDocument;
import org.openuri.test.person.Name;
import org.openuri.test.person.PersonDocument;
import org.openuri.test.person.PersonType;
import tools.util.JarUtil;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.events.XMLEvent;
import javax.xml.stream.util.StreamReaderDelegate;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

// Schema Imports


public class ValidatingXmlStreamReaderTests {

    // Base variable
    static String casesLoc = "xbean/ValidatingStream/";

    //////////////////////////////////////////////////////////////////////
    // Tests

    // NOTE: Tests that use getCasesFile are reading files
    //       from cases/qatest/files/xbean
    //       Tests that use getResourceFromJar are getting the contents of
    //       the file in the same location, but packaged into xmlcases.jar
    //       SO, any change to the xml files for these tests will not be
    //       reflected till they make it into xmlcases.jar. (ant build.xmlcases)
    @Test
    public void testDocWithNoSchema() throws Exception {
        checkDocIsInvalid(getCasesFile(casesLoc + "po.xml"), null);
    }

    @Test
    public void testValidLocationDoc() throws Exception {
        checkDocIsValid(getCasesFile(casesLoc + "location.xml"), null);
    }

    @Test
    public void testInvalidLocationDoc() throws Exception {
        checkDocIsInvalid(getCasesFile(casesLoc + "location-inv.xml"), LocationDocument.type);
    }

    @Test
    public void testValidPersonDoc() throws Exception {
        checkDocIsValid(getCasesFile(casesLoc + "person.xml"), PersonDocument.type);
    }

    @Test
    public void testInvalidPersonDoc() throws Exception {
        checkDocIsInvalid(getCasesFile(casesLoc + "person-inv.xml"), PersonDocument.type);
    }

    @Test
    public void testValidMixedContentDoc() throws Exception {
        checkDocIsValid(getCasesFile(casesLoc + "mixed-content.xml"),
            LetterDocument.type);
    }

    @Test
    public void testInvalidNomixedContentDoc() throws Exception {
        checkDocIsInvalid(getCasesFile(casesLoc + "nomixed-content-inv.xml"),
            NoMixedDocument.type);
    }

    @Test
    public void testInvalidMissingAttributeDoc() throws Exception {
        checkDocIsInvalid(getCasesFile(casesLoc + "foo-inv.xml"),
            HeadingDocument.type);
    }


    @Test
    public void testContentName() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-frag.xml");
        SchemaType type = Name.type;

        assertTrue("Xml-fragment is not valid:\n" + sXml,
            checkContent(sXml, type, true));
    }


    // Same as testContentName.. expect the xml has no chars before the first
    // start element
    @Test
    public void testContentName2() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-frag2.xml");
        SchemaType type = Name.type;

        assertTrue("Xml-fragment is not valid:\n" + sXml,
            checkContent(sXml, type, true));
    }

    @Test
    public void testContentSibling() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-sibling.xml");
        SchemaType type = PersonType.type;
        assertTrue("Xml-fragment is not valid:\n" + sXml,
            checkContent(sXml, type, true));
    }

    @Test
    public void testInvalidContentSibling() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-sibling-inv.xml");
        SchemaType type = PersonType.type;
        assertTrue("Invalid Xml-fragment is getting validated:\n" + sXml,
            !checkContent(sXml, type, true));
    }

    @Test
    public void testValidXsiType() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-justname.xml");
        SchemaType type = Name.type;
        assertTrue("Xml-fragment is not valid:\n" + sXml,
            checkContent(sXml, type, true));
    }

    @Test
    public void testInvalidXsiType() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-justname-inv.xml");
        SchemaType type = Name.type;
        assertTrue("Invalid Xml-fragment is getting validated:\n" + sXml,
            !checkContent(sXml, type, true));
    }

    @Test
    public void testIncompatibleXsiType() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "person-xsi-inv.xml");
        SchemaType type = Name.type;
        assertTrue("Invalid Xml-fragment is getting validated:\n" + sXml,
            !checkContent(sXml, type, true));
    }

    @Test
    public void testValidMixedContent() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "mixed-content.xml");
        SchemaType type = org.openuri.test.mixedContent.LetterType.type;
        assertTrue("Xml-fragment is not valid:\n" + sXml,
            checkContent(sXml, type, true));
    }

    @Test
    @Ignore
    public void testGlobalAttribute() throws Exception {
        String sXml = JarUtil.getResourceFromJar(casesLoc + "global-attr.xml");

        assertTrue("Global Attribute test failed:\n",
                   checkContent(sXml, null, true));
    }

    // Tests for increasing code-coverage metrics
    @Test
    public void testValXsrReuse() throws Exception {
        Collection errors = new ArrayList();
        File[] xmls = new File[2];
        xmls[0] = getCasesFile(casesLoc + "person.xml");
        xmls[1] = getCasesFile(casesLoc + "person-inv.xml");
        SchemaType type = PersonDocument.type;

        boolean[] ret = runValidator(xmls, type, errors);

        String common = "Test for ValidatingXmlStreamReader reuse failed";
        assertEquals(common + "\nReturn value has more than 2 elements", 2, ret.length);
        assertTrue(common + "\nExpected: true & false. Actual: "
                + ret[0] + " & " + ret[1],
            ret[0] && !ret[1]);
    }


    public void testIllegalEvent() throws Exception {
        // Will require writing another XSR wrapper.. albeit simple
    }

    /*/
    public void testWalk() throws Exception {
        walkXml(getCasesFile(casesLoc + "global-attr.xml"));
        System.out.println();
        walkXml(getCasesFile(casesLoc + "person-sibling.xml"));
    }
    // */
    //////////////////////////////////////////////////////////////////////
    // Utility Methods
    private void walkXml(File xml) throws Exception {
        XMLStreamReader xr = XMLInputFactory.newInstance().
            createXMLStreamReader(new FileInputStream(xml));

        //xsr.nextTag();
        XmlContentTestXSR xsr = new XmlContentTestXSR(xr);

        while(xsr.hasNext())
        {
            int type = xsr.next();
            System.out.print(type);
            //*
            if (type == XMLEvent.START_ELEMENT)
            {
                System.out.print("\n" + xsr.getLocalName() + " ");
            }
            if (type == XMLEvent.END_ELEMENT)
            {
                System.out.println("/" + xsr.getLocalName());
            }
            if (type == XMLEvent.CHARACTERS)
            {
                char[] arr = xsr.getTextCharacters();
                String str = new String(arr);
                System.out.print("Char:" + str + " ");
            }
            //*/
        }
    }

    private boolean runValidator(File xml,
                                 SchemaType type,
                                 Collection errors)
        throws IllegalArgumentException, Exception
    {
        if (errors == null)
            throw new IllegalArgumentException(
                "Collection object cannot be null");

        XMLStreamReader xsr = XMLInputFactory.newInstance().
            createXMLStreamReader(new FileInputStream(xml));

        ValidatingXMLStreamReader valXsr = new ValidatingXMLStreamReader();
        valXsr.init(xsr,
            false,
            type,
            XmlBeans.typeLoaderForClassLoader(ValidatingXMLStreamReader.class.getClassLoader()),
            null,
            errors);

        // Walk through the xml
        while (valXsr.hasNext())
            valXsr.next();

        return valXsr.isValid();
        //return true;
    }

    // This method is primarily for testing re-use of the ValXSR object.
    // but could come in handy later..
    private boolean[] runValidator(File[] xml,
                                   SchemaType type,
                                   Collection errors)
        throws IllegalArgumentException, Exception
    {
        if (errors == null)
            throw new IllegalArgumentException(
                "Collection object cannot be null");
        ValidatingXMLStreamReader valXsr = new ValidatingXMLStreamReader();
        boolean[] retArray = new boolean[xml.length];

        for (int i = 0; i < xml.length; i++)
        {
            XMLStreamReader xsr = XMLInputFactory.newInstance().
                createXMLStreamReader(new FileInputStream(xml[i]));

            valXsr.init(xsr,
                false,
                type,
                XmlBeans.typeLoaderForClassLoader(ValidatingXMLStreamReader.class.getClassLoader()),
                null,
                errors);

            // Walk through the xml
            while (valXsr.hasNext())
                valXsr.next();

            retArray[i] = valXsr.isValid();
        }

        return retArray;
    }

    protected void checkDocIsValid(File file, SchemaType type) throws Exception {
        Collection errors = new ArrayList();
        boolean isValid = runValidator(file, type, errors);

        tools.xml.Utils.printXMLErrors(errors);
        Assert.assertTrue("File '" + file.getName() + "' is invalid.", isValid);
    }


    protected void checkDocIsInvalid(File file, SchemaType type) throws Exception {
        Collection errors = new ArrayList();

        boolean isValid = runValidator(file, type, errors);
        assertTrue("File '" + file.getName() + "' is valid, but was expecting invalid.",
            !isValid);
    }


    public boolean checkContent(String fragment,
                                SchemaType type,
                                boolean printErrors) throws Exception {
        XMLStreamReader xsr = XMLInputFactory.newInstance().
            createXMLStreamReader(new StringReader(fragment));

        XmlContentTestXSR cxsr = new XmlContentTestXSR(xsr);
        Collection errors = new ArrayList();

        ValidatingXMLStreamReader valXsr = new ValidatingXMLStreamReader();
        valXsr.init(cxsr,
            false,
            type,
            XmlBeans.typeLoaderForClassLoader(ValidatingXMLStreamReader.class.getClassLoader()),
            null,
            errors);

        // Walk through the xml
        while (valXsr.hasNext())
            valXsr.next();

        if (!valXsr.isValid())
        {
            if (printErrors)
                tools.xml.Utils.printXMLErrors(errors);
            return false;
        }
        return true;
    }


    private static File getCasesFile(String path)
        throws java.io.IOException
    {
        if (path.length()==0)
            throw new IOException("getCasesFile was called with path of len 0");
        return JarUtil.getResourceFromJarasFile(path);
        //return new File(casesRoot + path);
    }

    /////////////////////////////////////////////////////////////////////////
    // XmlStreamReader extension for content Validation
    //     will not work for Global Attribute
    public class XmlContentTestXSR
        extends StreamReaderDelegate
        implements XMLStreamReader
    {
        private static final int TAGOPEN    = 100;
        private static final int TAGCLOSE   = 101;
        private static final int UNDEFINED  =  99;
        private static final int ATTRIBUTE  = 102;
        private static final int ENDCONTENT = 103;

        int state = -1;
        int depth = -1;
        boolean initialized = false;
        int attributeCount = -1;
        boolean hasAttributes = false;

        // Constructor Wrappers
        public XmlContentTestXSR(XMLStreamReader xsr)
            throws XMLStreamException
        {
            super(xsr);
        }


        public boolean hasNext()
        {
            if (state == UNDEFINED || state == ENDCONTENT)
                return false;

            if (!initialized)       // next() has not been called yet
                return true;



            return true;
        }

        public int next()
            throws XMLStreamException
        {
            int _next;
            if (!initialized)
            {
                // First time next() is called..
                // Scan for the first XMLEvent.START_ELEMENT
                _next = UNDEFINED;
                while ((super.hasNext()) && (_next != XMLEvent.START_ELEMENT))
                    _next = super.next();

                if (_next != XMLEvent.START_ELEMENT)
                    throw new XMLStreamException(
                        "Could not find a start element");
                initialized = true;

                // Now move past the first tag
                state = TAGOPEN;
                depth = 1;

                if ((attributeCount = super.getAttributeCount()) > 0)
                {
                    // The first element has attributes.. this is part of
                    // the content. So the first event should XMLEvent.ATTRIBUTE
                    _next = XMLEvent.ATTRIBUTE;
                }
                else
                {
                    // return super.next();
                    /*
                    If content is <xml-fragment/> then we will have returned
                    END_ELEMENT above, without ever generating a START_ELEMENT
                    In this case probably we should detect this and return a
                    END_DOCUMENT
                    */
                    _next = super.next();
                    if (_next == XMLEvent.END_ELEMENT)
                    {
                        _next = XMLEvent.END_DOCUMENT;
                        state = ENDCONTENT;
                    }
                }
                return _next;
            }

            _next = super.next();
            switch (_next)
            {
                case XMLEvent.START_ELEMENT:
                    state = TAGOPEN;
                    depth++;
                    break;

                case XMLEvent.END_ELEMENT:
                    --depth;
                    if (depth < 0 && state == TAGOPEN)
                    {
                        throw new XMLStreamException(
                            "Illegal XML Stream state");
                    }
                    else if (depth == 0 && state == TAGOPEN)
                    {
                        state = ENDCONTENT;
                        // at this point we will return ENDDOCUMENT
                        _next = XMLEvent.END_DOCUMENT;
                    }
                    break;
            }

            return _next;
        }

    }


}
