Package org.eclipse.persistence.jpa.jpql.tools

Source Code of org.eclipse.persistence.jpa.jpql.tools.DefaultSemanticValidator

/*******************************************************************************
* Copyright (c) 2006, 2013 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:
*     Oracle - initial API and implementation
*
******************************************************************************/
package org.eclipse.persistence.jpa.jpql.tools;

import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.jpa.jpql.AbstractSemanticValidator;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.ITypeHelper;
import org.eclipse.persistence.jpa.jpql.JPAVersion;
import org.eclipse.persistence.jpa.jpql.LiteralType;
import org.eclipse.persistence.jpa.jpql.LiteralVisitor;
import org.eclipse.persistence.jpa.jpql.SemanticValidatorHelper;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AdditionExpression;
import org.eclipse.persistence.jpa.jpql.parser.AllOrAnyExpression;
import org.eclipse.persistence.jpa.jpql.parser.AndExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticExpression;
import org.eclipse.persistence.jpa.jpql.parser.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.BooleanPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.EncapsulatedIdentificationVariableExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntryExpression;
import org.eclipse.persistence.jpa.jpql.parser.ExistsExpression;
import org.eclipse.persistence.jpa.jpql.parser.Expression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
import org.eclipse.persistence.jpa.jpql.parser.JPQLQueryBNF;
import org.eclipse.persistence.jpa.jpql.parser.KeyExpression;
import org.eclipse.persistence.jpa.jpql.parser.KeywordExpression;
import org.eclipse.persistence.jpa.jpql.parser.LengthExpression;
import org.eclipse.persistence.jpa.jpql.parser.LikeExpression;
import org.eclipse.persistence.jpa.jpql.parser.LiteralBNF;
import org.eclipse.persistence.jpa.jpql.parser.LocateExpression;
import org.eclipse.persistence.jpa.jpql.parser.LowerExpression;
import org.eclipse.persistence.jpa.jpql.parser.MaxFunction;
import org.eclipse.persistence.jpa.jpql.parser.MinFunction;
import org.eclipse.persistence.jpa.jpql.parser.ModExpression;
import org.eclipse.persistence.jpa.jpql.parser.MultiplicationExpression;
import org.eclipse.persistence.jpa.jpql.parser.NotExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullExpression;
import org.eclipse.persistence.jpa.jpql.parser.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.OrExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrderByClause;
import org.eclipse.persistence.jpa.jpql.parser.OrderByItem;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.SimpleArithmeticExpressionBNF;
import org.eclipse.persistence.jpa.jpql.parser.SizeExpression;
import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.StringLiteral;
import org.eclipse.persistence.jpa.jpql.parser.StringPrimaryBNF;
import org.eclipse.persistence.jpa.jpql.parser.SubExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubstringExpression;
import org.eclipse.persistence.jpa.jpql.parser.SubtractionExpression;
import org.eclipse.persistence.jpa.jpql.parser.SumFunction;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateItem;
import org.eclipse.persistence.jpa.jpql.parser.UpperExpression;
import org.eclipse.persistence.jpa.jpql.parser.ValueExpression;
import static org.eclipse.persistence.jpa.jpql.JPQLQueryProblemMessages.*;

/**
* This validator is responsible to gather the problems found in a JPQL query by validating the
* content to make sure it is semantically valid. The grammar is not validated by this visitor.
* <p>
* For instance, the function <b>AVG</b> accepts a state field path. The property it represents has
* to be of numeric type. <b>AVG(e.name)</b> is parsable but is not semantically valid because the
* type of name is a string (the property signature is: "<code>private String name</code>").
* <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.
*
* @see DefaultGrammarValidator
*
* @version 2.5
* @since 2.3
* @author Pascal Filion
*/
@SuppressWarnings("nls")
public class DefaultSemanticValidator extends AbstractSemanticValidator {

  /**
   * This validator determines whether the {@link Expression} visited represents
   * {@link Expression#NULL}.
   */
  protected NullValueVisitor nullValueVisitor;

  /**
   * This finder is responsible to retrieve the abstract schema name from the <b>UPDATE</b> range
   * declaration expression.
   */
  protected UpdateClauseAbstractSchemaNameFinder updateClauseAbstractSchemaNameFinder;

  /**
   * The {@link TypeValidator TypeVlidators} mapped to their Java class. Those validators validate
   * any {@link Expression} by making sure its type matches the desired type.
   */
  protected Map<Class<? extends TypeValidator>, TypeValidator> validators;

  /**
   * Creates a new <code>DefaultSemanticValidator</code>.
   *
   * @param queryContext The context used to query information about the JPQL query
   * @exception NullPointerException The given {@link JPQLQueryContext} cannot be <code>null</code>
   */
  public DefaultSemanticValidator(JPQLQueryContext queryContext) {
    super(new GenericSemanticValidatorHelper(queryContext));
  }

  /**
   * Creates a new <code>DefaultSemanticValidator</code>.
   *
   * @param helper The given helper allows the validator to access the JPA artifacts without using
   * Hermes SPI directly
   * @exception NullPointerException The given {@link SemanticValidatorHelper} cannot be <code>null</code>
   * @since 2.4
   */
  public DefaultSemanticValidator(SemanticValidatorHelper helper) {
    super(helper);
  }

  protected boolean areTypesEquivalent(Object[] typeDeclarations1, Object[] typeDeclarations2) {

    // Empty array
    if ((typeDeclarations1.length == 0) && (typeDeclarations2.length == 0)) {
      return true;
    }

    // Different array length, always not equivalent
    if (typeDeclarations1.length != typeDeclarations2.length) {
      return false;
    }

    // Compare each element of the array together
    for (int index = typeDeclarations1.length; --index >= 0; ) {
      if (!helper.isTypeDeclarationAssignableTo(typeDeclarations1[index], typeDeclarations2[index])) {
        return false;
      }
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected LiteralVisitor buildLiteralVisitor() {
    return new DefaultLiteralVisitor();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected OwningClauseVisitor buildOwningClauseVisitor() {
    return new OwningClauseVisitor();
  }

  protected ResultVariableInOrderByVisitor buildResultVariableInOrderByVisitor() {
    return new ResultVariableInOrderByVisitor();
  }

  protected AbstractSchemaName findAbstractSchemaName(UpdateItem expression) {
    UpdateClauseAbstractSchemaNameFinder visitor = getUpdateClauseAbstractSchemaNameFinder();
    try {
      expression.accept(visitor);
      return visitor.expression;
    }
    finally {
      visitor.expression = null;
    }
  }

  protected NullValueVisitor getNullValueVisitor() {
    if (nullValueVisitor == null) {
      nullValueVisitor = new NullValueVisitor();
    }
    return nullValueVisitor;
  }

  protected Object getType(Expression expression) {
    return helper.getType(expression);
  }

  protected ITypeHelper getTypeHelper() {
    return helper.getTypeHelper();
  }

  protected UpdateClauseAbstractSchemaNameFinder getUpdateClauseAbstractSchemaNameFinder() {
    if (updateClauseAbstractSchemaNameFinder == null) {
      updateClauseAbstractSchemaNameFinder = new UpdateClauseAbstractSchemaNameFinder();
    }
    return updateClauseAbstractSchemaNameFinder;
  }

  protected TypeValidator getValidator(Class<? extends TypeValidator> validatorClass) {
    TypeValidator validator = validators.get(validatorClass);
    if (validator == null) {
      try {
        Constructor<? extends TypeValidator> constructor = validatorClass.getDeclaredConstructor(DefaultSemanticValidator.class);
        constructor.setAccessible(true);
        validator = constructor.newInstance(this);
        validators.put(validatorClass, validator);
      }
      catch (Exception e) { /* Never happens */ }
    }
    return validator;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void initialize() {
    super.initialize();
    validators = new HashMap<Class<? extends TypeValidator>, TypeValidator>();
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a boolean value;</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
   * otherwise
   */
  protected boolean isBooleanType(Expression expression) {
    return isValid(expression, BooleanTypeValidator.class);
  }

  protected boolean isComparisonEquivalentType(Expression expression1, Expression expression2) {

    Object type1 = helper.getType(expression1);
    Object type2 = helper.getType(expression2);

    // 1. The types are the same
    // 2. The type cannot be determined, pretend they are equivalent,
    //    another rule will validate it
    // 3. One is assignable to the other one
    ITypeHelper typeHelper = getTypeHelper();

    return (type1 == type2) ||
           !helper.isTypeResolvable(type1) ||
           !helper.isTypeResolvable(type2) ||
            typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) ||
            typeHelper.isDateType(type1)    && typeHelper.isDateType(type2)    ||
            helper.isAssignableTo(type1, type2) ||
            helper.isAssignableTo(type2, type1);
  }

  protected boolean isEquivalentBetweenType(Expression expression1, Expression expression2) {

    Object type1 = helper.getType(expression1);
    Object type2 = helper.getType(expression2);

    // The type cannot be determined, pretend they are equivalent,
    // another rule will validate it
    if (!helper.isTypeResolvable(type1) ||
        !helper.isTypeResolvable(type2)) {

      return true;
    }

    ITypeHelper typeHelper = getTypeHelper();

    if (type1 == type2) {
      return typeHelper.isNumericType(type1) ||
      typeHelper.isStringType(type1||
      typeHelper.isDateType(type1);
    }
    else {
      return typeHelper.isNumericType(type1) && typeHelper.isNumericType(type2) ||
             typeHelper.isStringType(type1&& typeHelper.isStringType(type2||
             typeHelper.isDateType(type1)    && typeHelper.isDateType(type2);
    }
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression}'s type is an integral type (long or integer).</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
   * otherwise
   */
  protected boolean isIntegralType(Expression expression) {

    if (isNumericType(expression)) {

      ITypeHelper typeHelper = getTypeHelper();
      Object type = helper.getType(expression);

      return type == typeHelper.unknownType() ||
             typeHelper.isIntegralType(type);
    }

    return false;
  }

  protected boolean isNullValue(Expression expression) {
    NullValueVisitor visitor = getNullValueVisitor();
    try {
      expression.accept(visitor);
      return visitor.valid;
    }
    finally {
      visitor.valid = false;
    }
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a numeric value;</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
   * otherwise
   */
  protected boolean isNumericType(Expression expression) {
    return isValid(expression, NumericTypeValidator.class);
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression}'s type is a string type.</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
   * otherwise
   */
  protected boolean isStringType(Expression expression) {
    return isValid(expression, StringTypeValidator.class);
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type by using the
   * {@link TypeValidator}.
   *
   * @param expression The {@link Expression} to validate
   * @param validatorClass The Java class of the {@link TypeValidator} that will determine if the
   * given {@link Expression} has the right type
   * @return <code>true</code> if the given {@link Expression} passes the checks; <code>false</code>
   * otherwise
   */
  protected boolean isValid(Expression expression, Class<? extends TypeValidator> validatorClass) {
    TypeValidator validator = getValidator(validatorClass);
    try {
      expression.accept(validator);
      return validator.valid;
    }
    finally {
      validator.valid = false;
    }
  }

  protected boolean isValidWithFindQueryBNF(AbstractExpression expression, String queryBNF) {
    JPQLQueryBNFValidator validator = getExpressionValidator(queryBNF);
    try {
      JPQLQueryBNF childQueryBNF = expression.getParent().findQueryBNF(expression);
      validator.validate(childQueryBNF);
      return validator.isValid();
    }
    finally {
      validator.dispose();
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected PathType selectClausePathExpressionPathType() {
    return PathType.ANY_FIELD;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateAbsExpression(AbsExpression expression) {

    boolean valid = super.validateAbsExpression(expression);

    if (valid) {
      // The ABS function takes a numeric argument
      valid = validateNumericType(expression.getExpression(), AbsExpression_InvalidNumericExpression);
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateArithmeticExpression(ArithmeticExpression expression,
                                             String leftExpressionWrongTypeMessageKey,
                                             String rightExpressionWrongTypeMessageKey) {

    int result = super.validateArithmeticExpression(
      expression,
      leftExpressionWrongTypeMessageKey,
      rightExpressionWrongTypeMessageKey
    );

    // Only validate the left expression if it's still valid
    if (isValid(result, 0)) {
      boolean valid = validateNumericType(expression.getLeftExpression(), leftExpressionWrongTypeMessageKey);
      updateStatus(result, 0, valid);
    }

    // Validate the right expression
    boolean valid = validateNumericType(expression.getRightExpression(), rightExpressionWrongTypeMessageKey);
    updateStatus(result, 1, valid);

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateAvgFunction(AvgFunction expression) {

    // Arguments to the functions AVG must be numeric
    boolean valid = super.validateAvgFunction(expression);

    if (valid) {
      valid = validateNumericType(expression.getExpression(), AvgFunction_InvalidNumericExpression);
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateBetweenExpression(BetweenExpression expression) {

    int result = super.validateBetweenExpression(expression);

    // Only add extra validation if the lower and upper expressions are still valid
    if (isValid(result, 1) &&
        isValid(result, 2) &&
        !isEquivalentBetweenType(expression.getExpression(), expression.getLowerBoundExpression()) ||
        !isEquivalentBetweenType(expression.getExpression(), expression.getUpperBoundExpression())) {

      addProblem(expression, BetweenExpression_WrongType);
      updateStatus(result, 1, false);
      updateStatus(result, 2, false);
    }

    return result;
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a boolean value;</li>
   * <li>The {@link Expression}'s type is a boolean type.</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @param messageKey The key used to retrieve the localized message describing the problem
   */
  protected boolean validateBooleanType(Expression expression, String messageKey) {

    if (isValid(expression, BooleanPrimaryBNF.ID) && !isBooleanType(expression)) {
      addProblem(expression, messageKey);
      return false;
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateCollectionMemberExpression(CollectionMemberExpression expression) {

    int result = super.validateCollectionMemberExpression(expression);

    if (isValid(result, 0) && expression.hasEntityExpression()) {
      Expression entityExpression = expression.getEntityExpression();

      // Check for embeddable type
      Object type = helper.getType(entityExpression);

      if (helper.getEmbeddable(type) != null) {
        addProblem(entityExpression, CollectionMemberExpression_Embeddable);
        updateStatus(result, 0, false);
      }
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateComparisonExpression(ComparisonExpression expression) {

    boolean valid = super.validateComparisonExpression(expression);

    // Only validate the left and right if they are still valid
    if (valid && expression.hasLeftExpression() && expression.hasRightExpression()) {

      // The result of the two expressions must be like that of the other argument
      // Comparisons over instances of embeddable class or map entry types are not supported
      if (!isComparisonEquivalentType(expression.getLeftExpression(),
                                      expression.getRightExpression())) {

        addProblem(expression, ComparisonExpression_WrongComparisonType);
        valid = false;
      }
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateConcatExpression(ConcatExpression expression) {

    boolean result = super.validateConcatExpression(expression);

    // The CONCAT function returns a string that is a concatenation of its arguments
    if (expression.hasExpression()) {
      int index = 0;
      for (Expression child : getChildren(expression.getExpression())) {
        // Don't validate the first expression if it is not valid
        if (index != 0 || result) {
          result &= validateStringType(child, ConcatExpression_Expression_WrongType);
        }
        index++;
      }
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateConstructorExpression(ConstructorExpression expression) {

    super.validateConstructorExpression(expression);
    String className = expression.getClassName();

    // Only test the constructor if it has been specified
    if (ExpressionTools.stringIsNotEmpty(className)) {
      Object type = helper.getType(className);

      // Unknown type
      if (!helper.isTypeResolvable(type)) {
        int startPosition = position(expression) + 4 /* NEW + whitespace */;
        int endPosition = startPosition + className.length();
        addProblem(expression, startPosition, endPosition, ConstructorExpression_UnknownType, className);
      }
      // Test the arguments' types with the constructors' types
      else if (expression.hasLeftParenthesis() &&
               expression.hasConstructorItems()) {

        Expression constructorItems = expression.getConstructorItems();

        // Retrieve the constructor's arguments so their type can be calculated
        List<Expression> children = getChildren(constructorItems);
        Object[] typeDeclarations = null;
        boolean constructorFound = false;

        for (Object constructor : helper.getConstructors(type)) {
          Object[] parameterTypeDeclarations = helper.getMethodParameterTypeDeclarations(constructor);

          // The number of items match, check their types are equivalent
          if (children.size() == parameterTypeDeclarations.length) {

            // The constructor is the default constructor and both have no parameters
            if (parameterTypeDeclarations.length == 0) {
              constructor = true;
            }
            else {

              // Populate the type declaration array
              if (typeDeclarations == null) {
                typeDeclarations = new Object[children.size()];

                for (int index = children.size(); --index >= 0; ) {
                  typeDeclarations[index] = helper.getTypeDeclaration(children.get(index));
                }
              }

              constructorFound = areTypesEquivalent(parameterTypeDeclarations, typeDeclarations);

              if (constructorFound) {
                break;
              }
            }
          }
        }

        // No constructor was found matching the argument list
        if (!constructorFound) {
          int startPosition = position(expression) + 4 /* NEW + whitespace */;
          int endPosition = startPosition + className.length();
          addProblem(expression, startPosition, endPosition, ConstructorExpression_UndefinedConstructor);
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateCountFunction(CountFunction expression) {

    super.validateCountFunction(expression);

    // The use of DISTINCT with COUNT is not supported for arguments of
    // embeddable types or map entry types (weird because map entry is not
    // allowed in a COUNT expression)
    if (expression.hasExpression() &&
        expression.hasDistinct()) {

      Expression childExpression = expression.getExpression();

      // Check for embeddable type
      Object type = helper.getType(childExpression);

      if (helper.getEmbeddable(type) != null) {
        int distinctLength = Expression.DISTINCT.length() + 1; // +1 = space
        int startIndex  = position(childExpression) - distinctLength;
        int endIndex    = startIndex + length(childExpression) + distinctLength;
        addProblem(expression, startIndex, endIndex, CountFunction_DistinctEmbeddable);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateEntryExpression(EntryExpression expression) {
    validateMapIdentificationVariable(expression);
    super.validateEntryExpression(expression);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateIdentificationVariable(IdentificationVariable expression, String variable) {

    boolean valid = super.validateIdentificationVariable(expression, variable);

    for (String entityName : helper.entityNames()) {

      // An identification variable must not have the same name as any entity in the same
      // persistence unit, unless it's representing an entity literal
      if (variable.equalsIgnoreCase(entityName)) {

        // An identification variable could represent an entity type literal,
        // validate the parent to make sure it allows it
        if (!isValidWithFindQueryBNF(expression, LiteralBNF.ID)) {
          int startIndex = position(expression);
          int endIndex   = startIndex + variable.length();
          addProblem(expression, startIndex, endIndex, IdentificationVariable_EntityName);
          valid = false;
          break;
        }
      }
    }

    return valid;
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a integral value;</li>
   * <li>The {@link Expression}'s type is an integral type (long or integer).</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @param queryBNF The unique identifier of the query BNF used to validate the type
   * @param messageKey The key used to retrieve the localized message describing the problem
   * @return <code>false</code> if the given expression was validated and is invalid;
   * <code>true</code> otherwise
   */
  protected boolean validateIntegralType(Expression expression, String queryBNF, String messageKey) {

    if (isValid(expression, queryBNF) && !isIntegralType(expression)) {
      addProblem(expression, messageKey);
      return false;
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateKeyExpression(KeyExpression expression) {
    validateMapIdentificationVariable(expression);
    super.validateKeyExpression(expression);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateLengthExpression(LengthExpression expression) {

    boolean valid = super.validateLengthExpression(expression);

    if (valid) {
      valid = validateStringType(expression.getExpression(), LengthExpression_WrongType);
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateLocateExpression(LocateExpression expression) {

    int result = super.validateLocateExpression(expression);

    // The first argument is the string to be located; the second argument is the string to be
    // searched; the optional third argument is an integer that represents the string position at
    // which the search is started (by default, the beginning of the string to be searched)
    if (isValid(result, 0)) {
      boolean valid = validateStringType(expression.getFirstExpression(), LocateExpression_FirstExpression_WrongType);
      updateStatus(result, 0, valid);
    }

    if (isValid(result, 1)) {
      boolean valid = validateStringType (expression.getSecondExpression(), LocateExpression_SecondExpression_WrongType);
      updateStatus(result, 1, valid);
    }

    if (isValid(result, 2)) {
      boolean valid = validateNumericType(expression.getThirdExpression(),  LocateExpression_ThirdExpression_WrongType);
      updateStatus(result, 2, valid);
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateLowerExpression(LowerExpression expression) {

    boolean valid = super.validateLowerExpression(expression);

    if (valid) {
      valid = validateStringType(expression.getExpression(), LowerExpression_WrongType);
    }

    return valid;
  }

  protected void validateMapIdentificationVariable(EncapsulatedIdentificationVariableExpression expression) {

    // The KEY, VALUE, and ENTRY operators may only be applied to
    // identification variables that correspond to map-valued associations
    // or map-valued element collections
    if (expression.hasExpression()) {

      Expression childExpression = expression.getExpression();
      String variableName = literal(childExpression, LiteralType.IDENTIFICATION_VARIABLE);

      // Retrieve the identification variable's type without traversing the type parameters
      if (ExpressionTools.stringIsNotEmpty(variableName)) {
        Object typeDeclaration = helper.getTypeDeclaration(childExpression);
        Object type = helper.getType(typeDeclaration);

        if (!getTypeHelper().isMapType(type)) {
          addProblem(
            childExpression,
            EncapsulatedIdentificationVariableExpression_NotMapValued,
            expression.getIdentifier()
          );
        }
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateModExpression(ModExpression expression) {

    int result = super.validateModExpression(expression);

    // Don't validate the first expression if it's not valid
    if (isValid(result, 0)) {

      boolean valid = validateIntegralType(
        expression.getFirstExpression(),
        expression.parameterExpressionBNF(0),
        ModExpression_FirstExpression_WrongType
      );

      updateStatus(result, 0, valid);
    }

    // Don't validate the second expression if it's not valid
    if (isValid(result, 1)) {

      boolean valid = validateIntegralType(
        expression.getSecondExpression(),
        expression.parameterExpressionBNF(1),
        ModExpression_SecondExpression_WrongType
      );

      updateStatus(result, 1, valid);
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateNotExpression(NotExpression expression) {
    super.validateNotExpression(expression);
    validateBooleanType(expression.getExpression(), NotExpression_WrongType);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateNullComparisonExpression(NullComparisonExpression expression) {

    super.validateNullComparisonExpression(expression);

    // Null comparisons over instances of embeddable class types are not supported
    StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getExpression());

    if (pathExpression != null) {
      Object type = helper.getType(pathExpression);

      if (helper.getEmbeddable(type) != null) {
        addProblem(pathExpression, NullComparisonExpression_InvalidType, pathExpression.toParsedText());
      }
    }
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a numeric value;</li>
   * <li>The {@link Expression}'s type is an numeric type.</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @param messageKey The key used to retrieve the localized message describing the problem
   * @return <code>false</code> if the given expression was validated and is invalid;
   * <code>true</code> otherwise
   */
  protected boolean validateNumericType(Expression expression, String messageKey) {

    if (isValid(expression, SimpleArithmeticExpressionBNF.ID) && !isNumericType(expression)) {
      addProblem(expression, messageKey);
      return false;
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateSqrtExpression(SqrtExpression expression) {

    boolean valid = super.validateSqrtExpression(expression);

    if (valid) {
      // The SQRT function takes a numeric argument
      valid = validateNumericType(expression.getExpression(), SqrtExpression_WrongType);
    }

    return valid;
  }

  /**
   * Determines whether the given {@link Expression} is of the correct type based on these rules:
   * <ul>
   * <li>The {@link Expression} returns a String value;</li>
   * <li>The {@link Expression}'s type is a String type.</li>
   * </ul>
   *
   * @param expression The {@link Expression} to validate
   * @param messageKey The key used to retrieve the localized message describing the problem
   * @return <code>false</code> if the given expression was validated and is invalid;
   * <code>true</code> otherwise
   */
  protected boolean validateStringType(Expression expression, String messageKey) {

    if (isValid(expression, StringPrimaryBNF.ID) && !isStringType(expression)) {
      addProblem(expression, messageKey, expression.toParsedText());
      return false;
    }

    return true;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected int validateSubstringExpression(SubstringExpression expression) {

    int result = super.validateSubstringExpression(expression);

    if (isValid(result, 0)) {
      boolean valid = validateStringType(
        expression.getFirstExpression(),
        SubstringExpression_FirstExpression_WrongType
      );
      updateStatus(result, 0, valid);
    }

    // The second and third arguments of the SUBSTRING function denote the starting position and
    // length of the substring to be returned. These arguments are integers
    if (isValid(result, 1)) {

      boolean valid = validateIntegralType(
        expression.getSecondExpression(),
        expression.getParameterQueryBNFId(1),
        SubstringExpression_SecondExpression_WrongType
      );

      updateStatus(result, 1, valid);
    }

    // The third argument is optional for JPA 2.0
    if (isValid(result, 2) && getJPAVersion().isNewerThanOrEqual(JPAVersion.VERSION_2_0)) {

      boolean valid = validateIntegralType(
        expression.getThirdExpression(),
        expression.getParameterQueryBNFId(2),
        SubstringExpression_ThirdExpression_WrongType
      );

      updateStatus(result, 2, valid);
    }

    return result;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateSumFunction(SumFunction expression) {

    boolean valid = super.validateSumFunction(expression);

    if (valid) {
      // Arguments to the functions SUM must be numeric
      valid = validateNumericType(expression.getExpression(), SumFunction_WrongType);
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  @SuppressWarnings("null")
  protected boolean validateUpdateItem(UpdateItem expression) {

    // First validate the path expression
    boolean valid = super.validateUpdateItem(expression);

    if (valid) {

      // Retrieve the entity to make sure the state field is part of it
      AbstractSchemaName abstractSchemaName = findAbstractSchemaName(expression);
      String entityName = (abstractSchemaName != null) ? abstractSchemaName.getText() : null;

       if (ExpressionTools.stringIsNotEmpty(entityName)) {
         Object entity = helper.getEntityNamed(entityName);

        // Check the existence of the state field on the entity
        if ((entity != null) && expression.hasSpaceAfterStateFieldPathExpression()) {
          StateFieldPathExpression pathExpression = getStateFieldPathExpression(expression.getStateFieldPathExpression());
          String stateFieldValue = (pathExpression != null) ? pathExpression.toParsedText() : null;

          if (ExpressionTools.stringIsNotEmpty(stateFieldValue)) {

            // State field without a dot
            if (stateFieldValue.indexOf(".") == -1) {
              Object mapping = helper.getMappingNamed(entity, stateFieldValue);

               if (mapping == null) {
                 addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue);
               }
               else {
                 validateUpdateItemTypes(expression, helper.getMappingType(mapping));
               }
            }
            else {
              Object type = helper.getType(pathExpression);

              if (!helper.isTypeResolvable(type)) {
                 addProblem(pathExpression, UpdateItem_NotResolvable, stateFieldValue);
              }
              else {
                 validateUpdateItemTypes(expression, type);
              }
            }
          }
         }
       }
    }

    return valid;
  }

  protected void validateUpdateItemTypes(UpdateItem expression, Object type) {

    if (expression.hasNewValue()) {

      Expression newValue = expression.getNewValue();
      ITypeHelper typeHelper = getTypeHelper();
      boolean nullValue = isNullValue(newValue);

      // A NULL value is ignored, except if the type is a primitive, null cannot be
      // assigned to a mapping of primitive type
      if (nullValue) {
        if (typeHelper.isPrimitiveType(type)) {
          addProblem(expression, UpdateItem_NullNotAssignableToPrimitive);
        }
        return;
      }

      Object newValueType = getType(newValue);

      // Do a quick check for known JDK types:
      // 1) Date/Time/Timestamp
      // 2) Any classes related to a number, eg long/Long etc
      if (!helper.isTypeResolvable(newValueType) ||
          typeHelper.isDateType(type) && typeHelper.isDateType(newValueType) ||
          (typeHelper.isNumericType(type)         || typeHelper.isPrimitiveType(type)) &&
          (typeHelper.isNumericType(newValueType) || typeHelper.isPrimitiveType(newValueType))) {

        return;
      }

      // The new value's type can't be assigned to the item's type
      if (!helper.isAssignableTo(newValueType, type)) {
        addProblem(
          expression,
          UpdateItem_NotAssignable,
          helper.getTypeName(newValueType),
          helper.getTypeName(type)
        );
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected boolean validateUpperExpression(UpperExpression expression) {

    boolean valid = super.validateUpperExpression(expression);

    if (valid) {
      // The UPPER function convert a string to upper case,
      // with regard to the locale of the database
      valid = validateStringType(expression.getExpression(), UpperExpression_WrongType);
    }

    return valid;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void validateValueExpression(ValueExpression expression) {
    validateMapIdentificationVariable(expression);
    super.validateValueExpression(expression);
  }

  /**
   * This visitor validates expression that is a boolean literal to make sure the type is a
   * <b>Boolean</b>.
   */
  protected class BooleanTypeValidator extends TypeValidator {

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isRightType(Object type) {
      return getTypeHelper().isBooleanType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AllOrAnyExpression expression) {
      // ALL|ANY|SOME always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AndExpression expression) {
      // AND always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(BetweenExpression expression) {
      // BETWEEN always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ComparisonExpression expression) {
      // A comparison always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(EmptyCollectionComparisonExpression expression) {
      // IS EMPTY always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ExistsExpression expression) {
      // EXISTS always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(KeywordExpression expression) {
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LikeExpression expression) {
      // LIKE always returns a boolean value
      valid = true;
    }

     /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NotExpression expression) {
      // NOT always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullComparisonExpression expression) {
      // IS NULL always returns a boolean value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrExpression expression) {
      // OR always returns a boolean value
      valid = true;
    }
  }

  protected class NullValueVisitor extends AbstractExpressionVisitor {

    /**
     * Determines whether the {@link Expression} visited represents {@link Expression#NULL}.
     */
    protected boolean valid;

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(KeywordExpression expression) {
      valid = expression.getText() == Expression.NULL;
    }
  }

  /**
   * This visitor validates expression that is a numeric literal to make sure the type is an
   * instance of <b>Number</b>.
   */
  protected class NumericTypeValidator extends TypeValidator {

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isRightType(Object type) {
      return getTypeHelper().isNumericType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AbsExpression expression) {
      // ABS always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AdditionExpression expression) {
      // An addition expression always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ArithmeticFactor expression) {
      // +/- is always numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AvgFunction expression) {
      // AVG always returns a double
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CountFunction expression) {
      // COUNT always returns a long
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(DivisionExpression expression) {
      // A division expression always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(IndexExpression expression) {
      // INDEX always returns an integer
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LengthExpression expression) {
      // LENGTH always returns an integer
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LocateExpression expression) {
      // LOCATE always returns an integer
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MaxFunction expression) {
      // SUM always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MinFunction expression) {
      // SUM always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ModExpression expression) {
      // MOD always returns an integer
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(MultiplicationExpression expression) {
      // A multiplication expression always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NumericLiteral expression) {
      // A numeric literal is by definition valid
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SizeExpression expression) {
      // SIZE always returns an integer
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SqrtExpression expression) {
      // SQRT always returns a double
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SubtractionExpression expression) {
      // A subtraction expression always returns a numeric value
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SumFunction expression) {
      // SUM always returns a long
      valid = true;
    }
  }

  protected class ResultVariableInOrderByVisitor extends AbstractExpressionVisitor {

    public boolean result;

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(IdentificationVariable expression) {
      expression.getParent().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrderByClause expression) {
      result = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(OrderByItem expression) {
      expression.getParent().accept(this);
    }
  }

  /**
   * This visitor validates that the {@link Expression} is a string primary and to make sure the
   * type is String.
   */
  protected class StringTypeValidator extends TypeValidator {

    /**
     * {@inheritDoc}
     */
    @Override
    protected boolean isRightType(Object type) {
      return getTypeHelper().isStringType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(ConcatExpression expression) {
      // CONCAT always returns a string
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(LowerExpression expression) {
      // LOWER always returns a string
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(StringLiteral expression) {
      // A string literal is by definition valid
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(SubstringExpression expression) {
      // SUBSTRING always returns a string
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(TrimExpression expression) {
      // TRIM always returns a string
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpperExpression expression) {
      // UPPER always returns a string
      valid = true;
    }
  }

  /**
   * The basic validator for validating the type of an {@link Expression}.
   */
  protected abstract class TypeValidator extends AbstractExpressionVisitor {

    /**
     * Determines whether the expression that was visited returns a number.
     */
    protected boolean valid;

    /**
     * Determines whether the given type is the expected type.
     *
     * @param type The type to validate
     * @return <code>true</code> if the given type is of the expected type; <code>false</code> if
     * it's not the right type
     */
    protected abstract boolean isRightType(Object type);

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(CaseExpression expression) {
      Object type = getType(expression);
      valid = isRightType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(CoalesceExpression expression) {
      Object type = getType(expression);
      valid = isRightType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(InputParameter expression) {
      // An input parameter can't be validated until the query
      // is executed so it is assumed to be valid
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(NullExpression expression) {
      // The missing expression is validated by GrammarValidator
      valid = true;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(NullIfExpression expression) {
      expression.getFirstExpression().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(StateFieldPathExpression expression) {
      Object type = getType(expression);
      valid = isRightType(type);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public final void visit(SubExpression expression) {
      expression.getExpression().accept(this);
    }
  }

  protected class UpdateClauseAbstractSchemaNameFinder extends AbstractExpressionVisitor {

    protected AbstractSchemaName expression;

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(AbstractSchemaName expression) {
      this.expression = expression;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(CollectionExpression expression) {
      expression.getParent().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(RangeVariableDeclaration expression) {
      expression.getRootObject().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpdateClause expression) {
      expression.getRangeVariableDeclaration().accept(this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void visit(UpdateItem expression) {
      expression.getParent().accept(this);
    }
  }
}
TOP

Related Classes of org.eclipse.persistence.jpa.jpql.tools.DefaultSemanticValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.