blob: 573fc6e90ce8e9e742c174627bd77f2e985574bd [file] [log] [blame]
/*
* Copyright (c) 2012, 2021 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
//
package org.eclipse.persistence.jpa.jpql;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.persistence.jpa.jpql.parser.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractDoubleEncapsulatedExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractSchemaName;
import org.eclipse.persistence.jpa.jpql.parser.AbstractTraverseParentVisitor;
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.ArithmeticFactor;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionValuedPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.ComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.CompoundExpression;
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.DateTime;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EmptyCollectionComparisonExpression;
import org.eclipse.persistence.jpa.jpql.parser.EntityTypeLiteral;
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.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.InExpression;
import org.eclipse.persistence.jpa.jpql.parser.IndexExpression;
import org.eclipse.persistence.jpa.jpql.parser.InputParameter;
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.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.NullIfExpression;
import org.eclipse.persistence.jpa.jpql.parser.NumericLiteral;
import org.eclipse.persistence.jpa.jpql.parser.ObjectExpression;
import org.eclipse.persistence.jpa.jpql.parser.OrExpression;
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.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.TypeExpression;
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 org.eclipse.persistence.jpa.jpql.parser.WhenClause;
/**
* This visitor calculates the type of an input parameter.
* <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.5
* @author Pascal Filion
*/
public abstract class ParameterTypeVisitor extends AbstractTraverseParentVisitor {
/**
* The {@link Expression} that will help to determine the type of the input parameter.
*/
protected Expression expression;
/**
* Used to ignore the type when calculating it. If <b>Object.class</b> was used, then it could
* incorrectly calculate the type. Example: ":arg = 'JPQL' AND :arg IS NULL", the second :arg
* should be ignored.
*/
protected boolean ignoreType;
/**
* The {@link InputParameter} for which its type will be searched by visiting the query.
*/
protected InputParameter inputParameter;
/**
* The well defined type, which does not have to be calculated.
*/
protected Class<?> type;
/**
* The fully qualified name of the type.
*/
protected String typeName;
/**
* This is used to prevent an infinite loop.
*/
protected final Set<Expression> visitedExpressions;
/**
* Creates a new <code>ParameterTypeVisitor</code>.
*/
protected ParameterTypeVisitor() {
super();
this.visitedExpressions = new HashSet<Expression>();
}
/**
* Disposes this visitor.
*/
public void dispose() {
type = null;
typeName = null;
expression = null;
ignoreType = false;
inputParameter = null;
visitedExpressions.clear();
}
/**
* Returns the type, if it can be determined, of the input parameter.
*
* @return Either the closed type or {@link Object} if it can't be determined
*/
public abstract Object getType();
@Override
public void visit(AbsExpression expression) {
// The absolute function always have a return type
this.expression = expression;
}
@Override
public void visit(AbstractSchemaName expression) {
// An abstract schema type always have a type
this.expression = expression;
}
@Override
public void visit(AdditionExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(AllOrAnyExpression expression) {
super.visit(expression);
}
@Override
public void visit(AndExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(ArithmeticFactor expression) {
super.visit(expression);
}
@Override
public void visit(AvgFunction expression) {
// The average function always have a return type
this.expression = expression;
}
@Override
public void visit(BetweenExpression expression) {
Expression betweenExpression = expression.getExpression();
Expression lowerBound = expression.getLowerBoundExpression();
Expression upperBound = expression.getUpperBoundExpression();
// The input parameter is the expression to be tested within the range of values
if (betweenExpression.isAncestor(inputParameter)) {
if (visitedExpressions.add(expression)) {
lowerBound.accept(this);
visitedExpressions.remove(expression);
}
else {
type = null;
ignoreType = true;
expression = null;
}
}
// The input parameter is on the lower bound side, traverse the upper bound
else if (lowerBound.isAncestor(inputParameter)) {
if (visitedExpressions.add(expression)) {
upperBound.accept(this);
visitedExpressions.remove(expression);
// The upper bound might also be an input parameter
if ((type == null) && visitedExpressions.add(expression)) {
betweenExpression.accept(this);
visitedExpressions.remove(expression);
}
}
else {
type = null;
ignoreType = true;
expression = null;
}
}
// The input parameter is on the upper bound side, traverse the lower bound
else if (upperBound.isAncestor(inputParameter)) {
if (visitedExpressions.add(expression)) {
lowerBound.accept(this);
visitedExpressions.remove(expression);
// The lower bound might also be an input parameter
if ((type == null) && visitedExpressions.add(expression)) {
betweenExpression.accept(this);
visitedExpressions.remove(expression);
}
}
else {
type = null;
ignoreType = true;
expression = null;
}
}
else {
type = Boolean.class;
}
}
@Override
public void visit(CaseExpression expression) {
type = Object.class;
}
@Override
public void visit(CoalesceExpression expression) {
type = Object.class;
}
@Override
public void visit(CollectionMemberExpression expression) {
Expression pathExpression = expression.getCollectionValuedPathExpression();
if (pathExpression.isAncestor(inputParameter)) {
type = Collection.class;
}
else {
type = Object.class;
}
}
@Override
public void visit(CollectionValuedPathExpression expression) {
// A collection-valued path expression always have a return type
this.expression = expression;
}
@Override
public void visit(ComparisonExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(ConcatExpression expression) {
if (expression.getExpression().isAncestor(inputParameter)) {
this.expression = expression;
}
}
@Override
public void visit(ConstructorExpression expression) {
typeName = expression.getClassName();
}
@Override
public void visit(CountFunction expression) {
// The count function always have a return type
this.expression = expression;
}
@Override
public void visit(DateTime expression) {
// A date/time always have a type
this.expression = expression;
}
@Override
public void visit(DivisionExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(EmptyCollectionComparisonExpression expression) {
// Can't determine the type
if (expression.getExpression().isAncestor(inputParameter)) {
ignoreType = true;
}
else {
super.visit(expression);
}
}
@Override
public void visit(EntityTypeLiteral expression) {
this.expression = expression;
}
@Override
public void visit(EntryExpression expression) {
this.expression = expression;
}
@Override
public void visit(ExistsExpression expression) {
// The exist function always have a return type
this.expression = expression;
}
@Override
public void visit(FunctionExpression expression) {
type = Object.class;
}
@Override
public void visit(IdentificationVariable expression) {
// The identification variable always have a return type
this.expression = expression;
}
@Override
public void visit(IndexExpression expression) {
this.expression = expression;
}
@Override
public void visit(InExpression expression) {
if (expression.getInItems().isAncestor(inputParameter)) {
// BNF: ... IN collection_valued_input_parameter
if (expression.isSingleInputParameter()) {
type = Collection.class;
}
else if (visitedExpressions.add(expression)) {
expression.getExpression().accept(this);
visitedExpressions.remove(expression);
}
}
}
@Override
public void visit(InputParameter expression) {
if (inputParameter == null) {
this.inputParameter = expression;
expression.getParent().accept(this);
}
}
@Override
public void visit(KeyExpression expression) {
// KEY() always have a type
this.expression = expression;
}
@Override
public void visit(KeywordExpression expression) {
this.expression = expression;
}
@Override
public void visit(LengthExpression expression) {
// LENGTH takes a string as argument
if (expression.isAncestor(inputParameter)) {
type = String.class;
}
// LENGTH returns an integer value
else {
type = Integer.class;
}
}
@Override
public void visit(LikeExpression expression) {
Expression stringExpression = expression.getStringExpression();
Expression patternValue = expression.getPatternValue();
Expression escapeCharacter = expression.getEscapeCharacter();
if (stringExpression.isAncestor(inputParameter)) {
this.type = String.class;
} else if (patternValue.isAncestor(inputParameter)) {
this.type = String.class;
} else if (escapeCharacter.isAncestor(inputParameter)) {
this.type = Character.class;
} else {
//Shouldnt go in here?
this.type = boolean.class;
}
}
@Override
public void visit(LocateExpression expression) {
Expression firstExpression = expression.getFirstExpression();
Expression secondExpression = expression.getSecondExpression();
// The first two expressions should be a string
if (firstExpression .isAncestor(inputParameter) ||
secondExpression.isAncestor(inputParameter)) {
this.type = String.class;
}
// It either returns an integer or the third argument is an integer
else {
this.type = Integer.class;
}
}
@Override
public void visit(LowerExpression expression) {
// The lower function always have a return type
this.expression = expression;
}
@Override
public void visit(MaxFunction expression) {
// The maximum function always have a return type
this.expression = expression;
}
@Override
public void visit(MinFunction expression) {
// The minimum function always have a return type
this.expression = expression;
}
@Override
public void visit(ModExpression expression) {
visitDoubleEncapsulatedExpression(expression);
}
@Override
public void visit(MultiplicationExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(NotExpression expression) {
super.visit(expression);
}
@Override
public void visit(NullComparisonExpression expression) {
// Can't determine the type
if (expression.getExpression().isAncestor(inputParameter)) {
ignoreType = true;
}
// A singled valued path expression always have a return type
else {
type = Object.class;
}
}
@Override
public void visit(NullIfExpression expression) {
visitDoubleEncapsulatedExpression(expression);
}
@Override
public void visit(NumericLiteral expression) {
// A numerical expression always have a return type
this.expression = expression;
}
@Override
public void visit(ObjectExpression expression) {
super.visit(expression);
}
@Override
public void visit(OrExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(SizeExpression expression) {
// The modulo function always have a return type
this.expression = expression;
}
@Override
public void visit(SqrtExpression expression) {
if (expression.isAncestor(inputParameter)) {
super.visit(expression);
}
else {
this.expression = expression;
}
}
@Override
public void visit(StateFieldPathExpression expression) {
// A state field path expression always have a return type
this.expression = expression;
}
@Override
public void visit(StringLiteral expression) {
this.expression = expression;
}
@Override
public void visit(SubstringExpression expression) {
// The string primary is always a string
if (expression.getFirstExpression().isAncestor(inputParameter)) {
type = String.class;
}
// The first or second arithmetic expression is always an integer
else if (expression.getSecondExpression().isAncestor(inputParameter) ||
expression.getThirdExpression() .isAncestor(inputParameter)) {
type = Integer.class;
}
}
@Override
public void visit(SubtractionExpression expression) {
visitCompoundExpression(expression);
}
@Override
public void visit(SumFunction expression) {
// The sum function always have a return type
this.expression = expression;
}
@Override
public void visit(TrimExpression expression) {
if (expression.getTrimCharacter().isAncestor(inputParameter)) {
type = Character.class;
}
else if (expression.getExpression().isAncestor(inputParameter)) {
type = String.class;
}
}
@Override
public void visit(TypeExpression expression) {
if (expression.getExpression() != inputParameter) {
type = Class.class;
}
}
@Override
public void visit(UpdateItem expression) {
expression.getStateFieldPathExpression().accept(this);
}
@Override
public void visit(UpperExpression expression) {
this.expression = expression;
}
@Override
public void visit(ValueExpression expression) {
// VALUE() always have a type
this.expression = expression;
}
@Override
public void visit(WhenClause expression) {
super.visit(expression);
}
protected void visitCompoundExpression(CompoundExpression expression) {
visitDoubleExpressions(
expression,
expression.getLeftExpression(),
expression.getRightExpression(),
true
);
}
protected void visitDoubleEncapsulatedExpression(AbstractDoubleEncapsulatedExpression expression) {
visitDoubleExpressions(
expression,
expression.getFirstExpression(),
expression.getSecondExpression(),
false
);
}
protected void visitDoubleExpressions(Expression expression,
Expression firstExpression,
Expression secondExpression,
boolean traverseParent) {
// Now traverse the other side to find its return type
if (firstExpression.isAncestor(inputParameter)) {
if (visitedExpressions.add(expression)) {
secondExpression.accept(this);
visitedExpressions.remove(expression);
}
else {
type = null;
ignoreType = true;
expression = null;
}
}
// Now traverse the other side to find its return type
else if (secondExpression.isAncestor(inputParameter)) {
if (visitedExpressions.add(expression)) {
firstExpression.accept(this);
visitedExpressions.remove(expression);
}
else {
type = null;
ignoreType = true;
expression = null;
}
}
// Continue to traverse the hierarchy
else if (traverseParent) {
super.visit(expression);
}
// Otherwise stop here, the expression has a type
else {
this.expression = expression;
}
}
}