/*
 * Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
 */


/*
 *  @(#)XmlAdapter.test.xml	1.3 07/03/21 Igor Tseytin
 * Generated from : api/jakarta_xml/bind/annotation/adapters/XmlAdapter.test.xml
 *
 * Provides XmlAdapter implementation tests
 */

package javasoft.sqe.tests.api.jakarta.xml.bind.annotation.adapters.XmlAdapter;

import jakarta.xml.bind.*;
import jakarta.xml.bind.annotation.*;
import jakarta.xml.bind.annotation.adapters.*;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;
import javasoft.sqe.javatest.Status;
import javasoft.sqe.javatest.lib.MultiTest;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.*;
import org.w3c.dom.*;
import org.xml.sax.InputSource;


public class XmlAdapterTests extends MultiTest {

    Binder<Node> binder = null;
    {
        try {
            binder = JAXBContext.newInstance(Elem.class).createBinder();
        } catch( JAXBException x ){
            x.printStackTrace( ref );
            throw new RuntimeException( x );
        }
    }
    DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();

    static void assertT( boolean val ){
        if( !val )
            throw new RuntimeException();
    }

    static void assertT( boolean val, String msg ){
        if( !val )
            throw new RuntimeException( msg );
    }

    static public class Entry {
        public Entry(){};
        public Entry( Integer key, String value, Entry next ){
            this.key = key;
            this.value = value;
            this.next = next;
        }

        @XmlAttribute
        public Integer key = -1;

        @XmlAttribute
        public String value = null;

        @XmlElement
        public Entry next = null;
    }

    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)
    @XmlType(name = "Element", propOrder = { "key", "value" })
    static public class Elem {
        public Integer key;
        @XmlJavaTypeAdapter(EntryAdapter.class)
        public Map<Integer, String> value;

        public String toString(){
            return String.format( "[key = %s, value = %s]", key, value.toString() );
        }
    }

    static public class EntryAdapter extends XmlAdapter<Entry, Map<Integer, String>> {
        static public final int MARSHAL_F = 1;
        static public final int UNMARSHAL_F = 2;
        static public int flags = 0;
        public Entry marshal(Map<Integer, String> v) throws Exception {
            flags |= MARSHAL_F;
            Entry tail = null;
            for(Map.Entry<Integer, String> entry : v.entrySet()){
                tail = new Entry( entry.getKey(), entry.getValue(), tail );
            }
            return tail;
        }

        public Map<Integer, String> unmarshal(Entry v) throws Exception {
            flags |= UNMARSHAL_F;
            Map<Integer, String> map = new TreeMap<Integer, String>();
            while( v != null ){
                map.put( v.key, v.value );
                v = v.next;
            }
            return map;
        }
    }

    static class UnmarshalChecker {
        XmlAdapter<String, String> adapter;
        UnmarshalChecker( XmlAdapter<String, String> adapter ){
            this.adapter = adapter;
        }
        void check( String result, String raw ){
            try {
                String unres = adapter.unmarshal( raw );
                assertT( result.equals( unres ),
                    String.format( "unexpected result of NormalizedStringAdapter.unmarshal('%s'): '%s'. Expected '%s'.",
                                    raw, unres, result ) );
            } catch( RuntimeException x ){
                throw x;
            } catch( Exception x ){
                throw new RuntimeException( x );
            }
        }
    };

    void checkMarshalNoop( XmlAdapter<String, String> adapter ){
        try {
            assertT( "\t \r\ntext text\r\n \t".equals( adapter.marshal( "\t \r\ntext text\r\n \t" ) ) );
            assertT( "\t \r\ntext\ttext\r\n \t".equals( adapter.marshal( "\t \r\ntext\ttext\r\n \t" ) ) );
            assertT( "\t \r\ntext\rtext\r\n \t".equals( adapter.marshal( "\t \r\ntext\rtext\r\n \t" ) ) );
            assertT( "\t \r\ntext\ntext\r\n \t".equals( adapter.marshal( "\t \r\ntext\ntext\r\n \t" ) ) );
            assertT( "text\t\r\ntext".equals( adapter.marshal( "text\t\r\ntext" ) ) );
        } catch( Exception x ){
            throw new RuntimeException( x );
        }
    }

    static XPath xpath;
    static { 
        xpath = XPathFactory.newInstance().newXPath();
        /*xpath.setNamespaceContext( new NamespaceContext() {
            final String xs = XMLConstants.W3C_XML_SCHEMA_NS_URI;
            public String getNamespaceURI(String prefix) {
                if( "xs".equals( prefix ) )
                    return xs;
                return "";
            }
            public String getPrefix(String namespaceURI) {
                if( xs.equals( namespaceURI ) )
                    return "xs";
                return "";
            }
            public Iterator getPrefixes(String namespaceURI) {
                return Collections.singleton( getPrefix(namespaceURI) ).iterator();
            }
        } );*/
    }

    /* standalone interface */
    public static void main(String argv[]) {
        XmlAdapterTests test = new XmlAdapterTests();
        test.run(argv, System.err, System.out).exit();
    }

    /**
     * Functional tests for XmlAdapter,
     * the test checks that Binder.marshal uses the 'marshal' method of custom XmlAdapter.
     */
    public Status XmlAdapter_marshal() {
        Elem elem = new Elem();
        elem.key = -1;
        elem.value = new HashMap<Integer, String>();
        String[] numbers = { "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "zero" };
        for( int i = 0; i < numbers.length; i++){
            elem.value.put( (i + 1) % 10, numbers[ i ] );
        }
        try {
            Document doc = documentBuilderFactory.newDocumentBuilder().newDocument();

            EntryAdapter.flags = 0;
            binder.marshal(elem, doc);
            assertT( EntryAdapter.flags == EntryAdapter.MARSHAL_F );
            // check
            XPathExpression xpathExpr = xpath.compile( "//*[@key]" );
            NodeList l = (NodeList)xpathExpr.evaluate( doc.getDocumentElement(), XPathConstants.NODESET );
            assertT( l.getLength() == 10 );

            boolean f[] = new boolean[10];
            for( int i = 0; i < l.getLength(); i++){
                org.w3c.dom.Element el = (org.w3c.dom.Element)l.item( i );
                int key = Integer.parseInt( el.getAttribute("key") );
                assertT( key >= 0 && key < 10 );
                f[ key ] = true;
            }
            for( boolean b : f ){
                assertT( b );
            }
        } catch ( Exception ex ) {
            ex.printStackTrace( ref );
            return Status.failed( ex.toString() );
        }
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * the test checks that Binder.unmarshal uses the 'unmarshal' method of custom XmlAdapter.
     */
    public Status XmlAdapter_unmarshal() {
        String xml =
            "<?xml version='1.0' encoding='UTF-8' standalone='no'?>"+
            "<elem>"+
            "   <key>0</key>"+
            "   <value key='9' value='nine'>"+
            "       <next key='8' value='eight'>"+
            "           <next key='7' value='seven'>"+
            "               <next key='6' value='six'/>"+
            "           </next>"+
            "       </next>"+
            "   </value>"+
            "</elem>";
        try {
            Document doc = documentBuilderFactory.newDocumentBuilder().parse( new InputSource( new StringReader( xml ) ) );

            EntryAdapter.flags = 0;
            JAXBElement<Elem> result = binder.unmarshal(doc, Elem.class);
            assertT( EntryAdapter.flags == EntryAdapter.UNMARSHAL_F );

            System.out.println(result.getValue());
            Map<Integer, String> res = result.getValue().value;
            assertT( res.size() == 4 );
            assertT( "nine".equals( res.get( 9 ) ) );
            assertT( "eight".equals( res.get( 8 ) ) );
            assertT( "seven".equals( res.get( 7 ) ) );
            assertT( "six".equals( res.get( 6 ) ) );
        } catch (Exception ex) {
            ex.printStackTrace( ref );
            return Status.failed( ex.toString() );
        }
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * unmarshal() Removes leading and trailing whitespaces of the string given as the parameter, then replace any tab, CR, and LF by a whitespace character ' '..
     */
    public Status NormalizedStringAdapter_unmarshal() {
        NormalizedStringAdapter adapter = new NormalizedStringAdapter();
        UnmarshalChecker t = new UnmarshalChecker( adapter );
        t.check( "    1text text    ", "\t \r\n1text text\r\n \t" );
        t.check( "    2text text    ", "\t \r\n2text\ttext\r\n \t" );
        t.check( "    3text text    ", "\t \r\n3text\rtext\r\n \t" );
        t.check( "    4text text    ", "\t \r\n4text\ntext\r\n \t" );
        t.check( "5text   text", "5text\t\r\ntext" );
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * marshal() No-op. Just return the same string given as the parameter..
     */
    public Status NormalizedStringAdapter_marshal() {
        checkMarshalNoop( new NormalizedStringAdapter() );
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * unmarshal() Removes leading and trailing whitespaces of the string given as the parameter, then truncate any sequnce of tab, CR, LF, and SP by a single whitespace character ' '..
     */
    public Status CollapsedStringAdapter_unmarshal() {
        CollapsedStringAdapter adapter = new CollapsedStringAdapter();
        UnmarshalChecker t = new UnmarshalChecker( adapter );
        t.check( "text text", "\t \r\ntext text\r\n \t" );
        t.check( "text text", "\t \r\ntext\ttext\r\n \t" );
        t.check( "text text", "\t \r\ntext\rtext\r\n \t" );
        t.check( "text text", "\t \r\ntext\ntext\r\n \t" );
        t.check( "text text", "text\t\r\ntext" );
        t.check( "text text", "text \n \r text" );
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * marshal() No-op. Just return the same string given as the parameter..
     */
    public Status CollapsedStringAdapter_marshal() {
        checkMarshalNoop( new CollapsedStringAdapter() );
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * returns true if the specified char is a white space character..
     */
    public Status CollapsedStringAdapter_isWhiteSpace() {
        new CollapsedStringAdapter(){{
            assertT( isWhiteSpace('\n') );
            assertT( isWhiteSpace('\r') );
            assertT( isWhiteSpace('\t') );
            assertT( isWhiteSpace(' ') );
            assertT( !isWhiteSpace('n') );
            assertT( !isWhiteSpace('r') );
            assertT( !isWhiteSpace('t') );
        }};
        return Status.passed("Ok");
        }

    /**
     * Functional tests for XmlAdapter,
     * unmarshal() Throws exception - if there's an error during the conversion..
     */
    public Status HexBinaryAdapter_unmarshal() {
        HexBinaryAdapter adapter = new HexBinaryAdapter();
          try {
               byte[] result = adapter.unmarshal( "\t \r\n 010203FFGG \r\n \t" );
          } catch(Exception ex) {
            return Status.passed("Ok");
          }
          return Status.failed("No exception is thrown as expected");
        }

    /**
     * Functional tests for XmlAdapter,
     * marshal() Convert a bound type to a value type..
     */
    public Status HexBinaryAdapter_marshal() {
        HexBinaryAdapter adapter = new HexBinaryAdapter();
        String result = adapter.marshal( new byte[] { 0, 1, 2, 3, 4, 5, 0x7F, (byte)0x80, (byte)0xFC, (byte)0xFD, (byte)0xFE, (byte)0xFF } );
        return Status.passed("Ok");
        }
}
