/*
 * Copyright (c) 2012, 2021 Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2021 IBM Corporation. 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
//
package org.eclipse.persistence.jpa.jpql;

import java.util.List;
import java.util.Map;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.JPQLGrammar;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;

/**
 * This helper is used by {@link AbstractSemanticValidator} in order to retrieve JPA information.
 * This helper allows third party adopter to write an instance of this helper that directly access
 * the JPA information without having to implement Hermes SPI, which can improve performance.
 * <p>
 * {@link org.eclipse.persistence.jpa.jpql.tools.GenericSemanticValidatorHelper
 * GenericSemanticValidatorHelper} is a default implementation that uses Hermes SPI.
 * <p>
 * Provisional API: This interface is part of an interim API that is still under development and
 * expected to change significantly before reaching stability. It is available at this early stage
 * to solicit feedback from pioneering adopters on the understanding that any code that uses this
 * API will almost certainly be broken (repeatedly) as the API evolves.
 *
 * @version 2.5
 * @since 2.4
 * @author Pascal Filion
 */
public interface SemanticValidatorHelper {

    /**
     * Collects the identification variables that are defined in the <code>FROM</code> clause of the
     * current query and from the parent queries.
     *
     * @param identificationVariables The {@link Map} used to store the variables
     */
    void collectAllDeclarationIdentificationVariables(Map<String, List<IdentificationVariable>> identificationVariables);

    /**
     * Collects the identification variables that are defined in the <code>FROM</code> clause of the
     * current query.
     *
     * @param identificationVariables The {@link Map} used to store the variables
     */
    void collectLocalDeclarationIdentificationVariables(Map<String, List<IdentificationVariable>> identificationVariables);

    /**
     * Disposes this context, which is the current context being used by a subquery. Once it is
     * disposed, any information retrieved will be for the subquery's parent query.
     */
    void disposeSubqueryContext();

    /**
     * Returns the name of the all entities that are present in the context of a persistence unit.
     *
     * @return The list of entity names
     */
    String[] entityNames();

    /**
     * Returns the ordered list of {@link JPQLQueryDeclaration}, which contain the information
     * contained in the query's <code>FROM</code> clause.
     *
     * @return The list of {@link JPQLQueryDeclaration} of the current query that was parsed and from
     * the parent queries
     */
    List<JPQLQueryDeclaration> getAllDeclarations();

    /**
     * Returns the constructors for the given type. All public, protected, default (package) access,
     * and private constructors should be included.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IType IType} and the return type would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IConstructor IConstructor}.
     *
     * @return The declared constructors
     */
    Object[] getConstructors(Object type);

    /**
     * Returns the ordered list of {@link JPQLQueryDeclaration}, which contain the information
     * contained in the query's <code>FROM</code> clause.
     *
     * @return The list of {@link JPQLQueryDeclaration} of the current query that was parsed
     */
    List<JPQLQueryDeclaration> getDeclarations();

    /**
     * Retrieves the embeddable with the given type.
     *
     * @param type The Java type of the embeddable to retrieve
     * @return The embeddable for the given type if it's representing an embeddable; <code>null</code>
     * otherwise
     */
    Object getEmbeddable(Object type);

    /**
     * Retrieves the entity with the given entity name.
     *
     * @param entityName The abstract schema name of the entity to retrieve
     * @return The entity with the given name; <code>null</code> otherwise
     */
    Object getEntityNamed(String entityName);

    /**
     * Returns the constant names for the given {@link Enum} type.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @param type The {@link Enum} type
     * @return The list of constant names
     */
    String[] getEnumConstants(Object type);

    /**
     * Returns the {@link JPQLGrammar} that defines how the JPQL query was parsed.
     *
     * @return The {@link JPQLGrammar} that was used to parse the JPQL query
     */
    JPQLGrammar getGrammar();

    /**
     * Returns the managed type by resolving the given {@link Expression}.
     * <p>
     * If it was going through Hermes SPI, the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType}.
     */
    Object getManagedType(Expression expression);

    /**
     * Returns the mapping with the given name.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType}
     * and the return type would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param managedType The managed type that has a mapping with the given name
     * @param name The name of the mapping to retrieve
     * @return Either the mapping or <code>null</code> if it could not be found
     */
    Object getMappingNamed(Object managedType, String name);

    /**
     * Returns the type of the given mapping object.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping} and the return type
     * would be {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @param mapping The mapping object
     * @return The type of the given mapping
     */
    Object getMappingType(Object mapping);

    /**
     * Returns the list of type declarations representing the given constructor's parameter types.
     * If this is the default constructor, then an empty array should be returned.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IConstructor IConstructor} and the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration ITypeDeclaration}.
     *
     * @param constructor The constructor to return its parameter types
     * @return The list of parameter types or an empty list
     */
    Object[] getMethodParameterTypeDeclarations(Object constructor);

    /**
     * Returns the reference managed type from the given relationship mapping.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping} and the return type would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType}.
     *
     * @param relationshipMapping The relationship mapping
     * @return The managed type referenced by the given relationship mapping
     */
    Object getReferenceManagedType(Object relationshipMapping);

    /**
     * Returns the type by resolving the given {@link Expression}.
     * <p>
     * If it was going through Hermes SPI, the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @param expression The {@link Expression} to resolve
     * @return The type of the given {@link Expression} or <code>null</code> if it could not be
     * validated
     */
    Object getType(Expression expression);

    /**
     * Returns the type defined for the Java member.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be {@link
     * org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration ITypeDeclaration} and the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @return The type defined for the Java member
     */
    Object getType(Object typeDeclaration);

    /**
     * Retrieves the class with the given fully qualified name.
     * <p>
     * If it was going through Hermes SPI, an {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType
     * IType} would be returned.
     *
     * @param typeName The fully qualified  name of the class to retrieve
     * @return The class to retrieve
     */
    Object getType(String typeName);

    /**
     * Returns the type declaration for the given {@link Expression}'s type.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration ITypeDeclaration}.
     *
     * @param expression The {@link Expression} to resolve
     * @return Either the type declaration that was resolved for the given {@link Expression}
     */
    Object getTypeDeclaration(Expression expression);

    /**
     * Returns the helper that gives access to the most common class metadata.
     *
     * @return A helper containing a collection of methods related to class metadata
     */
    ITypeHelper getTypeHelper();

    /**
     * Returns the fully qualified class name of the given type.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @param type The type to retrieve its name
     * @return The name of the class represented by this one
     */
    String getTypeName(Object type);

    /**
     * Determines whether type 1 is an instance of type 2.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @param type1 The type to check if it is an instance of type 2
     * @param type2 The type used to determine if the class represented by type 1 is an instance
     * of with one
     * @return <code>true</code> if type 1 is an instance of the type 2; <code>false</code> otherwise
     */
    boolean isAssignableTo(Object type1, Object type2);

    /**
     * Determines whether the given identification variable is defining a join or a collection member
     * declaration expressions.
     *
     * @param variableName The identification variable to check for what it maps
     * @return <code>true</code> if the given identification variable maps a collection-valued field
     * defined in a <code>JOIN</code> or <code>IN</code> expression; <code>false</code> if it's not
     * defined or it's mapping an abstract schema name
     */
    boolean isCollectionIdentificationVariable(String variableName);

    /**
     * Determines whether the given mapping is a collection type mapping.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param mapping The mapping object to verify if it represents a collection mapping
     * @return <code>true</code> if the given mapping is a collection mapping; <code>false</code>
     * otherwise
     */
    boolean isCollectionMapping(Object mapping);

    /**
     * Determines whether the given mapping is an embeddable type mapping.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param mapping The mapping object to verify if it represents an embeddable mapping
     * @return <code>true</code> if the given mapping is an embeddable mapping; <code>false</code>
     * otherwise
     */
    boolean isEmbeddableMapping(Object mapping);

    /**
     * Determines whether the given type represents an {@link Enum}.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @return <code>true</code> if the given type is an {@link Enum}; <code>false</code> otherwise
     */
    boolean isEnumType(Object type);

    /**
     * Determines whether an identification variable can be used in a comparison expression when the
     * operator is either {@literal '<', '<=', '>', '>='}.
     *
     * @param expression The {@link IdentificationVariable} that is mapped to either an entity, a
     * singled-object value field, a collection-valued object field
     * @return <code>true</code> if it can be used in a ordering comparison expression; <code>false</code>
     * if it can't
     */
    boolean isIdentificationVariableValidInComparison(IdentificationVariable expression);

    /**
     * Determines whether the given managed type actually exists.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IManagedType IManagedType}.
     *
     * @return <code>true</code> if the given managed type can be located; <code>false</code> if it
     * could not be found
     */
    boolean isManagedTypeResolvable(Object managedType);

    /**
     * Determines whether the given mapping is a property type mapping.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param mapping The mapping object to verify if it represents a property mapping
     * @return <code>true</code> if the given mapping is a property mapping; <code>false</code> otherwise
     */
    boolean isPropertyMapping(Object mapping);

    /**
     * Determines whether the given mapping is a relationship type mapping.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param mapping The mapping object to verify if it represents a relationship mapping
     * @return <code>true</code> if the given mapping is a relationship mapping; <code>false</code> otherwise
     */
    boolean isRelationshipMapping(Object mapping);

    /**
     * Determines if the given variable is a result variable.
     *
     * @param variableName The variable to check if it's a result variable
     * @return <code>true</code> if the given variable is defined as a result variable;
     * <code>false</code> otherwise
     */
    boolean isResultVariable(String variableName);

    /**
     * Determines whether the given mapping is a transient attribute.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param mapping The mapping object to verify if it represents a transient attribute
     * @return <code>true</code> if the given attribute is a transient mapping; <code>false</code> otherwise
     */
    boolean isTransient(Object mapping);

    /**
     * Determines whether type declaration 1 is an instance of type declaration 2.
     * <p>
     * If it was going through Hermes SPI, the type of the arguments would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.ITypeDeclaration ITypeDeclaration}.
     *
     * @param typeDeclaration1 The type declaration to check if it is an instance of type declaration 2
     * @param typeDeclaration2 The type used to determine if the class represented by type
     * declaration 1 is an instance of with one
     * @return <code>true</code> if type declaration 1 is an instance of the type declaration 2;
     * <code>false</code> otherwise
     */
    boolean isTypeDeclarationAssignableTo(Object typeDeclaration1, Object typeDeclaration2);

    /**
     * Determines whether the given type actually exists.
     * <p>
     * If it was going through Hermes SPI, the type of the argument would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IType IType}.
     *
     * @return <code>true</code> if the actual class exists; <code>false</code> otherwise
     */
    boolean isTypeResolvable(Object type);

    /**
     * Changes the state of this helper to use the given subquery.
     *
     * @param expression The parsed tree representation of the subquery that will become the current query
     * @see #disposeSubqueryContext()
     */
    void newSubqueryContext(SimpleSelectStatement expression);

    /**
     * Returns the mapping for the field represented by the given {@link Expression}.
     * <p>
     * If it was going through Hermes SPI, the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param expression The {@link Expression} representing a state field path expression or a
     * collection-valued path expression
     * @return Either the mapping or <code>null</code> if none exists
     */
    Object resolveMapping(Expression expression);

    /**
     * Returns the mapping that should be a persistence field from the entity defined by the given
     * identification variable.
     * <p>
     * If it was going through Hermes SPI, the return type would be
     * {@link org.eclipse.persistence.jpa.jpql.tools.spi.IMapping IMapping}.
     *
     * @param identificationVariable The identification variable that is defined in the <code>FROM</code>
     * clause of the query (which can be in the current subquery) or in one of the parent queries.
     * @param name The name of the persistent field to retrieve
     * @return The persistence field with the given name or <code>null</code> if it could not be found
     */
    Object resolveMapping(String identificationVariable, String name);
}
