| /* |
| * 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 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; |
| } |
| } |
| } |
| } |
| } |