/*******************************************************************************
* Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0
* which accompanies this distribution.
* The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
* and the Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*     mmacivor - March 10/2009 - Initial implementation
******************************************************************************/
package org.eclipse.persistence.internal.jaxb;

import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedActionException;

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.descriptors.InstantiationPolicy;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;

/**
 * Purpose:
 * Provide a version of Instantiation Policy that can make use of a multiple argument factory method.
 * The defaultValues specified on this policy will be passed in to the factory method for the parameter values.
 * 
 * This is required for certain JAXB generated classes that have no 0 arg constructor and a factory method with multiple arguments. 
 * @author mmacivor
 *
 */
public class MultiArgInstantiationPolicy extends InstantiationPolicy {
    private String[] parameterTypeNames;
    private Class[] parameterTypes;
    
    private Object[] defaultValues;
    
    
    public void setParameterTypeNames(String[] parameterTypeNames) {
        this.parameterTypeNames = parameterTypeNames;
    }
    
    public void setParameterTypes(Class[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }
    
    public void setDefaultValues(Object[] values) {
        defaultValues = values;
    }
    
    @Override
    public void convertClassNamesToClasses(ClassLoader loader) {
        super.convertClassNamesToClasses(loader);
        if(parameterTypes == null) {
            if(parameterTypeNames != null) {
                Class[] values = new Class[parameterTypeNames.length];
                for(int i = 0; i < values.length; i++) {
                    try{
                        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                            try {
                                values[i] = AccessController.doPrivileged(new PrivilegedClassForName(parameterTypeNames[i], true, loader));
                            } catch (PrivilegedActionException exception) {
                                throw ValidationException.classNotFoundWhileConvertingClassNames(parameterTypeNames[i], exception.getException());
                            }
                        } else {
                            values[i] = org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(parameterTypeNames[i], true, loader);
                        }
                    } catch (ClassNotFoundException exc){
                        throw ValidationException.classNotFoundWhileConvertingClassNames(factoryClassName, exc);
                    }                    
                }
                this.parameterTypes = values;
            }
        }
        
    }
    protected void initializeMethod() throws DescriptorException {
        Class tempClass;
        if (this.getFactory() != null) {
            tempClass = this.getFactory().getClass();
        } else if (this.getFactoryClass() == null) {
            tempClass = this.getDescriptor().getJavaClass();
        } else {
            tempClass = this.getFactoryClass();
        }
        if(this.parameterTypes == null) {
            this.setMethod(this.buildMethod(tempClass, this.getMethodName(), new Class[0]));
        } else {
            this.setMethod(this.buildMethod(tempClass, this.getMethodName(), this.parameterTypes));
        }
    }
    
    /**
     * Build and return a new instance, using the factory.
     * The factory can be null, in which case the method is a static method defined by the descriptor class.
     */
    protected Object buildNewInstanceUsingFactory() throws DescriptorException {
        try {
            // If the method is static, the first argument is ignored and can be null
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try {
                    return AccessController.doPrivileged(new PrivilegedMethodInvoker(getMethod(), getFactory(), this.defaultValues));
                } catch (PrivilegedActionException exception) {
                    Exception throwableException = exception.getException();
                    if (throwableException instanceof IllegalAccessException) {
                        throw DescriptorException.illegalAccessWhileMethodInstantiation(getMethod().toString(), this.getDescriptor(), throwableException);
                    } else {
                        throw DescriptorException.targetInvocationWhileMethodInstantiation(getMethod().toString(), this.getDescriptor(), throwableException);
                    }
                }
            } else {
                return PrivilegedAccessHelper.invokeMethod(getMethod(), getFactory(), this.defaultValues);
            }
        } catch (IllegalAccessException exception) {
            throw DescriptorException.illegalAccessWhileMethodInstantiation(getMethod().toString(), this.getDescriptor(), exception);
        } catch (InvocationTargetException exception) {
            throw DescriptorException.targetInvocationWhileMethodInstantiation(getMethod().toString(), this.getDescriptor(), exception);
        } catch (NullPointerException exception) {
            // Some JVMs will throw a NULL pointer exception here
            throw DescriptorException.nullPointerWhileMethodInstantiation(this.getMethod().toString(), this.getDescriptor(), exception);
        }
    }    

}
