/*
 * Copyright (c) 1998, 2018 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:
//     Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.mappings.transformers;

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

import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.helper.ClassConstants;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedGetMethodParameterTypes;
import org.eclipse.persistence.internal.security.PrivilegedMethodInvoker;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.mappings.foundation.AbstractTransformationMapping;

/**
 *  @version $Header: MethodBasedFieldTransformer.java 18-sep-2006.16:20:59 gyorke Exp $
 *  @author  mmacivor
 *  @since   10
 *  This class is used to preserve the old method of doing Field Transformations
 *  on a transformation mapping. It is used internally when the older API is used on
 *  a TransformationMapping, and handles doing invocations on the user's domain class
 */
public class MethodBasedFieldTransformer implements FieldTransformer {
    protected transient Method fieldTransformationMethod;
    protected String methodName;
    protected AbstractTransformationMapping mapping;

    public MethodBasedFieldTransformer(String methodName) {
        this.methodName = methodName;
    }

    public void initialize(AbstractTransformationMapping mapping) {
        this.mapping = mapping;
        final Class javaClass = this.mapping.getDescriptor().getJavaClass();
        try {
            // look for the zero-argument version first
            fieldTransformationMethod = Helper.getDeclaredMethod(javaClass, methodName, new Class[0]);
        } catch (NoSuchMethodException ex) {
            try {
                // if the zero-argument version is not there, look for the one-argument version
                Class[] methodParameterTypes = new Class[1];
                methodParameterTypes[0] = ClassConstants.PublicInterfaceSession_Class;
                fieldTransformationMethod = Helper.getDeclaredMethod(javaClass, methodName, methodParameterTypes);
            } catch (NoSuchMethodException ex2) {
                try {
                    //if the one-argument version is absent, try with sessions.Session
                    Class[] methodParameterTypes = new Class[1];
                    methodParameterTypes[0] = ClassConstants.SessionsSession_Class;
                    fieldTransformationMethod = Helper.getDeclaredMethod(javaClass, methodName, methodParameterTypes);
                } catch (NoSuchMethodException exception) {
                    throw DescriptorException.noSuchMethodWhileConvertingToMethod(methodName, this.mapping, exception);
                } catch (SecurityException exception) {
                    throw DescriptorException.securityWhileConvertingToMethod(methodName, this.mapping, exception);
                }
            } catch (SecurityException exception) {
                throw DescriptorException.securityWhileConvertingToMethod(methodName, this.mapping, exception);
            }
        } catch (SecurityException exception) {
            throw DescriptorException.securityWhileConvertingToMethod(methodName, this.mapping, exception);
        }
    }

    /**
     * Return the Java class type of the field value.
     * This uses the method return type.
     */
    public Class getFieldType() {
        if (this.fieldTransformationMethod != null) {
            return this.fieldTransformationMethod.getReturnType();
        }
        return null;
    }

    public Object buildFieldValue(Object object, String fieldName, Session session) {
        Class[] parameterTypes = null;
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
            try{
                parameterTypes = AccessController.doPrivileged(new PrivilegedGetMethodParameterTypes(fieldTransformationMethod));
            }catch (PrivilegedActionException ex){
                throw (RuntimeException) ex.getCause();
            }
        }else{
            parameterTypes = PrivilegedAccessHelper.getMethodParameterTypes(fieldTransformationMethod);
        }

        Object[] parameters = new Object[parameterTypes.length];
        if (parameters.length == 1) {
            parameters[0] = session;
        }

        try {
            if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                try{
                    return AccessController.doPrivileged(new PrivilegedMethodInvoker(fieldTransformationMethod, object, parameters));
                }catch (PrivilegedActionException ex){
                    if (ex.getCause() instanceof IllegalAccessException){
                        throw (IllegalAccessException) ex.getCause();
                    }
                    if (ex.getCause() instanceof InvocationTargetException){
                        throw (InvocationTargetException) ex.getCause();
                    }
                    throw (RuntimeException) ex.getCause();
                }
            }else{
                return PrivilegedAccessHelper.invokeMethod(fieldTransformationMethod, object, parameters);
            }
        } catch (IllegalAccessException exception) {
            throw DescriptorException.illegalAccessWhileInvokingFieldToMethod(fieldTransformationMethod.getName(), mapping, exception);
        } catch (IllegalArgumentException exception) {
            throw DescriptorException.illegalArgumentWhileInvokingFieldToMethod(fieldTransformationMethod.getName(), mapping, exception);
        } catch (InvocationTargetException exception) {
            throw DescriptorException.targetInvocationWhileInvokingFieldToMethod(fieldTransformationMethod.getName(), mapping, exception);
        }
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String name) {
        methodName = name;
    }
}
