blob: 9c431df9a4d70cd07d46b4cb3ed61ef48cda3ea3 [file] [log] [blame]
/*
* Copyright (c) 2011, 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.internal.jpa.jpql;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Date;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.internal.descriptors.InstanceVariableAttributeAccessor;
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import org.eclipse.persistence.internal.queries.ContainerPolicy;
import org.eclipse.persistence.internal.queries.MappedKeyMapContainerPolicy;
import org.eclipse.persistence.jpa.jpql.ExpressionTools;
import org.eclipse.persistence.jpa.jpql.parser.*;
import org.eclipse.persistence.mappings.AttributeAccessor;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.DirectMapMapping;
import org.eclipse.persistence.mappings.querykeys.DirectQueryKey;
import org.eclipse.persistence.mappings.querykeys.ForeignReferenceQueryKey;
import org.eclipse.persistence.mappings.querykeys.QueryKey;
/**
* This visitor resolves the type of any given {@link Expression}.
*
* @version 2.6
* @since 2.4
* @author Pascal Filion
*/
@SuppressWarnings("nls")
final class TypeResolver implements EclipseLinkExpressionVisitor {
/**
* This visitor is responsible to retrieve the {@link CollectionExpression} if it is visited.
*/
private CollectionExpressionVisitor collectionExpressionVisitor;
/**
* This {@link Comparator} compares numeric types and sorts them based on precedence.
*/
private Comparator<Class<?>> numericTypeComparator;
/**
* This visitor resolves a path expression by retrieving the mapping and descriptor of the last segment.
*/
private PathResolver pathResolver;
/**
* The context used to query information about the application metadata and cached information.
*/
private final JPQLQueryContext queryContext;
/**
* The well defined type, which does not have to be calculated.
*/
private Class<?> type;
/**
* A constant representing an unresolvable type.
*/
private static final Class<?> UNRESOLVABLE_TYPE = TypeResolver.class;
/**
* Creates a new <code>TypeResolver</code>.
*
* @param queryContext The context used to query information about the application metadata and
* cached information
*/
TypeResolver(JPQLQueryContext queryContext) {
super();
this.queryContext = queryContext;
}
/**
* Returns the type of the given {@link DatabaseMapping}, which is the persistent field type.
*
* @param mapping The {@link DatabaseMapping} to retrieve its persistent field type
* @return The persistent field type
*/
@SuppressWarnings("null")
Class<?> calculateMappingType(DatabaseMapping mapping) {
// For aggregate mappings (@Embedded and @EmbeddedId), we need to use the descriptor
// because its mappings have to be retrieve from this one and not from the descriptor
// returned when querying it with a Java type
if (mapping.isAggregateMapping()) {
ClassDescriptor descriptor = mapping.getReferenceDescriptor();
if (descriptor != null) {
return descriptor.getJavaClass();
}
}
// Relationship mapping
if (mapping.isForeignReferenceMapping()) {
ClassDescriptor descriptor = mapping.getReferenceDescriptor();
if (descriptor != null) {
return descriptor.getJavaClass();
}
}
// Collection mapping
else if (mapping.isCollectionMapping()) {
return mapping.getContainerPolicy().getContainerClass();
}
// Property mapping
AttributeAccessor accessor = mapping.getAttributeAccessor();
// Attribute
if (accessor.isInstanceVariableAttributeAccessor()) {
InstanceVariableAttributeAccessor attributeAccessor = (InstanceVariableAttributeAccessor) accessor;
Field field = attributeAccessor.getAttributeField();
if (field == null) {
try {
field = mapping.getDescriptor().getJavaClass().getDeclaredField(attributeAccessor.getAttributeName());
}
catch (Exception e) {}
}
return field.getType();
}
// Property
if (accessor.isMethodAttributeAccessor()) {
MethodAttributeAccessor methodAccessor = (MethodAttributeAccessor) accessor;
Method method = methodAccessor.getGetMethod();
if (method == null) {
try {
method = mapping.getDescriptor().getJavaClass().getDeclaredMethod(methodAccessor.getGetMethodName());
}
catch (Exception e) {}
}
return method.getReturnType();
}
// Anything else
return accessor.getAttributeClass();
}
/**
* Returns the type of the given {@link QueryKey}, which is the persistent field type.
*
* @param queryKey The {@link QueryKey} to retrieve its persistent field type
* @return The persistent field type
*/
Class<?> calculateQueryKeyType(QueryKey queryKey) {
// ForeignReferenceQueryKey
if (queryKey.isForeignReferenceQueryKey()) {
ForeignReferenceQueryKey foreignReferenceQueryKey = (ForeignReferenceQueryKey) queryKey;
return foreignReferenceQueryKey.getReferenceClass();
}
// DirectQueryKey
DirectQueryKey key = (DirectQueryKey) queryKey;
Class<?> type = key.getField().getType();
return (type != null) ? type : Object.class;
}
/**
* Returns the visitor that collects the {@link CollectionExpression} if it's been visited.
*
* @return The {@link CollectionExpressionVisitor}
*/
protected CollectionExpressionVisitor collectionExpressionVisitor() {
if (collectionExpressionVisitor == null) {
collectionExpressionVisitor = new CollectionExpressionVisitor();
}
return collectionExpressionVisitor;
}
Class<?> compareCollectionEquivalentTypes(List<Class<?>> types) {
Class<?> localType = null;
for (Class<?> anotherType : types) {
if (anotherType == UNRESOLVABLE_TYPE) {
continue;
}
if (localType == null) {
localType = anotherType;
}
// Two types are not the same, then the type is Object
else if (localType != anotherType) {
return Object.class;
}
}
if (localType == null) {
localType = UNRESOLVABLE_TYPE;
}
return localType;
}
Class<?> convertSumFunctionType(Class<?> type) {
// Integral types
if ((type == Integer.TYPE) ||
(type == Integer.class) ||
(type == Long.TYPE) ||
(type == Long.class) ||
(type == Byte.TYPE) ||
(type == Byte.class) ||
(type == Short.TYPE) ||
(type == Short.class) ||
(type == Character.TYPE) ||
(type == Character.class)) {
type = Long.class;
}
// Floating types
else if ((type == Float.TYPE) ||
(type == Float.class) ||
(type == Double.TYPE) ||
(type == Double.class)) {
type = Double.class;
}
// Anything else, use Object
else if ((type != BigDecimal.class) &&
(type != BigInteger.class)) {
type = Object.class;
}
return type;
}
/**
* Casts the given {@link Expression} to a {@link CollectionExpression} if it is actually an
* object of that type.
*
* @param expression The {@link Expression} to cast
* @return The given {@link Expression} if it is a {@link CollectionExpression} or <code>null</code>
* if it is any other object
*/
private CollectionExpression getCollectionExpression(Expression expression) {
CollectionExpressionVisitor visitor = collectionExpressionVisitor();
try {
expression.accept(visitor);
return visitor.expression;
}
finally {
visitor.expression = null;
}
}
private boolean isNumericType() {
return type == Integer.TYPE || type == Integer.class ||
type == Long.TYPE || type == Long.class ||
type == Float.TYPE || type == Float.class ||
type == Double.TYPE || type == Double.class ||
type == BigInteger.class || type == BigDecimal.class;
}
private PathResolver pathResolver() {
if (pathResolver == null) {
pathResolver = new PathResolver();
}
return pathResolver;
}
/**
* Returns the type of the given {@link Expression}.
*
* @param expression The {@link Expression} to resolve its type
* @return Either the closest type or {@link Object} if it could not be determined
*/
Class<?> resolve(Expression expression) {
Class<?> oldType = type;
try {
expression.accept(this);
return (type == UNRESOLVABLE_TYPE) ? Object.class : type;
}
finally {
type = oldType;
}
}
ClassDescriptor resolveDescriptor(Expression expression) {
PathResolver resolver = pathResolver();
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
expression.accept(resolver);
return resolver.descriptor;
}
finally {
resolver.mapping = oldMapping;
resolver.descriptor = oldDescriptor;
}
}
DatabaseMapping resolveMapping(Expression expression) {
PathResolver resolver = pathResolver();
QueryKey oldQueryKey = resolver.queryKey;
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
resolver.queryKey = null;
expression.accept(resolver);
return resolver.mapping;
}
finally {
resolver.mapping = oldMapping;
resolver.queryKey = oldQueryKey;
resolver.descriptor = oldDescriptor;
}
}
/**
* Resolves the given {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression} and
* either returns the {@link DatabaseMapping} or the {@link QueryKey} object.
*
* @param expression The {@link org.eclipse.persistence.jpa.jpql.parser.Expression Expression} to
* resolve by traversing its path expression
* @return Either the {@link DatabaseMapping} or the {@link QueryKey} that is representing the
* last path or <code>null</code> if the path expression could not be resolved
*/
Object resolveMappingObject(Expression expression) {
PathResolver resolver = pathResolver();
QueryKey oldQueryKey = resolver.queryKey;
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
resolver.queryKey = null;
expression.accept(resolver);
return (resolver.mapping != null) ? resolver.mapping : resolver.queryKey;
}
finally {
resolver.mapping = oldMapping;
resolver.queryKey = oldQueryKey;
resolver.descriptor = oldDescriptor;
}
}
private Class<?> resolveMappingType(AbstractPathExpression expression) {
PathResolver resolver = pathResolver();
QueryKey oldQueryKey = resolver.queryKey;
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
resolver.queryKey = null;
expression.accept(resolver);
if (resolver.mapping != null) {
return calculateMappingType(resolver.mapping);
}
else if (resolver.queryKey != null) {
return calculateQueryKeyType(resolver.queryKey);
}
else {
return queryContext.getEnumType(expression.toParsedText());
}
}
finally {
resolver.mapping = oldMapping;
resolver.queryKey = oldQueryKey;
resolver.descriptor = oldDescriptor;
}
}
QueryKey resolveQueryKey(org.eclipse.persistence.jpa.jpql.parser.Expression expression) {
PathResolver resolver = pathResolver();
QueryKey oldQueryKey = resolver.queryKey;
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
resolver.queryKey = null;
expression.accept(resolver);
return resolver.queryKey;
}
finally {
resolver.mapping = oldMapping;
resolver.queryKey = oldQueryKey;
resolver.descriptor = oldDescriptor;
}
}
@Override
public void visit(AbsExpression expression) {
// Visit the child expression in order to create the resolver
expression.getExpression().accept(this);
}
@Override
public void visit(AbstractSchemaName expression) {
ClassDescriptor descriptor = queryContext.getDescriptor(expression.getText());
type = descriptor.getJavaClass();
}
@Override
public void visit(AdditionExpression expression) {
visitArithmeticExpression(expression);
}
@Override
public void visit(AllOrAnyExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(AndExpression expression) {
type = Boolean.class;
}
@Override
public void visit(ArithmeticFactor expression) {
// First traverse the expression
expression.getExpression().accept(this);
// Make sure the type is a numeric type
if (!isNumericType()) {
type = Object.class;
}
}
@Override
public void visit(AsOfClause expression) {
type = Object.class;
}
@Override
public void visit(AvgFunction expression) {
type = Double.class;
}
@Override
public void visit(BadExpression expression) {
type = Object.class;
}
@Override
public void visit(BetweenExpression expression) {
type = Boolean.class;
}
@Override
public void visit(CaseExpression expression) {
visitCollectionEquivalentExpression(
expression.getWhenClauses(),
expression.getElseExpression()
);
}
@Override
public void visit(CastExpression expression) {
type = Object.class;
}
@Override
public void visit(CoalesceExpression expression) {
visitCollectionEquivalentExpression(expression.getExpression(), null);
}
@Override
public void visit(CollectionExpression expression) {
expression.acceptChildren(this);
}
@Override
public void visit(CollectionMemberDeclaration expression) {
expression.getCollectionValuedPathExpression().accept(this);
}
@Override
public void visit(CollectionMemberExpression expression) {
type = Boolean.class;
}
@Override
public void visit(CollectionValuedPathExpression expression) {
type = resolveMappingType(expression);
}
@Override
public void visit(ComparisonExpression expression) {
type = Boolean.class;
}
@Override
public void visit(ConcatExpression expression) {
type = String.class;
}
@Override
public void visit(ConnectByClause expression) {
type = Object.class;
}
@Override
public void visit(ConstructorExpression expression) {
type = queryContext.getType(expression.getClassName());
}
@Override
public void visit(CountFunction expression) {
type = Long.class;
}
@Override
public void visit(DatabaseType expression) {
// Nothing to do
}
@Override
public void visit(DateTime expression) {
if (expression.isCurrentDate()) {
type = Date.class;
}
else if (expression.isCurrentTime()) {
type = Time.class;
}
else if (expression.isCurrentTimestamp()) {
type = Timestamp.class;
}
else {
String text = expression.getText();
if (text.startsWith("{d")) {
type = Date.class;
}
else if (text.startsWith("{ts")) {
type = Timestamp.class;
}
else if (text.startsWith("{t")) {
type = Time.class;
}
else {
type = Object.class;
}
}
}
@Override
public void visit(DeleteClause expression) {
type = Object.class;
}
@Override
public void visit(DeleteStatement expression) {
type = Object.class;
}
@Override
public void visit(DivisionExpression expression) {
visitArithmeticExpression(expression);
}
@Override
public void visit(EmptyCollectionComparisonExpression expression) {
type = Boolean.class;
}
@Override
public void visit(EntityTypeLiteral expression) {
String entityTypeName = expression.getEntityTypeName();
ClassDescriptor descriptor = queryContext.getDescriptor(entityTypeName);
type = descriptor.getJavaClass();
}
@Override
public void visit(EntryExpression expression) {
type = Map.Entry.class;
}
@Override
public void visit(ExistsExpression expression) {
type = Boolean.class;
}
@Override
public void visit(ExtractExpression expression) {
type = Object.class;
}
@Override
public void visit(FromClause expression) {
type = Object.class;
}
@Override
public void visit(FunctionExpression expression) {
type = Object.class;
}
@Override
public void visit(GroupByClause expression) {
type = Object.class;
}
@Override
public void visit(HavingClause expression) {
type = Object.class;
}
@Override
public void visit(HierarchicalQueryClause expression) {
type = Object.class;
}
@Override
public void visit(IdentificationVariable expression) {
PathResolver resolver = pathResolver();
DatabaseMapping oldMapping = resolver.mapping;
ClassDescriptor oldDescriptor = resolver.descriptor;
try {
resolver.mapping = null;
resolver.descriptor = null;
expression.accept(resolver);
if (resolver.mapping != null) {
type = calculateMappingType(resolver.mapping);
}
else if (resolver.descriptor != null) {
type = resolver.descriptor.getJavaClass();
}
else {
type = Object.class;
}
}
finally {
resolver.mapping = oldMapping;
resolver.descriptor = oldDescriptor;
}
}
@Override
public void visit(IdentificationVariableDeclaration expression) {
type = Object.class;
}
@Override
public void visit(IndexExpression expression) {
type = Integer.class;
}
@Override
public void visit(InExpression expression) {
type = Boolean.class;
}
@Override
public void visit(InputParameter expression) {
type = UNRESOLVABLE_TYPE;
}
@Override
public void visit(Join expression) {
expression.getJoinAssociationPath().accept(this);
}
@Override
public void visit(JPQLExpression expression) {
expression.getQueryStatement().accept(this);
}
@Override
public void visit(KeyExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
Declaration declaration = queryContext.findDeclaration(identificationVariable.getVariableName());
DatabaseMapping mapping = declaration.getMapping();
MappedKeyMapContainerPolicy mapContainerPolicy = (MappedKeyMapContainerPolicy) mapping.getContainerPolicy();
type = (Class<?>) mapContainerPolicy.getKeyType();
}
@Override
public void visit(KeywordExpression expression) {
String text = expression.getText();
if (text == KeywordExpression.FALSE ||
text == KeywordExpression.TRUE) {
type = Boolean.class;
}
else {
type = Object.class;
}
}
@Override
public void visit(LengthExpression expression) {
type = Integer.class;
}
@Override
public void visit(LikeExpression expression) {
type = Boolean.class;
}
@Override
public void visit(LocateExpression expression) {
type = Integer.class;
}
@Override
public void visit(LowerExpression expression) {
type = String.class;
}
@Override
public void visit(MaxFunction expression) {
// Visit the state field path expression in order to create the resolver
expression.getExpression().accept(this);
// Wrap the Resolver used to determine the type of the state field
// path expression so we can return the actual type
if (!isNumericType()) {
type = Object.class;
}
}
@Override
public void visit(MinFunction expression) {
// Visit the state field path expression in order to create the resolver
expression.getExpression().accept(this);
// Wrap the Resolver used to determine the type of the state field
// path expression so we can return the actual type
if (!isNumericType()) {
type = Object.class;
}
}
@Override
public void visit(ModExpression expression) {
type = Integer.class;
}
@Override
public void visit(MultiplicationExpression expression) {
visitArithmeticExpression(expression);
}
@Override
public void visit(NotExpression expression) {
type = Boolean.class;
}
@Override
public void visit(NullComparisonExpression expression) {
type = Boolean.class;
}
@Override
public void visit(NullExpression expression) {
type = UNRESOLVABLE_TYPE;
}
@Override
public void visit(NullIfExpression expression) {
expression.getFirstExpression().accept(this);
}
@Override
public void visit(NumericLiteral expression) {
try {
String text = expression.getText();
// Long value
// Integer value
if (ExpressionTools.LONG_REGEXP .matcher(text).matches() ||
ExpressionTools.INTEGER_REGEXP.matcher(text).matches()) {
// Special case for a long number, Long.parseLong() does not handle 'l|L'
if (text.endsWith("L") || text.endsWith("l")) {
type = Long.class;
}
else {
Long value = Long.parseLong(text);
if (value <= Integer.MAX_VALUE) {
type = Integer.class;
}
else {
type = Long.class;
}
}
}
// Float
else if (ExpressionTools.FLOAT_REGEXP.matcher(text).matches()) {
type = Float.class;
}
// Decimal
else if (ExpressionTools.DOUBLE_REGEXP.matcher(text).matches()) {
type = Double.class;
}
}
catch (Exception e) {
type = Object.class;
}
}
@Override
public void visit(ObjectExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(OnClause expression) {
expression.getConditionalExpression().accept(this);
}
@Override
public void visit(OrderByClause expression) {
type = Object.class;
}
@Override
public void visit(OrderByItem expression) {
type = Object.class;
}
@Override
public void visit(OrderSiblingsByClause expression) {
type = Object.class;
}
@Override
public void visit(OrExpression expression) {
type = Boolean.class;
}
@Override
public void visit(RangeVariableDeclaration expression) {
type = Object.class;
}
@Override
public void visit(RegexpExpression expression) {
type = Boolean.class;
}
@Override
public void visit(ResultVariable expression) {
expression.getSelectExpression().accept(this);
}
@Override
public void visit(SelectClause expression) {
Expression selectExpression = expression.getSelectExpression();
// visit(CollectionExpression) iterates through the children but for a
// SELECT clause, a CollectionExpression means the result type is Object[]
CollectionExpression collectionExpression = getCollectionExpression(selectExpression);
if (collectionExpression != null) {
type = Object[].class;
}
else {
selectExpression.accept(this);
}
}
@Override
public void visit(SelectStatement expression) {
expression.getSelectClause().accept(this);
}
@Override
public void visit(SimpleFromClause expression) {
type = Object.class;
}
@Override
public void visit(SimpleSelectClause expression) {
expression.getSelectExpression().accept(this);
}
@Override
public void visit(SimpleSelectStatement expression) {
queryContext.newSubQueryContext(expression, null);
try {
expression.getSelectClause().accept(this);
}
finally {
queryContext.disposeSubqueryContext();
}
}
@Override
public void visit(SizeExpression expression) {
type = Integer.class;
}
@Override
public void visit(SqrtExpression expression) {
type = Double.class;
}
@Override
public void visit(StartWithClause expression) {
type = Object.class;
}
@Override
public void visit(StateFieldPathExpression expression) {
type = resolveMappingType(expression);
}
@Override
public void visit(StringLiteral expression) {
type = String.class;
}
@Override
public void visit(SubExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(SubstringExpression expression) {
type = String.class;
}
@Override
public void visit(SubtractionExpression expression) {
visitArithmeticExpression(expression);
}
@Override
public void visit(SumFunction expression) {
// Visit the state field path expression in order to create the resolver
expression.getExpression().accept(this);
// Wrap the Resolver used to determine the type of the state field
// path expression so we can return the actual type
type = convertSumFunctionType(type);
}
@Override
public void visit(TableExpression expression) {
type = Object.class;
}
@Override
public void visit(TableVariableDeclaration expression) {
type = Object.class;
}
@Override
public void visit(TreatExpression expression) {
expression.getEntityType().accept(this);
}
@Override
public void visit(TrimExpression expression) {
type = String.class;
}
@Override
public void visit(TypeExpression expression) {
expression.getExpression().accept(this);
}
@Override
public void visit(UnionClause expression) {
type = Object.class;
}
@Override
public void visit(UnknownExpression expression) {
type = Object.class;
}
@Override
public void visit(UpdateClause expression) {
type = Object.class;
}
@Override
public void visit(UpdateItem expression) {
type = Object.class;
}
@Override
public void visit(UpdateStatement expression) {
type = Object.class;
}
@Override
public void visit(UpperExpression expression) {
type = String.class;
}
@Override
public void visit(ValueExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
Declaration declaration = queryContext.findDeclaration(identificationVariable.getVariableName());
DatabaseMapping mapping = declaration.getMapping();
if (mapping.isDirectMapMapping()) {
DirectMapMapping mapMapping = (DirectMapMapping) mapping;
type = mapMapping.getValueClass();
if (type == null) {
type = mapMapping.getDirectField().getType();
}
}
else {
type = calculateMappingType(declaration.getMapping());
}
}
@Override
public void visit(WhenClause expression) {
expression.getThenExpression().accept(this);
}
@Override
public void visit(WhereClause expression) {
expression.getConditionalExpression().accept(this);
}
/**
* Visits the given {@link ArithmeticExpression} and creates the appropriate {@link org.eclipse.
* persistence.expressions.Expression Expression}.
*
* @param expression The {@link ArithmeticExpression} to visit
*/
private void visitArithmeticExpression(ArithmeticExpression expression) {
List<Class<?>> types = new ArrayList<>(2);
// Visit the first expression
expression.getLeftExpression().accept(this);
if (isNumericType()) {
types.add(type);
}
// Visit the second expression
expression.getRightExpression().accept(this);
if (isNumericType()) {
types.add(type);
}
if (types.size() == 2) {
Collections.sort(types, NumericTypeComparator.instance());
type = types.get(0);
}
else {
type = Object.class;
}
}
/**
* Visits the given {@link Expression} and creates the appropriate {@link org.eclipse.persistence.
* expressions.Expression Expression} that will check the type for each of its children. If the
* type is the same, then it's the {@link Expression}'s type; otherwise the type will be {@link Object}.
*
* @param expression The {@link Expression} to calculate the type of its children
* @param extraExpression This {@link Expression} will be resolved, if it's not <code>null</code>
* and its type will be added to the collection of types
*/
private void visitCollectionEquivalentExpression(Expression expression,
Expression extraExpression) {
List<Class<?>> types = new ArrayList<>();
CollectionExpression collectionExpression = getCollectionExpression(expression);
// Gather the resolver for all children
if (collectionExpression != null) {
for (Expression child : collectionExpression.children()) {
child.accept(this);
types.add(type);
}
}
// Otherwise visit the actual expression
else {
expression.accept(this);
types.add(type);
}
// Add the resolver for the other expression
if (extraExpression != null) {
extraExpression.accept(this);
types.add(type);
}
// Now compare the types
type = compareCollectionEquivalentTypes(types);
}
/**
* This visitor is used to check if the expression visited is a {@link CollectionExpression}.
*/
private static class CollectionExpressionVisitor extends AbstractExpressionVisitor {
/**
* The {@link CollectionExpression} that was visited, otherwise <code>null</code>.
*/
protected CollectionExpression expression;
/**
* Creates a new <code>CollectionExpressionVisitor</code>.
*/
public CollectionExpressionVisitor() {
super();
}
@Override
public void visit(CollectionExpression expression) {
this.expression = expression;
}
}
private class PathResolver extends AbstractEclipseLinkExpressionVisitor {
ClassDescriptor descriptor;
DatabaseMapping mapping;
QueryKey queryKey;
@Override
public void visit(AbstractSchemaName expression) {
descriptor = queryContext.getDescriptor(expression.getText());
}
/**
* {@link InputParameter}
*/
@Override
public void visit(CollectionValuedPathExpression expression) {
visitPathExpression(expression);
}
@Override
public void visit(EntityTypeLiteral expression) {
descriptor = queryContext.getDescriptor(expression.getEntityTypeName());
}
/**
* {@link InputParameter}
*/
@Override
public void visit(IdentificationVariable expression) {
// Check to see if the identification variable is "virtual" and internally
// changed to a state field path expression. If so, it means it's an unqualified
// path found in an UPDATE or DELETE query
StateFieldPathExpression pathExpression = expression.isVirtual() ? expression.getStateFieldPathExpression() : null;
if (pathExpression != null) {
pathExpression.accept(this);
}
else {
Declaration declaration = queryContext.findDeclaration(expression.getVariableName());
// A null declaration Expression would mean it's the first package of an enum type
if (declaration != null) {
descriptor = declaration.getDescriptor();
}
}
}
/**
* {@link InputParameter}
*/
@Override
public void visit(Join expression) {
expression.getJoinAssociationPath().accept(this);
}
/**
* {@link InputParameter}
*/
@Override
public void visit(KeyExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
Declaration declaration = queryContext.getDeclaration(identificationVariable.getVariableName());
DatabaseMapping mapping = declaration.getMapping();
ContainerPolicy containerPolicy = mapping.getContainerPolicy();
MappedKeyMapContainerPolicy mapPolicy = (MappedKeyMapContainerPolicy) containerPolicy;
descriptor = mapPolicy.getKeyMapping().getReferenceDescriptor();
}
@Override
public void visit(RangeVariableDeclaration expression) {
expression.getIdentificationVariable().accept(this);
}
@Override
public void visit(StateFieldPathExpression expression) {
visitPathExpression(expression);
}
/**
* {@link InputParameter}
*/
@Override
public void visit(TreatExpression expression) {
expression.getEntityType().accept(this);
}
/**
* {@link InputParameter}
*/
@Override
public void visit(ValueExpression expression) {
IdentificationVariable identificationVariable = (IdentificationVariable) expression.getExpression();
Declaration declaration = queryContext.getDeclaration(identificationVariable.getVariableName());
descriptor = declaration.getDescriptor();
}
private void visitPathExpression(AbstractPathExpression expression) {
expression.getIdentificationVariable().accept(this);
if (descriptor == null) {
return;
}
// Now traverse the rest of the path
for (int index = expression.hasVirtualIdentificationVariable() ? 0 : 1, count = expression.pathSize(); index < count; index++) {
String path = expression.getPath(index);
mapping = descriptor.getObjectBuilder().getMappingForAttributeName(path);
if (mapping == null) {
queryKey = descriptor.getQueryKeyNamed(path);
if ((queryKey != null) && queryKey.isForeignReferenceQueryKey()) {
ForeignReferenceQueryKey referenceQueryKey = (ForeignReferenceQueryKey) queryKey;
descriptor = queryContext.getDescriptor(referenceQueryKey.getReferenceClass());
}
else {
if (index + 1 < count) {
mapping = null;
}
descriptor = null;
}
}
// A collection mapping cannot be used in a path (if it's not the last path)
else if (mapping.isCollectionMapping() && (index + 1 < count)) {
mapping = null;
descriptor = null;
}
else {
descriptor = mapping.getReferenceDescriptor();
}
if (descriptor == null) {
if (index + 1 < count) {
mapping = null;
}
break;
}
}
}
}
}