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

// Contributors:
//  - rbarkhouse - 08 November 2011 - 2.4 - Initial implementation
package org.eclipse.persistence.testing.jaxb.externalizedmetadata.xmltype.factory;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.IntegrityException;
import org.eclipse.persistence.jaxb.JAXBContextFactory;

import junit.framework.TestCase;
import org.eclipse.persistence.jaxb.JAXBContextProperties;

public class FactoryTestCases extends TestCase {

    private JAXBContext jaxbContext;

    public FactoryTestCases(String name) throws Exception {
        super(name);
    }

    @Override
    public String getName() {
        return "JAXB Factory Instantiation: " + super.getName();
    }

    /**
     * Concrete factory class, non-static factory method.
     */
    public void testNormalFactory() throws Exception {
        Class<?>[] classes = new Class<?>[] { Employee.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(NORMAL_FACTORY);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        jaxbContext = JAXBContextFactory.createContext(classes, properties);

        InputStream xml = classLoader.getResourceAsStream(EMPLOYEE_XML);
        Employee e = (Employee) jaxbContext.createUnmarshaller().unmarshal(xml);
        assertEquals(getControlObject(), e);
    }

    /**
     * Abstract factory class, static factory method.
     */
    public void testAbstractFactory() throws Exception {
        Class<?>[] classes = new Class<?>[] { Employee.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(ABSTRACTFACTORY);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        jaxbContext = JAXBContextFactory.createContext(classes, properties);

        InputStream xml = classLoader.getResourceAsStream(EMPLOYEE_XML);
        Employee e = (Employee) jaxbContext.createUnmarshaller().unmarshal(xml);
        assertEquals(getControlObject(), e);
    }

    /**
     * Abstract factory class, non-static factory method (ERROR).
     */
    public void testAbstractFactoryError() throws Exception {
        Class<?>[] classes = new Class<?>[] { Employee.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(ABSTRACTFACTORY_ERROR);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        Exception caughtException = null;
        try {
            jaxbContext = JAXBContextFactory.createContext(classes, properties);
        } catch (Exception e) {
            caughtException = e;
        }

        assertNotNull(caughtException);

        // Check for
        // "The factory class does not define a public default constructor, or the constructor raised an exception."
        try {
            JAXBException jaxbException = (JAXBException) caughtException;
            IntegrityException integrityException = (IntegrityException) jaxbException.getLinkedException();
            DescriptorException descriptorException = (DescriptorException) integrityException.getIntegrityChecker().getCaughtExceptions().firstElement();
            assertEquals("Incorrect error code:", DescriptorException.INSTANTIATION_WHILE_CONSTRUCTOR_INSTANTIATION_OF_FACTORY, descriptorException.getErrorCode());
        } catch (Exception e) {
            fail("The expected Exception structure was not thrown, instead caught: " + caughtException.getLocalizedMessage());
        }
    }

    /**
     * Concrete factory class with no default constructor, static factory method.
     */
    public void testNoDefaultConstructorFactory() throws Exception {
        Class<?>[] classes = new Class<?>[] { Employee.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(NODEFAULTCONSTRUCTOR);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        jaxbContext = JAXBContextFactory.createContext(classes, properties);

        InputStream xml = classLoader.getResourceAsStream(EMPLOYEE_XML);
        Employee e = (Employee) jaxbContext.createUnmarshaller().unmarshal(xml);
        assertEquals(getControlObject(), e);
    }

    /**
     * Concrete factory class with no default constructor, non-static factory method (ERROR).
     */
    public void testNoDefaultConstructorFactoryError() throws Exception {
        Class<?>[] classes = new Class<?>[] { Employee.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(NODEFAULTCONSTRUCTOR_ERROR);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        Exception caughtException = null;
        try {
            jaxbContext = JAXBContextFactory.createContext(classes, properties);
        } catch (Exception e) {
            caughtException = e;
        }

        assertNotNull(caughtException);

        // Check for
        // "The instance creation method [{0}], with no parameters, does not exist, or is not accessible."
        try {
            JAXBException jaxbException = (JAXBException) caughtException;
            IntegrityException integrityException = (IntegrityException) jaxbException.getLinkedException();
            DescriptorException descriptorException = (DescriptorException) integrityException.getIntegrityChecker().getCaughtExceptions().firstElement();
            assertEquals("Incorrect error code:", DescriptorException.NO_SUCH_METHOD_WHILE_INITIALIZING_INSTANTIATION_POLICY, descriptorException.getErrorCode());
        } catch (Exception e) {
            fail("The expected Exception structure was not thrown, instead caught: " + caughtException.getLocalizedMessage());
        }
    }

    /**
     * No factory class specified, no default constructor on domain class, static factory method on domain class.
     */
    public void testDomainClassFactoryMethod() throws Exception {
        Class<?>[] classes = new Class<?>[] { EmployeeWithFactoryMethod.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(DOMAINCLASSFACTORYMETHOD);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        jaxbContext = JAXBContextFactory.createContext(classes, properties);

        InputStream xml = classLoader.getResourceAsStream(EMPLOYEE_XML);
        EmployeeWithFactoryMethod e = (EmployeeWithFactoryMethod) jaxbContext.createUnmarshaller().unmarshal(xml);
        assertEquals(getControlObject2(), e);
    }

    /**
     * No factory class specified, no default constructor on domain class, non-static factory method on domain class (ERROR).
     */
    public void testDomainClassFactoryMethodError() throws Exception {
        Class<?>[] classes = new Class<?>[] { EmployeeWithNonStaticFactoryMethod.class };

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        InputStream bindings = classLoader.getResourceAsStream(DOMAINCLASSFACTORYMETHOD_ERROR);
        Map<String, Object> properties = new HashMap<String, Object>();
        properties.put(JAXBContextProperties.OXM_METADATA_SOURCE, bindings);

        Exception caughtException = null;
        try {
            jaxbContext = JAXBContextFactory.createContext(classes, properties);
            InputStream xml = classLoader.getResourceAsStream(EMPLOYEE_XML);
            EmployeeWithNonStaticFactoryMethod e = (EmployeeWithNonStaticFactoryMethod) jaxbContext.createUnmarshaller().unmarshal(xml);
        } catch (Exception e) {
            caughtException = e;
        }

        assertNotNull(caughtException);

        // Check for
        // "Problem in creating new instance using creation method [x].  The creation method is not accessible."
        try {
            JAXBException jaxbException = (JAXBException) caughtException;
            DescriptorException descriptorException = (DescriptorException) jaxbException.getLinkedException();
            assertEquals("Incorrect error code:", DescriptorException.NULL_POINTER_WHILE_METHOD_INSTANTIATION, descriptorException.getErrorCode());
        } catch (Exception ex) {
            fail("The expected Exception structure was not thrown, instead caught: " + caughtException.getLocalizedMessage());
        }
    }

    // ========================================================================

    public Employee getControlObject() {
        Employee e = new Employee();
        e.id = EMP_ID;
        e.firstName = EMP_FIRST;
        e.lastName = EMP_LAST;
        e.fromFactoryMethod = true;
        return e;
    }

    public EmployeeWithFactoryMethod getControlObject2() {
        EmployeeWithFactoryMethod e = new EmployeeWithFactoryMethod(false);
        e.id = EMP_ID;
        e.firstName = EMP_FIRST;
        e.lastName = EMP_LAST;
        e.fromFactoryMethod = 1;
        return e;
    }

    private static final String RESOURCE_DIR = "org/eclipse/persistence/testing/jaxb/externalizedmetadata/xmltype/factory/";

    private static final String NORMAL_FACTORY = RESOURCE_DIR + "normalfactory.xml";
    private static final String ABSTRACTFACTORY = RESOURCE_DIR + "abstractfactory.xml";
    private static final String ABSTRACTFACTORY_ERROR = RESOURCE_DIR + "abstractfactoryerror.xml";
    private static final String NODEFAULTCONSTRUCTOR = RESOURCE_DIR + "nodefaultconstructor.xml";
    private static final String NODEFAULTCONSTRUCTOR_ERROR = RESOURCE_DIR + "nodefaultconstructorerror.xml";
    private static final String DOMAINCLASSFACTORYMETHOD = RESOURCE_DIR + "domainclassfactorymethod.xml";
    private static final String DOMAINCLASSFACTORYMETHOD_ERROR = RESOURCE_DIR + "domainclassfactorymethoderror.xml";

    private static final String EMPLOYEE_XML = RESOURCE_DIR + "employee.xml";

    public static final int EMP_ID = 101;
    public static final String EMP_FIRST = "Joe";
    public static final String EMP_LAST = "Oracle";

}
