/*
 * Copyright (c) 2011, 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:
//     James Sutherland (Oracle) - initial API and implementation
//      //     30/05/2012-2.4 Guy Pelletier
//       - 354678: Temp classloader is still being used during metadata processing
package org.eclipse.persistence.descriptors.partitioning;

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

import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetConstructorFor;
import org.eclipse.persistence.internal.security.PrivilegedInvokeConstructor;

/**
 * PUBLIC:
 * Represent a specific range partition.
 * Values {@literal >=} startValue and {@literal <=} endValue will be routed to the connection pool.
 * @author James Sutherland
 * @since EclipseLink 2.2
 */
public class RangePartition  {
    protected String endValueName;
    protected String startValueName;
    protected String partitionValueTypeName;
    protected String connectionPool;

    protected Class partitionValueType;
    protected Comparable startValue;
    protected Comparable endValue;

    public RangePartition() {}

    /**
     * INTERNAL:
     * COnstructor used from metadata processing to avoid classloader
     * dependencies. Class names are converted/initialized in the
     * convertClassNamesToClasses method.
     */
    public RangePartition(String connectionPool, String partitionValueTypeName, String startValueName, String endValueName) {
        this.connectionPool = connectionPool;
        this.endValue = null;
        this.endValueName = endValueName;
        this.startValue = null;
        this.startValueName = startValueName;
        this.partitionValueTypeName = partitionValueTypeName;
    }

    /**
     * PUBLIC:
     * Create the partition for the connectionPool and start/end values.
     */
    public RangePartition(String connectionPool, Comparable startValue, Comparable endValue) {
        this.connectionPool = connectionPool;
        this.startValue = startValue;
        this.endValue = endValue;
    }

    /**
     * INTERNAL:
     * Convert all the class-name-based settings to actual class-based settings.
     * This method is used when converting a project that has been built with
     * class names to a project with classes.
     */
    public void convertClassNamesToClasses(ClassLoader classLoader) {
        if (partitionValueType == null && partitionValueTypeName != null) {
            try {
                if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
                    try {
                        partitionValueType = AccessController.doPrivileged(new PrivilegedClassForName(partitionValueTypeName, true, classLoader));
                    } catch (PrivilegedActionException e) {
                        throw ValidationException.classNotFoundWhileConvertingClassNames(partitionValueTypeName, e.getException());
                    }
                } else {
                    partitionValueType = org.eclipse.persistence.internal.security.PrivilegedAccessHelper.getClassForName(partitionValueTypeName, true, classLoader);
                }
            } catch (Exception exception) {
                throw ValidationException.classNotFoundWhileConvertingClassNames(partitionValueTypeName, exception);
            }
        }

        // Once we know we have a partition value type we can convert our partition ranges.
        if (partitionValueType != null) {
            if (startValueName != null) {
                startValue = (Comparable) initObject(partitionValueType, startValueName);
            }

            if (endValueName != null) {
                endValue = (Comparable) initObject(partitionValueType, endValueName);
            }
        }
    }

    /**
     * PUBLIC:
     * Return the range start value.  Values greater or equal to this value are part of this partition.
     */
    public Comparable getStartValue() {
        return startValue;
    }

    /**
     * INTERNAL:
     * TODO: clean up the exception handling.
     */
    protected Object initObject(Class type, String value) {
        if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
            try {
                Constructor constructor = AccessController.doPrivileged(new PrivilegedGetConstructorFor(type, new Class[] {String.class}, false));
                return AccessController.doPrivileged(new PrivilegedInvokeConstructor(constructor, new Object[] {value}));
            } catch (PrivilegedActionException exception) {
                //throwInitObjectException(exception, type, value, isData);
            }
        } else {
            try {
                Constructor constructor = PrivilegedAccessHelper.getConstructorFor(type, new Class[] {String.class}, false);
                return PrivilegedAccessHelper.invokeConstructor(constructor, new Object[] {value});
            } catch (Exception exception) {
                //throwInitObjectException(exception, type, value, isData);
            }
        }

        return value;
    }

    /**
     * PUBLIC:
     * Set the range start value.  Values greater or equal to this value are part of this partition.
     */
    public void setStartValue(Comparable startValue) {
        this.startValue = startValue;
    }

    /**
     * PUBLIC:
     * Return the range end value.  Values less than or equal this value are part of this partition.
     */
    public Comparable getEndValue() {
        return endValue;
    }

    /**
     * PUBLIC:
     * Set the range end value.  Values less than or equal this value are part of this partition.
     */
    public void setEndValue(Comparable endValue) {
        this.endValue = endValue;
    }

    /**
     * PUBLIC:
     * Return the connection pool to use for this partition.
     */
    public String getConnectionPool() {
        return connectionPool;
    }

    /**
     * PUBLIC:
     * Return the connection pool to use for this partition.
     */
    public void setConnectionPool(String connectionPool) {
        this.connectionPool = connectionPool;
    }

    /**
     * INTERNAL:
     * Return if the value is in the partitions range.
     */
    public boolean isInRange(Object value) {
        if ((this.startValue != null) && (this.startValue.compareTo(value) > 0)) {
            return false;
        }
        if ((this.endValue != null) && (this.endValue.compareTo(value) < 0)) {
            return false;
        }
        return true;
    }

}
