/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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 org.apache.axis2.databinding.utils;

import org.apache.axiom.om.*;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.AxisFault;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.java2wsdl.TypeTable;
import org.apache.axis2.engine.DefaultObjectSupplier;
import org.apache.axis2.engine.ObjectSupplier;

import junit.framework.TestCase;

import javax.activation.DataHandler;
import javax.mail.util.ByteArrayDataSource;
import javax.xml.namespace.QName;

import static com.google.common.truth.Truth.assertAbout;
import static org.apache.axiom.truth.xml.XMLTruth.xml;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;


public class BeanUtilTest extends TestCase {

    public class ComplexType {
        private String child;

        public void setChild(String child) {
            this.child = child;
        }

        public String getChild() {
            return child;
        }
    }
    
    private ObjectSupplier objectSupplier;

    private SOAPFactory omFactory;
    private OMElement omElement;
    private OMNamespace xsiNamespace;
    
    @Override
    protected void setUp() throws Exception {
        objectSupplier = new DefaultObjectSupplier();
        
        omFactory = OMAbstractFactory.getSOAP12Factory();
        xsiNamespace = omFactory.createOMNamespace(Constants.XSI_NAMESPACE, "xsi");
        omElement = omFactory.createOMElement(new QName("hello"));

        MessageContext msgContext = new MessageContext();
        msgContext.setEnvelope(omFactory.createSOAPEnvelope());
        MessageContext.setCurrentMessageContext(msgContext);
    }

    @Override
    protected void tearDown() throws Exception {
        MessageContext.setCurrentMessageContext(null);
    }

    public void testProcessObjectAsSimpleType() throws Exception {
        omElement.setText("World");
        
        Object result = BeanUtil.processObject(omElement, String.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertTrue(result instanceof String);
        assertEquals("World", result);
    }

    public void testProcessObjectAsOmElement() throws Exception {
        omElement.setText("World");
        
        Object result = BeanUtil.processObject(omElement, OMElement.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertTrue(result instanceof OMElement);
        assertEquals(omElement, result);
    }
    
    public void testProcessObjectAsNull() throws Exception {
        OMAttribute nilAttribute = omFactory.createOMAttribute("nil", xsiNamespace, "true");
        omElement.addAttribute(nilAttribute);
        
        Object result = BeanUtil.processObject(omElement, String.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertNull(result);
    }

    public void testProcessObjectAsByteArray() throws Exception {
        omElement.setText("Word");
        
        Object result = BeanUtil.processObject(omElement, byte.class, new MultirefHelper(omElement), true, objectSupplier, null);
        assertTrue(result instanceof byte[]);
        assertEquals(3, ((byte[]) result).length);
    }

    public void testProcessObjectAsList() throws Exception {
        OMElement child = omFactory.createOMElement(new QName("child"), omElement);
        child.setText("World");
        
        Object result = BeanUtil.processObject(omElement.getFirstElement(), List.class, new MultirefHelper(omElement), false, objectSupplier, List.class);
        assertTrue(result instanceof List);
        assertEquals(1, ((List) result).size());
    }

    public void testProcessObjectAsDataHandler() throws Exception {
        omElement.setText("Word");
        
        Object result = BeanUtil.processObject(omElement, DataHandler.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertTrue(result instanceof DataHandler);
    }

    public void testProcessObjectAsComplexType() throws Exception {
        OMElement child = omFactory.createOMElement(new QName("child"), omElement);
        child.setText("World");
        
        Object result = BeanUtil.processObject(omElement, ComplexType.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertTrue(result instanceof ComplexType);
        assertEquals("World", ((ComplexType) result).getChild());
    }

    public void testProcessObjectAsObject() throws Exception {
        omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

        omElement.setText("World");
        omElement.addAttribute(createTypeAttribute("xs:string"));
        
        Object result = BeanUtil.processObject(omElement, Object.class, new MultirefHelper(omElement), false, objectSupplier, null);
        assertTrue(result instanceof OMText);
        assertEquals("World", ((OMText) result).getText());
    }
    
    private OMAttribute createTypeAttribute(String value) {
        return omFactory.createOMAttribute("type", xsiNamespace, value);
    }
    
    /**
     * Test that for a {@link DataHandler} object, {@link BeanUtil} creates sequence of
     * events that allows Axiom to recognize the optimized binary.
     */
    public void testGetOMElementWithDataHandlerArg() {
        DataHandler dh = new DataHandler(new ByteArrayDataSource(new byte[4096],
                "application/octet-stream"));
        OMElement element = BeanUtil.getOMElement(new QName("urn:ns1", "myop"),
                new Object[] { dh }, new QName("urn:ns1", "part"), true, new TypeTable());
        OMText text = (OMText)element.getFirstElement().getFirstOMChild();
        assertTrue(text.isOptimized());
        assertSame(dh, text.getDataHandler());
    }

    public void testProcessObjectWithWrongType() throws Exception {
        omElement.setLocalName("Queensland");
        omElement.setText("Brisbane");

        try {
            BeanUtil.processObject(omElement, int.class, new MultirefHelper(omElement), true, objectSupplier, null);
        } catch (AxisFault e) {
            assertEquals(org.apache.axis2.Constants.FAULT_SOAP12_SENDER, e.getFaultCode());
            assertTrue(e.getMessage().contains("Queensland"));
            assertTrue(e.getMessage().contains("Brisbane"));
        }
    }

    public void testDeserializeWithWrongType() throws Exception {
        omElement.setLocalName("Queensland");
        omElement.setText("Brisbane");

        try {
            BeanUtil.deserialize(int.class, omElement, objectSupplier, "Queensland");
        } catch (AxisFault e) {
            assertEquals(org.apache.axis2.Constants.FAULT_SOAP12_SENDER, e.getFaultCode());
            assertTrue(e.getMessage().contains("Queensland"));
            assertTrue(e.getMessage().contains("Brisbane"));
        }
    }
    
    public void testDeserializeWithArrayLocalNameForString() throws Exception {    	
    	   omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

           omElement.setText("World");
           omElement.addAttribute(createTypeAttribute("xs:string"));
           
           Object result = BeanUtil.deserialize(String.class, omElement, objectSupplier, null);
           assertNotNull("Result can not be null",result);
           assertEquals("Not the expected Class",String.class,result.getClass());
           assertEquals("Not the expected value","World",result);
	   
   }
    
    public void testDeserializeWithArrayLocalNameForInt() throws Exception {    	
 	   omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

        omElement.setText("1000");
        omElement.addAttribute(createTypeAttribute("xs:int"));
        
        Object result = BeanUtil.deserialize(Integer.class, omElement, objectSupplier, null);
        assertNotNull("Result can not be null",result);
        assertEquals("Not the expected Class",Integer.class,result.getClass());
        assertEquals("Not the expected value",1000,result);
	   
   } 
    public void testDeserializeWithArrayLocalNameForInteger() throws Exception {    	
  	   omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

         omElement.setText("100000");
         omElement.addAttribute(createTypeAttribute("xs:integer"));
         
         Object result = BeanUtil.deserialize(BigInteger.class, omElement, objectSupplier, null);
         assertNotNull("Result can not be null",result);
         assertEquals("Not the expected Class",BigInteger.class,result.getClass());
         assertEquals("Not the expected value",new BigInteger("100000"),result);
 	   
    } 
    public void testDeserializeWithArrayLocalNameForBase64Binary() throws Exception {    	
  	   omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

         omElement.setText("SGVsbG8gV29ybGQ=");
         omElement.addAttribute(createTypeAttribute("xs:base64Binary"));
         
         Object result = BeanUtil.deserialize(DataHandler.class, omElement, objectSupplier, null);
         assertNotNull("Result can not be null",result);
         assertEquals("Not the expected Class",DataHandler.class,result.getClass());
         assertEquals("Not the expected value","Hello World",toStr((ByteArrayInputStream) ((DataHandler)result).getContent()));
 	   
    } 
    public void testDeserializeWithArrayLocalNameForHexBinary() throws Exception {
    	 AxisService service = new AxisService();
    	 service.setTypeTable(new TypeTable());
    	 MessageContext.getCurrentMessageContext().setAxisService(service);
  	     omElement.declareNamespace(omFactory.createOMNamespace(Constants.XSD_NAMESPACE, "xs"));

         omElement.setText("48656c6c6f20576f726c64");
         omElement.addAttribute(createTypeAttribute("xs:hexBinary"));
         
         Object result = BeanUtil.deserialize(DataHandler.class, omElement, objectSupplier, null);
         assertNotNull("Result can not be null",result);
         assertEquals("Not the expected Class",DataHandler.class,result.getClass());
         assertEquals("Not the expected value","Hello World",toStr((ByteArrayInputStream) ((DataHandler)result).getContent()));
 	   
    } 
    
    public void testProcessSimpleMap() throws Exception {
    	OMNamespace ns = omFactory.createOMNamespace(org.apache.axis2.Constants.AXIS2_MAP_NAMESPACE_URI,
    			org.apache.axis2.Constants.AXIS2_MAP_NAMESPACE_PREFIX);
    	OMElement entry = omFactory.createOMElement(org.apache.axis2.Constants.MAP_ENTRY_ELEMENT_NAME,ns);
    	OMElement key = omFactory.createOMElement(org.apache.axis2.Constants.MAP_KEY_ELEMENT_NAME,ns);
    	OMElement value = omFactory.createOMElement(org.apache.axis2.Constants.MAP_VALUE_ELEMENT_NAME,ns);
    	key.setText("key1");
    	value.setText("value1");
    	entry.addChild(key);
    	entry.addChild(value);
    	omElement.addChild(entry);   
    	
        Object result = BeanUtil.processObject(omElement, Map.class, new MultirefHelper(omElement), false, objectSupplier, Map.class);
        assertTrue(result instanceof Map);       
    }
    
    private static String toStr(ByteArrayInputStream is) {
	    int size = is.available();
	    char[] theChars = new char[size];
	    byte[] bytes    = new byte[size];

	    is.read(bytes, 0, size);
	    for (int i = 0; i < size;)
	        theChars[i] = (char)(bytes[i++]&0xff);
	    
	    return new String(theChars);
	      }
  
    /**
     * Regression test for AXIS2-5751.
     */
    public void testSerializeAnyTypeNull() {
        assertAbout(xml())
                .that(BeanUtil.getPullParser(new ComplexTypeWithAnyTypeElement(), new QName("root"), null, false, false))
                .ignoringNamespaceDeclarations()
                .ignoringNamespacePrefixes()
                .hasSameContentAs("<root><prop xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:nil='true'/></root>");
    }
}
