/*******************************************************************************
 * Copyright (c) 2011, 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.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.AbsExpression;
import org.eclipse.persistence.jpa.jpql.parser.AbstractEclipseLinkExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractExpressionVisitor;
import org.eclipse.persistence.jpa.jpql.parser.AbstractPathExpression;
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.AsOfClause;
import org.eclipse.persistence.jpa.jpql.parser.AvgFunction;
import org.eclipse.persistence.jpa.jpql.parser.BadExpression;
import org.eclipse.persistence.jpa.jpql.parser.BetweenExpression;
import org.eclipse.persistence.jpa.jpql.parser.CaseExpression;
import org.eclipse.persistence.jpa.jpql.parser.CastExpression;
import org.eclipse.persistence.jpa.jpql.parser.CoalesceExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionExpression;
import org.eclipse.persistence.jpa.jpql.parser.CollectionMemberDeclaration;
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.ConcatExpression;
import org.eclipse.persistence.jpa.jpql.parser.ConnectByClause;
import org.eclipse.persistence.jpa.jpql.parser.ConstructorExpression;
import org.eclipse.persistence.jpa.jpql.parser.CountFunction;
import org.eclipse.persistence.jpa.jpql.parser.DatabaseType;
import org.eclipse.persistence.jpa.jpql.parser.DateTime;
import org.eclipse.persistence.jpa.jpql.parser.DeleteClause;
import org.eclipse.persistence.jpa.jpql.parser.DeleteStatement;
import org.eclipse.persistence.jpa.jpql.parser.DivisionExpression;
import org.eclipse.persistence.jpa.jpql.parser.EclipseLinkExpressionVisitor;
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.ExtractExpression;
import org.eclipse.persistence.jpa.jpql.parser.FromClause;
import org.eclipse.persistence.jpa.jpql.parser.FunctionExpression;
import org.eclipse.persistence.jpa.jpql.parser.GroupByClause;
import org.eclipse.persistence.jpa.jpql.parser.HavingClause;
import org.eclipse.persistence.jpa.jpql.parser.HierarchicalQueryClause;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariable;
import org.eclipse.persistence.jpa.jpql.parser.IdentificationVariableDeclaration;
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.JPQLExpression;
import org.eclipse.persistence.jpa.jpql.parser.Join;
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.NullExpression;
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.OnClause;
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.OrderSiblingsByClause;
import org.eclipse.persistence.jpa.jpql.parser.RangeVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.RegexpExpression;
import org.eclipse.persistence.jpa.jpql.parser.ResultVariable;
import org.eclipse.persistence.jpa.jpql.parser.SelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SimpleFromClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectClause;
import org.eclipse.persistence.jpa.jpql.parser.SimpleSelectStatement;
import org.eclipse.persistence.jpa.jpql.parser.SizeExpression;
import org.eclipse.persistence.jpa.jpql.parser.SqrtExpression;
import org.eclipse.persistence.jpa.jpql.parser.StartWithClause;
import org.eclipse.persistence.jpa.jpql.parser.StateFieldPathExpression;
import org.eclipse.persistence.jpa.jpql.parser.StringLiteral;
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.TableExpression;
import org.eclipse.persistence.jpa.jpql.parser.TableVariableDeclaration;
import org.eclipse.persistence.jpa.jpql.parser.TreatExpression;
import org.eclipse.persistence.jpa.jpql.parser.TrimExpression;
import org.eclipse.persistence.jpa.jpql.parser.TypeExpression;
import org.eclipse.persistence.jpa.jpql.parser.UnionClause;
import org.eclipse.persistence.jpa.jpql.parser.UnknownExpression;
import org.eclipse.persistence.jpa.jpql.parser.UpdateClause;
import org.eclipse.persistence.jpa.jpql.parser.UpdateItem;
import org.eclipse.persistence.jpa.jpql.parser.UpdateStatement;
import org.eclipse.persistence.jpa.jpql.parser.UpperExpression;
import org.eclipse.persistence.jpa.jpql.parser.ValueExpression;
import org.eclipse.persistence.jpa.jpql.parser.WhenClause;
import org.eclipse.persistence.jpa.jpql.parser.WhereClause;
import org.eclipse.persistence.mappings.AggregateMapping;
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 = ((AggregateMapping) 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;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AbsExpression expression) {
		// Visit the child expression in order to create the resolver
		expression.getExpression().accept(this);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AbstractSchemaName expression) {
		ClassDescriptor descriptor = queryContext.getDescriptor(expression.getText());
		type = descriptor.getJavaClass();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AdditionExpression expression) {
		visitArithmeticExpression(expression);
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AndExpression expression) {
		type = Boolean.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AsOfClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(AvgFunction expression) {
		type = Double.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(BadExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(BetweenExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CaseExpression expression) {
		visitCollectionEquivalentExpression(
			expression.getWhenClauses(),
			expression.getElseExpression()
		);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CastExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CoalesceExpression expression) {
		visitCollectionEquivalentExpression(expression.getExpression(), null);
	}

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CollectionMemberExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CollectionValuedPathExpression expression) {
		type = resolveMappingType(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ComparisonExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ConcatExpression expression) {
		type = String.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ConnectByClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ConstructorExpression expression) {
		type = queryContext.getType(expression.getClassName());
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(CountFunction expression) {
		type = Long.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DatabaseType expression) {
		// Nothing to do
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DeleteClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DeleteStatement expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(DivisionExpression expression) {
		visitArithmeticExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(EmptyCollectionComparisonExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(EntityTypeLiteral expression) {
		String entityTypeName = expression.getEntityTypeName();
		ClassDescriptor descriptor = queryContext.getDescriptor(entityTypeName);
		type = descriptor.getJavaClass();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(EntryExpression expression) {
		type = Map.Entry.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ExistsExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ExtractExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(FromClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(FunctionExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(GroupByClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(HavingClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(HierarchicalQueryClause expression) {
		type = Object.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(IdentificationVariableDeclaration expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(IndexExpression expression) {
		type = Integer.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(InExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(InputParameter expression) {
		type = UNRESOLVABLE_TYPE;
	}

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

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

	/**
	 * {@inheritDoc}
	 */
	@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();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(KeywordExpression expression) {

		String text = expression.getText();

		if (text == KeywordExpression.FALSE ||
		    text == KeywordExpression.TRUE) {

			type = Boolean.class;
		}
		else {
			type = Object.class;
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LengthExpression expression) {
		type = Integer.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LikeExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LocateExpression expression) {
		type = Integer.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(LowerExpression expression) {
		type = String.class;
	}

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(ModExpression expression) {
		type = Integer.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(MultiplicationExpression expression) {
		visitArithmeticExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(NotExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(NullComparisonExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(NullExpression expression) {
		type = UNRESOLVABLE_TYPE;
	}

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

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

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

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(OrderByClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(OrderByItem expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(OrderSiblingsByClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(OrExpression expression) {
		type = Boolean.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(RangeVariableDeclaration expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(RegexpExpression expression) {
		type = Boolean.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@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);
		}
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SimpleFromClause expression) {
		type = Object.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SimpleSelectStatement expression) {
		queryContext.newSubQueryContext(expression, null);
		try {
			expression.getSelectClause().accept(this);
		}
		finally {
			queryContext.disposeSubqueryContext();
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SizeExpression expression) {
		type = Integer.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SqrtExpression expression) {
		type = Double.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StartWithClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StateFieldPathExpression expression) {
		type = resolveMappingType(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(StringLiteral expression) {
		type = String.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SubstringExpression expression) {
		type = String.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(SubtractionExpression expression) {
		visitArithmeticExpression(expression);
	}

	/**
	 * {@inheritDoc}
	 */
	@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);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(TableExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(TableVariableDeclaration expression) {
		type = Object.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(TrimExpression expression) {
		type = String.class;
	}

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

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UnionClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UnknownExpression expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateClause expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateItem expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpdateStatement expression) {
		type = Object.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void visit(UpperExpression expression) {
		type = String.class;
	}

	/**
	 * {@inheritDoc}
	 */
	@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());
		}
	}

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

	/**
	 * {@inheritDoc}
	 */
	@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<Class<?>>(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<Class<?>>();
		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();
		}

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

	private class PathResolver extends AbstractEclipseLinkExpressionVisitor {

		ClassDescriptor descriptor;
		DatabaseMapping mapping;
		QueryKey queryKey;

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void visit(AbstractSchemaName expression) {
			descriptor = queryContext.getDescriptor(expression.getText());
		}

		/**
		 * {@link InputParameter}
		 */
		@Override
		public void visit(CollectionValuedPathExpression expression) {
			visitPathExpression(expression);
		}

		/**
		 * {@inheritDoc}
		 */
		@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();
		}

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