/*
 * 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.internal.queries;

import java.util.*;

import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.expressions.*;
import org.eclipse.persistence.exceptions.*;
import org.eclipse.persistence.queries.*;

/**
 * <p><b>Purpose</b>:
 * Mechanism used for all queries by example.
 * <p>
 * <p><b>Responsibilities</b>:
 * Builds a selection criteria for this query given an example object and
 * a QueryByExample policy.  Works akin to EBQLMechanism which builds the
 * selection criteria from an EJBQL string.
 *
 * @author Stephen McRitchie
 * @since 9.0.3.5
 */
public class QueryByExampleMechanism extends ExpressionQueryMechanism {
    protected boolean isParsed = false;

    /** Used for Query By Example. */
    protected Object exampleObject;
    protected QueryByExamplePolicy queryByExamplePolicy;

    /**
     * Initialize the state of the query
     * @param query - owner of mechanism
     */
    public QueryByExampleMechanism(DatabaseQuery query) {
        super(query);
    }

    /**
     * Initialize the state of the query
     * @param query - owner of mechanism
     * @param expression - selection criteria
     */
    public QueryByExampleMechanism(DatabaseQuery query, Expression expression) {
        super(query, expression);
    }

    /**
     * INTERNAL:
     * In the case of EJBQL or query by example, an expression needs to be
     * generated. Build the required expression.
     */
    public void buildSelectionCriteria(AbstractSession session) {
        if (isParsed() || (getExampleObject() == null)) {
            return;
        }
        ObjectLevelReadQuery query = (ObjectLevelReadQuery)getQuery();

        query.checkDescriptor(session);

        QueryByExamplePolicy policy = getQueryByExamplePolicy();

        if (policy == null) {
            policy = new QueryByExamplePolicy();
        }

        if (query.getReferenceClass().isInstance(getExampleObject())) {
            Expression exampleExpression = query.getDescriptor().getObjectBuilder().buildExpressionFromExample(getExampleObject(), policy, query.getExpressionBuilder(), new IdentityHashMap(), session);

            if (getSelectionCriteria() != null) {
                setSelectionCriteria(getSelectionCriteria().and(exampleExpression));
            } else {
                setSelectionCriteria(exampleExpression);
            }
        } else {
            throw QueryException.exampleAndReferenceObjectClassMismatch(getExampleObject().getClass(), query.getReferenceClass(), query);
        }
        setIsParsed(true);
    }

    /**
     * PUBLIC:
     * This method returns the current example object.  The "example" object is an actual domain object, provided
     * by the client, from which an expression is generated.
     * This expression is used for a query of all objects from the same class, that match the attribute values of
     * the "example" object.
     */
    public Object getExampleObject() {
        return exampleObject;
    }

    /**
     * PUBLIC:
     * When using Query By Example, an instance of QueryByExamplePolicy is used to customize the query.
     * The policy is useful when special operations are to be used for comparisons (notEqual, lessThan,
     * greaterThan, like etc.), when a certain value is to be ignored, or when dealing with nulls.
     */
    public QueryByExamplePolicy getQueryByExamplePolicy() {
        return queryByExamplePolicy;
    }

    /**
     * INTERNAL:
     * Is this query Parsed
     */
    public boolean isParsed() {
        return isParsed;
    }

    /**
     * Return true if this is a query by example mechanism
     */
    public boolean isQueryByExampleMechanism() {
        return true;
    }

    /**
     * PUBLIC:
     * Set the example object of the query to be the newExampleObject.
     * The example object is used for Query By Example.
     * When doing a Query By Example, an instance of the desired object is created, and the fields are filled with
     * the values that are required in the result set.  From these values the corresponding expression is build
     * by TopLink, and the query is executed, returning the set of results.
     */
    public void setExampleObject(Object newExampleObject) {
        exampleObject = newExampleObject;
    }

    /**
     * INTERNAL:
     * Set the isParsed state
     */
    public void setIsParsed(boolean newIsParsed) {
        isParsed = newIsParsed;
    }

    /**
     * PUBLIC:
     * The QueryByExamplePolicy, is a useful to customize the query when Query By Example is used.
     * The pollicy will control what attributes should, or should not be included in the query.
     * When dealing with nulls, using specail operations (notEqual, lessThan, like, etc.)
     * for comparison, or chosing to include certain attributes at all times, it is useful to modify
     * the policy accordingly.
     */
    public void setQueryByExamplePolicy(QueryByExamplePolicy queryByExamplePolicy) {
        this.queryByExamplePolicy = queryByExamplePolicy;
    }
}
