You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@geode.apache.org by ja...@apache.org on 2018/02/07 21:53:38 UTC

[geode] branch develop updated: GEODE-1758: Refactor, test and clean up TypeUtils (#1367)

This is an automated email from the ASF dual-hosted git repository.

jasonhuynh pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/geode.git


The following commit(s) were added to refs/heads/develop by this push:
     new 952db5f  GEODE-1758: Refactor, test and clean up TypeUtils (#1367)
952db5f is described below

commit 952db5f1c46a293fe3d5bf01894a5be50effce1d
Author: Juan José Ramos <ju...@users.noreply.github.com>
AuthorDate: Wed Feb 7 21:53:36 2018 +0000

    GEODE-1758: Refactor, test and clean up TypeUtils (#1367)
    
    * GEODE-4436: JUnit tests for TypeUtils class.
    * GEODE-4437: Improvements for TypeUtils class.
---
 .../cache/query/internal/types/TypeUtils.java      |  662 ++++++-----
 .../query/internal/types/ObjectTypeAssert.java     |   67 ++
 .../cache/query/internal/types/TypeUtilTest.java   |   44 -
 .../query/internal/types/TypeUtilsJUnitTest.java   | 1197 ++++++++++++++++++++
 .../apache/geode/codeAnalysis/excludedClasses.txt  |    6 +
 5 files changed, 1646 insertions(+), 330 deletions(-)

diff --git a/geode-core/src/main/java/org/apache/geode/cache/query/internal/types/TypeUtils.java b/geode-core/src/main/java/org/apache/geode/cache/query/internal/types/TypeUtils.java
index 87beda4..133d4b5 100644
--- a/geode-core/src/main/java/org/apache/geode/cache/query/internal/types/TypeUtils.java
+++ b/geode-core/src/main/java/org/apache/geode/cache/query/internal/types/TypeUtils.java
@@ -14,11 +14,10 @@
  */
 package org.apache.geode.cache.query.internal.types;
 
-
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Comparator;
-import java.util.Iterator;
+import java.util.Date;
 import java.util.List;
 import java.util.Map;
 
@@ -29,7 +28,6 @@ import org.apache.geode.cache.query.TypeMismatchException;
 import org.apache.geode.cache.query.internal.Support;
 import org.apache.geode.cache.query.internal.Undefined;
 import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
-import org.apache.geode.cache.query.types.MapType;
 import org.apache.geode.cache.query.types.ObjectType;
 import org.apache.geode.internal.i18n.LocalizedStrings;
 import org.apache.geode.pdx.internal.EnumInfo.PdxInstanceEnumInfo;
@@ -45,121 +43,96 @@ import org.apache.geode.pdx.internal.PdxString;
  */
 
 public class TypeUtils implements OQLLexerTokenTypes {
-  private static List _numericPrimitiveClasses = Arrays.asList(
+  protected static List<Class> _numericPrimitiveClasses = Arrays.asList(
       new Class[] {Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE});
 
-  private static List _numericWrapperClasses = Arrays.asList(
+  protected static List<Class> _numericWrapperClasses = Arrays.asList(
       new Class[] {Byte.class, Short.class, Integer.class, Long.class, Float.class, Double.class});
 
-  /* Common Types */
-  /** ObjectType for Object.class */
-  public static final ObjectType OBJECT_TYPE = new ObjectTypeImpl(Object.class);
-
-
-  /** prevent instantiation */
-  private TypeUtils() {}
-
-
   /**
-   * Verify that type-cast will work, or else throw an informative exception.
-   *
-   * @return the castTarget
-   * @throws InternalGemFireError if cast will fail
-   */
-  public static Object checkCast(Object castTarget, Class castClass) {
-    if (castTarget == null)
-      return null; // null can be cast to anything
-    if (!castClass.isInstance(castTarget)) {
-      throw new InternalGemFireError(LocalizedStrings.TypeUtils_EXPECTED_INSTANCE_OF_0_BUT_WAS_1
-          .toLocalizedString(new Object[] {castClass.getName(), castTarget.getClass().getName()}));
-    }
-    return castTarget;
-  }
-
-
-  // implicit conversion of numeric types:
-  // floating point type to other floating point type, or
-  // any integral type to any other integral type if possible
-  // if an integral conversion cannot be done then a TypeMismatchException
-  // is thrown.
-  /*
-   * public static Object convert(Object obj, Class toType) throws TypeMismatchException { // if obj
-   * is null, just leave it alone if (obj == null) return null;
-   *
-   *
-   * if (toType.isInstance(obj)) return obj;
-   *
-   * if (Float.class.isAssignableFrom(toType) && obj instanceof Double) return new
-   * Float(((Double)obj).floatValue());
-   *
-   * if (Double.class.isAssignableFrom(toType) && obj instanceof Float) return
-   * Double.valueOf(((Float)obj).doubleValue());
-   *
-   * if (Long.class.isAssignableFrom(toType) && obj instanceof Byte || obj instanceof Short || obj
-   * instanceof Integer) return Long.valueOf(((Number)obj).longValue());
-   *
-   * if (Integer.class.isAssignableFrom(toType) && obj instanceof Byte || obj instanceof Short ||
-   * obj instanceof Long) { int newInt = ((Number)obj).intValue(); if (!(obj instanceof Long) ||
-   * (long)newInt == ((Long)obj).longValue()) return Integer.valueOf(newInt); }
-   *
-   * if (Short.class.isAssignableFrom(toType) && obj instanceof Byte || obj instanceof Integer ||
-   * obj instanceof Long) { short newShort = ((Number)obj).shortValue(); if (obj instanceof Byte ||
-   * (obj instanceof Long && (long)newShort == ((Long)obj).longValue()) || (obj instanceof Integer
-   * && (int)newShort == ((Integer)obj).intValue())) return new Short(newShort); }
-   *
-   * throw new
-   * TypeMismatchException(LocalizedStrings.TypeUtils_UNABLE_TO_CONVERT_0_TO_1.toLocalizedString(new
-   * Object[] {obj, toType})); }
-   */
-  /**
-   * Compares two objects using the operator
-   *
-   * @param obj1
-   * @param obj2
-   * @param compOp
-   * @return boolean;<br>
-   *         {@link Undefined} if either of the operands is {@link Undefined} or if either of the
-   *         operands is Null and operator is other than == or !=
-   * @throws TypeMismatchException
+   * Enum to execute comparisons based on types.
    */
-  public static Object compare(Object obj1, Object obj2, int compOp) throws TypeMismatchException {
-    if (obj1 == null || obj2 == null) {
-      Boolean result = nullCompare(obj1, obj2, compOp);
-      if (result == null)
-        return QueryService.UNDEFINED;
-      return result;
-    }
+  private enum ComparisonStrategy {
+    TEMPORAL {
+      public Boolean execute(Object object1, Object object2, int comparator)
+          throws ClassCastException {
+        return applyComparator(getTemporalComparator().compare(object1, object2), comparator);
+      }
+    },
 
-    // if either object is UNDEFINED, result is UNDEFINED
-    if (obj1 == QueryService.UNDEFINED || obj2 == QueryService.UNDEFINED) {
-      if (compOp == TOK_NE && !(obj1 == QueryService.UNDEFINED && obj2 == QueryService.UNDEFINED)) {
-        return true;
-      } else if (compOp == TOK_EQ && obj1.equals(obj2)) {
-        return true;
-      } else {
-        return QueryService.UNDEFINED;
+    NUMERIC {
+      public Boolean execute(Object object1, Object object2, int comparator)
+          throws ClassCastException {
+        return applyComparator(getNumericComparator().compare(object1, object2), comparator);
       }
-    }
+    },
 
-    if (obj1 instanceof PdxInstanceEnumInfo && obj2 instanceof Enum) {
-      obj2 = new PdxInstanceEnum((Enum<?>) obj2);
-    } else if (obj1 instanceof Enum && obj2 instanceof PdxInstanceEnumInfo) {
-      obj1 = new PdxInstanceEnum((Enum<?>) obj1);
-    }
+    BOOLEAN {
+      public Boolean execute(Object object1, Object object2, int comparator)
+          throws TypeMismatchException {
+        return booleanCompare(object1, object2, comparator);
+      }
+    },
 
-    if (obj1 instanceof PdxString && obj2 instanceof String) {
-      obj2 = new PdxString((String) obj2);
-    } else if (obj1 instanceof String && obj2 instanceof PdxString) {
-      obj1 = new PdxString((String) obj1);
+    COMPARABLE {
+      public Boolean execute(Object object1, Object object2, int comparator)
+          throws ClassCastException {
+        return applyComparator(((Comparable) object1).compareTo(object2), comparator);
+      }
+    },
+
+    ARBITRARY {
+      public Boolean execute(Object object1, Object object2, int comparator) {
+        if (comparator == TOK_EQ) {
+          return object1.equals(object2);
+        } else {
+          return !object1.equals(object2);
+        }
+      }
+    };
+
+    /**
+     *
+     * @param temporalResult
+     * @param comparator
+     * @return
+     * @throws IllegalArgumentException
+     */
+    protected Boolean applyComparator(int temporalResult, int comparator)
+        throws IllegalArgumentException {
+      switch (comparator) {
+        case TOK_EQ:
+          return temporalResult == 0;
+        case TOK_LT:
+          return temporalResult < 0;
+        case TOK_LE:
+          return temporalResult <= 0;
+        case TOK_GT:
+          return temporalResult > 0;
+        case TOK_GE:
+          return temporalResult >= 0;
+        case TOK_NE:
+          return temporalResult != 0;
+        default:
+          throw new IllegalArgumentException(LocalizedStrings.TypeUtils_UNKNOWN_OPERATOR_0
+              .toLocalizedString(Integer.valueOf(comparator)));
+      }
     }
-    try {
-      int r;
-
-      if (obj1 instanceof java.util.Date && obj2 instanceof java.util.Date)
-        r = getTemporalComparator().compare(obj1, obj2);
 
-      else if (obj1.getClass() != obj2.getClass()
-          && (obj1 instanceof Number && obj2 instanceof Number)) {
+    /**
+     *
+     * @param object1Class
+     * @param object2Class
+     * @param comparator
+     * @return
+     */
+    public static ComparisonStrategy get(Class object1Class, Class object2Class, int comparator)
+        throws TypeMismatchException {
+      if (isAssignableFrom(object1Class, Date.class)
+          && isAssignableFrom(object1Class, Date.class)) {
+        return TEMPORAL;
+      } else if (object1Class != object2Class && (isAssignableFrom(object1Class, Number.class)
+          && isAssignableFrom(object2Class, Number.class))) {
         /*
          * @todo check for NaN, in which case we should not call compareTo Must also handle this in
          * the index lookup code to be consistent See bug 37716 NumericComparator cmprtr =
@@ -167,295 +140,412 @@ public class TypeUtils implements OQLLexerTokenTypes {
          * { return new Boolean(cmprtr.compareWithNaN(obj2)); } else if (obj2.equals(Float.NaN) ||
          * obj2.equals(Float.NaN)) { return new Boolean(cmprtr.compareWithNaN(obj1)); }
          */
-        r = getNumericComparator().compare(obj1, obj2);
-      }
-
-      else if (obj1 instanceof Boolean || obj2 instanceof Boolean)
-        return Boolean.valueOf(booleanCompare(obj1, obj2, compOp));
-
-
-      else if (obj1 instanceof Comparable && obj2 instanceof Comparable)
-        r = ((Comparable) obj1).compareTo(obj2);
-      // comparison of two arbitrary objects: use equals()
-      else if (compOp == TOK_EQ)
-        return Boolean.valueOf(obj1.equals(obj2));
-      else if (compOp == TOK_NE)
-        return Boolean.valueOf(!obj1.equals(obj2));
-      else
+        return NUMERIC;
+      } else if (isAssignableFrom(object1Class, Boolean.class)
+          || isAssignableFrom(object2Class, Boolean.class)) {
+        return BOOLEAN;
+      } else if (isAssignableFrom(object1Class, Comparable.class)
+          && isAssignableFrom(object2Class, Comparable.class)) {
+        return COMPARABLE;
+      } else if ((comparator == TOK_EQ) || (comparator == TOK_NE)) {
+        return ARBITRARY;
+      } else {
         throw new TypeMismatchException(
             LocalizedStrings.TypeUtils_UNABLE_TO_USE_A_RELATIONAL_COMPARISON_OPERATOR_TO_COMPARE_AN_INSTANCE_OF_CLASS_0_WITH_AN_INSTANCE_OF_1
-                .toLocalizedString(
-                    new Object[] {obj1.getClass().getName(), obj2.getClass().getName()}));
-
-
-      switch (compOp) {
-        case TOK_EQ:
-          return Boolean.valueOf(r == 0);
-        case TOK_LT:
-          return Boolean.valueOf(r < 0);
-        case TOK_LE:
-          return Boolean.valueOf(r <= 0);
-        case TOK_GT:
-          return Boolean.valueOf(r > 0);
-        case TOK_GE:
-          return Boolean.valueOf(r >= 0);
-        case TOK_NE:
-          return Boolean.valueOf(r != 0);
-        default:
-          throw new IllegalArgumentException(LocalizedStrings.TypeUtils_UNKNOWN_OPERATOR_0
-              .toLocalizedString(Integer.valueOf(compOp)));
+                .toLocalizedString(new Object[] {object1Class.getName(), object2Class.getName()}));
       }
-    } catch (ClassCastException e) {
-      // if a ClassCastException was thrown and the operator is equals or not equals,
-      // then override and return true or false
-      if (compOp == TOK_EQ)
-        return Boolean.FALSE;
-      if (compOp == TOK_NE)
-        return Boolean.TRUE;
-
-      throw new TypeMismatchException(
-          LocalizedStrings.TypeUtils_UNABLE_TO_COMPARE_OBJECT_OF_TYPE_0_WITH_OBJECT_OF_TYPE_1
-              .toLocalizedString(
-                  new Object[] {obj1.getClass().getName(), obj2.getClass().getName()}),
-          e);
-    } catch (TypeMismatchException e) {
-      // same for TypeMismatchException
-      if (compOp == TOK_EQ)
-        return Boolean.FALSE;
-      if (compOp == TOK_NE)
-        return Boolean.TRUE;
-      throw e;
     }
+
+    /**
+     * Executes the comparison strategy.
+     *
+     * @param object1
+     * @param object2
+     * @param comparator
+     * @return
+     * @throws TypeMismatchException
+     * @throws ClassCastException
+     */
+    public abstract Boolean execute(Object object1, Object object2, int comparator)
+        throws TypeMismatchException, ClassCastException;
   }
 
+  /* Common Types */
+  /** ObjectType for Object.class */
+  public static final ObjectType OBJECT_TYPE = new ObjectTypeImpl(Object.class);
+
+  /** prevent instantiation */
+  private TypeUtils() {}
+
+  /**
+   * Creates an instance of {@link org.apache.geode.cache.query.internal.types.TemporalComparator}.
+   *
+   * @return Comparator for mixed comparisons between instances of java.util.Date, java.sql.Date,
+   *         java.sql.Time, and java.sql.Timestamp.
+   * @see org.apache.geode.cache.query.internal.types.TemporalComparator
+   */
   public static Comparator getTemporalComparator() {
     return new TemporalComparator();
   }
 
+  /**
+   * Creates an instance of {@link org.apache.geode.cache.query.internal.types.NumericComparator}.
+   *
+   * @return Comparator for mixed comparisons between numbers.
+   * @see org.apache.geode.cache.query.internal.types.NumericComparator
+   */
   public static Comparator getNumericComparator() {
     return new NumericComparator();
   }
 
+  /**
+   * Creates an instance of
+   * {@link org.apache.geode.cache.query.internal.types.ExtendedNumericComparator}.
+   *
+   * @return A general comparator that compares different numeric types for equality.
+   * @see org.apache.geode.cache.query.internal.types.ExtendedNumericComparator
+   */
   public static Comparator getExtendedNumericComparator() {
     return new ExtendedNumericComparator();
   }
 
+  /**
+   * Verify that type-cast will work, or else throw an informative exception.
+   *
+   * @return the castTarget
+   * @throws InternalGemFireError if cast will fail
+   */
+  public static Object checkCast(Object castTarget, Class castClass) {
+    if (castTarget == null) {
+      return null; // null can be cast to anything
+    }
+
+    if (!castClass.isInstance(castTarget)) {
+      throw new InternalGemFireError(LocalizedStrings.TypeUtils_EXPECTED_INSTANCE_OF_0_BUT_WAS_1
+          .toLocalizedString(new Object[] {castClass.getName(), castTarget.getClass().getName()}));
+    }
 
+    return castTarget;
+  }
+
+  /**
+   * Given an arbitrary object, return the value that should be used as the key for a particular
+   * index.
+   *
+   * @param obj the object to evaluate.
+   * @return the value that should be used as the key for an index.
+   * @throws TypeMismatchException if the object type is not supported to be used as an index.
+   */
   public static Object indexKeyFor(Object obj) throws TypeMismatchException {
-    if (obj == null)
+    if (obj == null) {
       return null;
-    if (obj instanceof Byte)
+    }
+
+    if (obj instanceof Byte) {
       return Integer.valueOf(((Byte) obj).intValue());
-    if (obj instanceof Short)
+    }
+
+    if (obj instanceof Short) {
       return Integer.valueOf(((Short) obj).intValue());
+    }
+
     // Ketan : Added later. Indexes should be created
     // if the IndexedExpr implements Comparable interface.
     if (obj instanceof Comparable) {
       if (obj instanceof Enum) {
         obj = new PdxInstanceEnum((Enum<?>) obj);
       }
+
       return obj;
     }
+
     throw new TypeMismatchException(LocalizedStrings.TypeUtils_INDEXES_ARE_NOT_SUPPORTED_FOR_TYPE_0
         .toLocalizedString(obj.getClass().getName()));
   }
 
-  // returns null if this type doesn't need a Comparator
-  public static Comparator comparatorFor(Class pathType) {
-    Iterator i = _numericWrapperClasses.iterator();
-    while (i.hasNext())
-      if (((Class) i.next()).isAssignableFrom(pathType))
-        return getNumericComparator();
-    i = _numericPrimitiveClasses.iterator();
-    while (i.hasNext())
-      if (((Class) i.next()).isAssignableFrom(pathType))
-        return getNumericComparator();
-    if (java.util.Date.class.isAssignableFrom(pathType))
-      return getTemporalComparator();
-    return null;
-  }
-
-
-  // indexes are allowed on primitive types except char,
-  // plus Strings, and temporals
   /**
-   * Return the type of the keys for a given path type.
+   * Determines if the Class represented by the first parameter is either the same as, or is a
+   * superclass or superinterface of, the class or interface represented by the second parameter.
    *
-   * @param pathType the Class of the last attribute in the path
-   * @return the Class of the index keys
-   * @throws TypeMismatchException if indexes are not allowed on this type
+   * @param srcType the class to be checked.
+   * @param destType the class to be checked against.
+   * @return the {@code boolean} value indicating whether objects of the type {@code srcType} can be
+   *         assigned to objects of type {@code destType} .
    */
-
-  public static Class indexTypeFor(Class pathType) throws TypeMismatchException {
-    if (Character.class.isAssignableFrom(pathType) || Character.TYPE == pathType)
-      return pathType;
-
-    if (Byte.class.isAssignableFrom(pathType) || Short.class.isAssignableFrom(pathType)
-        || Byte.TYPE == pathType || Short.TYPE == pathType)
-      return Integer.class;
-    Iterator i = _numericWrapperClasses.iterator();
-    while (i.hasNext()) {
-      Class cls = (Class) i.next();
-      if (cls.isAssignableFrom(pathType))
-        return pathType;
-    }
-    i = _numericPrimitiveClasses.iterator();
-    while (i.hasNext()) {
-      Class cls = (Class) i.next();
-      if (cls == pathType)
-        return pathType;
-    }
-
-    if (java.util.Date.class.isAssignableFrom(pathType) || pathType == String.class)
-      return pathType;
-    throw new TypeMismatchException(
-        LocalizedStrings.TypeUtils_INDEXES_ARE_NOT_SUPPORTED_ON_PATHS_OF_TYPE_0
-            .toLocalizedString(pathType.getName()));
-  }
-
-
-
-  public static boolean areTypesConvertible(Class[] srcTypes, Class[] destTypes) {
-    Support.assertArg(srcTypes.length == destTypes.length,
-        "Arguments 'srcTypes' and 'destTypes' must be of same length");
-
-    for (int i = 0; i < srcTypes.length; i++)
-      if (!isTypeConvertible(srcTypes[i], destTypes[i]))
-        return false;
-    return true;
+  protected static boolean isAssignableFrom(Class srcType, Class destType) {
+    return destType.isAssignableFrom(srcType);
   }
 
+  /**
+   * Determines if the Class represented by the first parameter can be safely converted to the Class
+   * represented by the second parameter.
+   *
+   * @param srcType the source class to be converted.
+   * @param destType the target class to be converted to.
+   * @return the {@code boolean} value indicating whether objects of the type {@code srcType} can be
+   *         converted to objects of type {@code destType} .
+   */
   public static boolean isTypeConvertible(Class srcType, Class destType) {
     // handle null: if srcType is null, then it represents
     // a null runtime value, and for our purposes it is type
     // convertible to any type that is assignable from Object.class
-    if (srcType == null)
-      return (Object.class.isAssignableFrom(destType));
+    if (srcType == null) {
+      // TODO: This should always be true as Object is assignable from everything, is the root type
+      // of all Java classes. Object.class is always assignable from every other Java class.
+      return isAssignableFrom(destType, Object.class);
+    }
 
     // check to see if the classes are assignable
-    if (destType.isAssignableFrom(srcType))
+    if (isAssignableFrom(srcType, destType)) {
       return true;
+    }
 
     // handle booleans: are we going from a wrapper Boolean
     // to a primitive boolean or vice-versa?
     if ((srcType == Boolean.TYPE || srcType == Boolean.class)
-        && (destType == Boolean.TYPE || destType == Boolean.class))
+        && (destType == Boolean.TYPE || destType == Boolean.class)) {
       return true;
-
+    }
 
     // a numeric primitive or wrapper can be converted to
     // the same wrapper or a same or wider primitive.
     // handle chars specially
     int i = _numericPrimitiveClasses.indexOf(srcType);
-    if (i < 0)
+    if (i < 0) {
       i = _numericWrapperClasses.indexOf(srcType);
+    }
 
     int destP = _numericPrimitiveClasses.indexOf(destType);
     int destW = -1;
-    if (destP < 0)
+    if (destP < 0) {
       destW = _numericWrapperClasses.indexOf(destType);
+    }
 
     // same size wrapper
-    if (i >= 0 && destW == i)
+    if (i >= 0 && destW == i) {
       return true;
+    }
 
     // same or wider primitive
-    if (i >= 0 && destP >= i)
+    if (i >= 0 && destP >= i) {
       return true;
+    }
 
     // chars
     if (srcType == Character.class || srcType == Character.TYPE) {
       // chars: same size wrapper/primitive
-      if (destType == Character.class || destType == Character.TYPE)
+      if (destType == Character.class || destType == Character.TYPE) {
         return true;
+      }
     }
 
     // no other possibilities
     return false;
   }
 
+  /**
+   * Determines if the all classes represented by the first parameter can be safely converted to the
+   * classes represented by the second parameter.
+   *
+   * @param srcTypes the source classes to be converted.
+   * @param destTypes the target classes to be converted to.
+   * @return the {@code boolean} value indicating whether all objects of the first array can be
+   *         converted to objects of the second array.
+   */
+  public static boolean areTypesConvertible(Class[] srcTypes, Class[] destTypes) {
+    Support.assertArg(srcTypes.length == destTypes.length,
+        "Arguments 'srcTypes' and 'destTypes' must be of same length");
 
-  private static boolean booleanCompare(Object obj1, Object obj2, int compOp)
-      throws TypeMismatchException {
-    if (!(obj1 instanceof Boolean) || !(obj2 instanceof Boolean))
-      throw new TypeMismatchException(
-          LocalizedStrings.TypeUtils_BOOLEANS_CAN_ONLY_BE_COMPARED_WITH_BOOLEANS
-              .toLocalizedString());
-
-    if (compOp == TOK_EQ)
-      return obj1.equals(obj2);
-    else if (compOp == TOK_NE)
-      return !obj1.equals(obj2);
-    else
-      throw new TypeMismatchException(
-          LocalizedStrings.TypeUtils_BOOLEAN_VALUES_CAN_ONLY_BE_COMPARED_WITH_OR
-              .toLocalizedString());
-  }
-
-
-  // returns a Boolean or null if UNDEFINED
-  private static Boolean nullCompare(Object obj1, Object obj2, int compOp) {
-    switch (compOp) {
-      case TOK_EQ:
-        if (obj1 == null)
-          return Boolean.valueOf(obj2 == null);
-        else // obj1 is not null obj2 must be
-          return Boolean.FALSE;
-      case TOK_NE:
-        if (obj1 == null)
-          return Boolean.valueOf(obj2 != null);
-        else // obj1 is not null so obj2 must be
-          return Boolean.TRUE;
-      default:
-        return null;
+    for (int i = 0; i < srcTypes.length; i++) {
+      if (!isTypeConvertible(srcTypes[i], destTypes[i])) {
+        return false;
+      }
     }
-  }
 
-  // public static boolean isList(Class clazz) {
-  // if( clazz == java.util.ArrayList.class ||
-  // clazz == java.util.LinkedList.class ||
-  // clazz == java.util.Vector.class)
-  // return true;
-  // return false;
-  // }
-
-  public static boolean isMap(ObjectType objType) {
-    return objType instanceof MapType;
+    return true;
   }
 
-  // public static boolean isSet(Class clazz){
-  // if( clazz == java.util.HashSet.class ||
-  // clazz == java.util.LinkedHashSet.class ||
-  // clazz == java.util.TreeSet.class)
-  // return true;
-  // return false;
-  // }
-
+  /**
+   * Return the {@link org.apache.geode.cache.query.types.ObjectType} corresponding to the specified
+   * Class.
+   *
+   * @param cls
+   * @return the internal ObjectType implementation that represents the specified class.
+   */
   public static ObjectType getObjectType(Class cls) {
     if (cls == Object.class) {
       return OBJECT_TYPE;
     }
+
     if (Collection.class.isAssignableFrom(cls)) {
       // we don't have element type info here
       return new CollectionTypeImpl(cls, OBJECT_TYPE);
     }
+
     if (cls.isArray()) {
       return new CollectionTypeImpl(cls, getObjectType(cls.getComponentType()));
     }
+
     if (Region.class.isAssignableFrom(cls)) {
       // we don't have access to the region itself for element type
       return new CollectionTypeImpl(cls, OBJECT_TYPE);
     }
+
     if (Map.class.isAssignableFrom(cls)) {
       return new MapTypeImpl(cls, OBJECT_TYPE, OBJECT_TYPE);
     }
+
     // if it's a struct we have no field info, so just return an ObjectTypeImpl
     return new ObjectTypeImpl(cls);
   }
 
+  /**
+   * Return a fixed {@link org.apache.geode.cache.query.types.ObjectType} representing a Region
+   * Entry.
+   *
+   * @param rgn
+   * @return the internal ObjectType implementation that represents a the region entry class.
+   */
   public static ObjectType getRegionEntryType(Region rgn) {
     // just use an ObjectType for now
     return new ObjectTypeImpl(Region.Entry.class);
   }
 
+  /**
+   * Compare two booleans.
+   *
+   * @param obj1
+   * @param obj2
+   * @param compOp
+   * @return a boolean indicating the result of applying the comparison operator on the arguments.
+   * @throws TypeMismatchException When either one of the arguments is not a Boolean, or the
+   *         comparison operator is not supported.
+   */
+  protected static boolean booleanCompare(Object obj1, Object obj2, int compOp)
+      throws TypeMismatchException {
+    if (!(obj1 instanceof Boolean) || !(obj2 instanceof Boolean)) {
+      throw new TypeMismatchException(
+          LocalizedStrings.TypeUtils_BOOLEANS_CAN_ONLY_BE_COMPARED_WITH_BOOLEANS
+              .toLocalizedString());
+    }
+
+    if (compOp == TOK_EQ) {
+      return obj1.equals(obj2);
+    } else if (compOp == TOK_NE) {
+      return !obj1.equals(obj2);
+    } else {
+      throw new TypeMismatchException(
+          LocalizedStrings.TypeUtils_BOOLEAN_VALUES_CAN_ONLY_BE_COMPARED_WITH_OR
+              .toLocalizedString());
+    }
+  }
+
+  /**
+   *
+   * @param obj1
+   * @param obj2
+   * @param compOp
+   * @return a Boolean, or null if UNDEFINED.
+   */
+  private static Boolean nullCompare(Object obj1, Object obj2, int compOp) {
+    Boolean result = null;
+
+    switch (compOp) {
+      case TOK_EQ: {
+        if (obj1 == null) {
+          result = Boolean.valueOf(obj2 == null);
+        } else { // obj1 is not null obj2 must be
+          result = Boolean.FALSE;
+        }
+
+        break;
+      }
+
+      case TOK_NE: {
+        if (obj1 == null) {
+          result = Boolean.valueOf(obj2 != null);
+        } else { // obj1 is not null so obj2 must be
+          result = Boolean.TRUE;
+        }
+
+        break;
+      }
+    }
+
+    return result;
+  }
+
+  /**
+   * Compares two objects using the operator
+   *
+   * @param obj1
+   * @param obj2
+   * @param compOp
+   * @return boolean;<br>
+   *         {@link Undefined} if either of the operands is {@link Undefined} or if either of the
+   *         operands is Null and operator is other than == or !=
+   * @throws TypeMismatchException
+   */
+  public static Object compare(Object obj1, Object obj2, int compOp) throws TypeMismatchException {
+    // Check for nulls first.
+    if (obj1 == null || obj2 == null) {
+      Boolean result = nullCompare(obj1, obj2, compOp);
+
+      if (result == null) {
+        return QueryService.UNDEFINED;
+      }
+
+      return result;
+    }
+
+    // if either object is UNDEFINED, result is UNDEFINED
+    if (obj1 == QueryService.UNDEFINED || obj2 == QueryService.UNDEFINED) {
+      if (compOp == TOK_NE && !(obj1 == QueryService.UNDEFINED && obj2 == QueryService.UNDEFINED)) {
+        return true;
+      } else if (compOp == TOK_EQ && obj1.equals(obj2)) {
+        return true;
+      } else {
+        return QueryService.UNDEFINED;
+      }
+    }
+
+    // Prepare pdx instances if needed.
+    if (obj1 instanceof PdxInstanceEnumInfo && obj2 instanceof Enum) {
+      obj2 = new PdxInstanceEnum((Enum<?>) obj2);
+    } else if (obj1 instanceof Enum && obj2 instanceof PdxInstanceEnumInfo) {
+      obj1 = new PdxInstanceEnum((Enum<?>) obj1);
+    }
+
+    if (obj1 instanceof PdxString && obj2 instanceof String) {
+      obj2 = new PdxString((String) obj2);
+    } else if (obj1 instanceof String && obj2 instanceof PdxString) {
+      obj1 = new PdxString((String) obj1);
+    }
+
+    try {
+      ComparisonStrategy strategy =
+          ComparisonStrategy.get(obj1.getClass(), obj2.getClass(), compOp);
+      return strategy.execute(obj1, obj2, compOp);
+    } catch (ClassCastException | TypeMismatchException e) {
+      // if a ClassCastException or TypeMismatchException was thrown and the operator is equals
+      // or not equals, then override and return true or false.
+      if (compOp == TOK_EQ) {
+        return Boolean.FALSE;
+      }
+
+      if (compOp == TOK_NE) {
+        return Boolean.TRUE;
+      }
+
+      if (isAssignableFrom(e.getClass(), ClassCastException.class)) {
+        throw new TypeMismatchException(
+            LocalizedStrings.TypeUtils_UNABLE_TO_COMPARE_OBJECT_OF_TYPE_0_WITH_OBJECT_OF_TYPE_1
+                .toLocalizedString(
+                    new Object[] {obj1.getClass().getName(), obj2.getClass().getName()}),
+            e);
+      } else {
+        throw e;
+      }
+    }
+  }
 }
diff --git a/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/ObjectTypeAssert.java b/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/ObjectTypeAssert.java
new file mode 100644
index 0000000..227bc51
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/ObjectTypeAssert.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.query.internal.types;
+
+import org.assertj.core.api.AbstractAssert;
+
+import org.apache.geode.cache.query.types.ObjectType;
+
+/**
+ * Custom Assertion to validate {@link ObjectType} instances.
+ */
+public class ObjectTypeAssert extends AbstractAssert<ObjectTypeAssert, ObjectType> {
+
+  public ObjectTypeAssert(ObjectType actual) {
+    super(actual, ObjectTypeAssert.class);
+  }
+
+  public static ObjectTypeAssert assertThat(ObjectType actual) {
+    return new ObjectTypeAssert(actual);
+  }
+
+  public ObjectTypeAssert resolves(Class clazz) {
+    isNotNull();
+    org.assertj.core.api.Assertions.assertThat(actual.resolveClass()).isEqualTo(clazz);
+
+    return this;
+  }
+
+  public ObjectTypeAssert isObject() {
+    isNotNull();
+    isExactlyInstanceOf(ObjectTypeImpl.class);
+
+    return this;
+  }
+
+  public ObjectTypeAssert isCollectionOf(Class componentType) {
+    isNotNull();
+    isExactlyInstanceOf(CollectionTypeImpl.class);
+    org.assertj.core.api.Assertions.assertThat(((CollectionTypeImpl) actual).getElementType())
+        .isExactlyInstanceOf(componentType);
+
+    return this;
+  }
+
+  public ObjectTypeAssert isMapOf(Class keyType, Class valueType) {
+    isNotNull();
+    isExactlyInstanceOf(MapTypeImpl.class);
+    org.assertj.core.api.Assertions.assertThat(((MapTypeImpl) actual).getKeyType())
+        .isExactlyInstanceOf(keyType);
+    org.assertj.core.api.Assertions.assertThat(((MapTypeImpl) actual).getElementType())
+        .isExactlyInstanceOf(valueType);
+
+    return this;
+  }
+}
diff --git a/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilTest.java b/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilTest.java
deleted file mode 100644
index 7dcdf74..0000000
--- a/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
- * agreements. See the NOTICE file distributed with this work for additional information regarding
- * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance with the License. You may obtain a
- * copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package org.apache.geode.cache.query.internal.types;
-
-import static org.junit.Assert.*;
-
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-
-import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
-import org.apache.geode.pdx.internal.PdxString;
-import org.apache.geode.test.junit.categories.UnitTest;
-
-@Category(UnitTest.class)
-public class TypeUtilTest {
-
-  @Test
-  public void comparingEquivalentPdxStringToStringShouldMatchCorrectly() throws Exception {
-    String theString = "MyString";
-    PdxString pdxString = new PdxString(theString);
-    assertTrue((Boolean) TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ));
-    assertTrue((Boolean) TypeUtils.compare(theString, pdxString, OQLLexerTokenTypes.TOK_EQ));
-  }
-
-  @Test
-  public void comparingUnequivalentPdxStringToStringShouldNotMatch() throws Exception {
-    String theString = "MyString";
-    PdxString pdxString = new PdxString("AnotherString");
-    assertFalse((Boolean) TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ));
-    assertFalse((Boolean) TypeUtils.compare(theString, pdxString, OQLLexerTokenTypes.TOK_EQ));
-  }
-}
diff --git a/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilsJUnitTest.java b/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilsJUnitTest.java
new file mode 100644
index 0000000..eaa27df
--- /dev/null
+++ b/geode-core/src/test/java/org/apache/geode/cache/query/internal/types/TypeUtilsJUnitTest.java
@@ -0,0 +1,1197 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
+ * agreements. See the NOTICE file distributed with this work for additional information regarding
+ * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License. You may obtain a
+ * copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package org.apache.geode.cache.query.internal.types;
+
+import static org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes.*;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.io.Serializable;
+import java.lang.reflect.Field;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.PriorityQueue;
+import java.util.Random;
+import java.util.Vector;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import org.apache.geode.InternalGemFireError;
+import org.apache.geode.cache.Region;
+import org.apache.geode.cache.query.QueryService;
+import org.apache.geode.cache.query.TypeMismatchException;
+import org.apache.geode.cache.query.internal.CompiledValue;
+import org.apache.geode.cache.query.internal.parse.OQLLexerTokenTypes;
+import org.apache.geode.internal.cache.AbstractRegion;
+import org.apache.geode.internal.cache.BucketRegion;
+import org.apache.geode.internal.cache.LocalRegion;
+import org.apache.geode.internal.cache.PartitionedRegion;
+import org.apache.geode.pdx.internal.PdxInstanceEnum;
+import org.apache.geode.pdx.internal.PdxString;
+import org.apache.geode.test.junit.categories.UnitTest;
+
+@Category(UnitTest.class)
+@PowerMockIgnore("*.UnitTest")
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(TypeUtils.class)
+public class TypeUtilsJUnitTest {
+  private List<Integer> equalityOperators =
+      Arrays.stream(new int[] {TOK_EQ, TOK_NE}).boxed().collect(Collectors.toList());
+  private List<Integer> comparisonOperators =
+      Arrays.stream(new int[] {TOK_EQ, TOK_LT, TOK_LE, TOK_GT, TOK_GE, TOK_NE}).boxed()
+          .collect(Collectors.toList());
+
+  @Test
+  public void getTemporalComparatorShouldAlwaysReturnAnInstanceOfTemporalComparator() {
+    Comparator comparator = TypeUtils.getTemporalComparator();
+    assertThat(comparator).isNotNull();
+    assertThat(comparator).isExactlyInstanceOf(TemporalComparator.class);
+  }
+
+  @Test
+  public void getNumericComparatorShouldAlwaysReturnAnInstanceOfNumericComparator() {
+    Comparator comparator = TypeUtils.getNumericComparator();
+    assertThat(comparator).isNotNull();
+    assertThat(comparator).isExactlyInstanceOf(NumericComparator.class);
+  }
+
+  @Test
+  public void getExtendedNumericComparatorShouldAlwaysReturnAnInstanceOfExtendedNumericComparator() {
+    Comparator comparator = TypeUtils.getExtendedNumericComparator();
+    assertThat(comparator).isNotNull();
+    assertThat(comparator).isExactlyInstanceOf(ExtendedNumericComparator.class);
+  }
+
+  @Test
+  public void checkCastShouldReturnNullWhenTargetObjectIsNull() {
+    Object result = TypeUtils.checkCast(null, CompiledValue.class);
+    assertThat(result).isNull();
+  }
+
+  @Test
+  public void checkCastShouldThrowExceptionWhenTargetObjectCanNotBeTypeCasted() {
+    assertThatThrownBy(() -> TypeUtils.checkCast(new String("SomeCharacters"), Integer.class))
+        .isInstanceOf(InternalGemFireError.class)
+        .hasMessageMatching("^expected instance of (.*) but was (.*)$");
+  }
+
+  @Test
+  /**
+   * Can't test every possible combination, so try a few ones.
+   */
+  public void checkCastShouldReturnCorrectlyWhenTargetObjectIsNotNullAndCanBeTypeCasted() {
+    Object stringCastTarget = new String("SomeCharacters");
+    Object stringCastResult = TypeUtils.checkCast(stringCastTarget, String.class);
+    assertThat(stringCastResult).isNotNull();
+    assertThat(stringCastResult).isInstanceOf(String.class);
+    assertThat(stringCastResult).isSameAs(stringCastTarget);
+
+    Object integerCastTarget = new Integer(20);
+    Object integerCastResult = TypeUtils.checkCast(integerCastTarget, Integer.class);
+    assertThat(integerCastResult).isNotNull();
+    assertThat(integerCastResult).isInstanceOf(Integer.class);
+    assertThat(integerCastResult).isSameAs(integerCastResult);
+
+    Object numberCastResult = TypeUtils.checkCast(integerCastTarget, Number.class);
+    assertThat(numberCastResult).isNotNull();
+    assertThat(numberCastResult).isInstanceOf(Integer.class);
+    assertThat(numberCastResult).isSameAs(numberCastResult);
+  }
+
+  @Test
+  public void indexKeyForShouldReturnNullWhenTheKeyIsNull() throws TypeMismatchException {
+    Object key = TypeUtils.indexKeyFor(null);
+    assertThat(key).isNull();
+  }
+
+  @Test
+  public void indexKeyForShouldThrowExceptionWhenTheKeyTypeCanNotBeUsedAsIndex() {
+    assertThatThrownBy(() -> TypeUtils.indexKeyFor(new AtomicInteger(0)))
+        .isInstanceOf(TypeMismatchException.class)
+        .hasMessageMatching("^Indexes are not supported for type ' (.*) '$");
+  }
+
+  @Test
+  public void indexKeyForShouldReturnIntegerWhenObjectIsInstanceOfByte()
+      throws TypeMismatchException {
+    Object keyByte = new Byte("5");
+    Object keyByteResult = TypeUtils.indexKeyFor(keyByte);
+    assertThat(keyByteResult).isNotNull();
+    assertThat(keyByteResult).isInstanceOf(Integer.class);
+    assertThat(keyByteResult).isEqualTo(new Integer("5"));
+  }
+
+  @Test
+  public void indexKeyForShouldReturnIntegerWhenObjectIsInstanceOfShort()
+      throws TypeMismatchException {
+    Object keyShort = new Short("10");
+    Object keyShortResult = TypeUtils.indexKeyFor(keyShort);
+    assertThat(keyShortResult).isNotNull();
+    assertThat(keyShortResult).isInstanceOf(Integer.class);
+    assertThat(keyShortResult).isEqualTo(new Integer("10"));
+  }
+
+  @Test
+  public void indexKeyForShouldReturnPdxInstanceEnumWhenObjectIsInstanceOfEnum()
+      throws TypeMismatchException {
+    Object keyEnum = TimeUnit.SECONDS;
+    Object keyEnumResult = TypeUtils.indexKeyFor(keyEnum);
+    assertThat(keyEnumResult).isNotNull();
+    assertThat(keyEnumResult).isInstanceOf(PdxInstanceEnum.class);
+    assertThat(((PdxInstanceEnum) keyEnumResult).getName()).isEqualTo(TimeUnit.SECONDS.name());
+    assertThat(((PdxInstanceEnum) keyEnumResult).getOrdinal())
+        .isEqualTo(TimeUnit.SECONDS.ordinal());
+    assertThat(((PdxInstanceEnum) keyEnumResult).getClassName())
+        .isEqualTo(TimeUnit.SECONDS.getDeclaringClass().getName());
+  }
+
+  @Test
+  public void indexKeyForShouldReturnIdentityWhenObjectIsInstanceOfComparable()
+      throws TypeMismatchException {
+    Object keyComparable = new String("myKey");
+    Object keyComparableResult = TypeUtils.indexKeyFor(keyComparable);
+    assertThat(keyComparableResult).isNotNull();
+    assertThat(keyComparableResult).isSameAs(keyComparable);
+
+    Object customComparableKey = new Comparable() {
+      @Override
+      public int compareTo(Object o) {
+        return 0;
+      }
+    };
+
+    Object customComparableKeyResult = TypeUtils.indexKeyFor(customComparableKey);
+    assertThat(customComparableKeyResult).isNotNull();
+    assertThat(customComparableKeyResult).isSameAs(customComparableKey);
+  }
+
+  /**
+   * Can't test every possible combination so try the known, relevant ones.
+   */
+  @Test
+  public void isAssignableFromShouldWorkProperlyForKnownTypes() {
+    // Booleans
+    assertThat(TypeUtils.isAssignableFrom(new Boolean(true).getClass(), Comparable.class)).isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Boolean(false).getClass(), Comparable.class))
+        .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new AtomicBoolean(false).getClass(), Comparable.class))
+        .isFalse();
+
+    // Dates supported by the TemporalComparator
+    long currentTime = Calendar.getInstance().getTimeInMillis();
+    assertThat(TypeUtils.isAssignableFrom(new Date(currentTime).getClass(), Date.class)).isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Long(currentTime).getClass(), Date.class)).isFalse();
+    assertThat(TypeUtils.isAssignableFrom(new java.sql.Date(currentTime).getClass(), Date.class))
+        .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new java.sql.Time(currentTime).getClass(), Date.class))
+        .isTrue();
+    assertThat(
+        TypeUtils.isAssignableFrom(new java.sql.Timestamp(currentTime).getClass(), Date.class))
+            .isTrue();
+
+    // Numbers supported by the NumericComparator
+    Random random = new Random();
+    assertThat(TypeUtils.isAssignableFrom(new Short("0").getClass(), Number.class)).isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Long(random.nextLong()).getClass(), Number.class))
+        .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Float(random.nextLong()).getClass(), Number.class))
+        .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Double(random.nextLong()).getClass(), Number.class))
+        .isTrue();
+    assertThat(
+        TypeUtils.isAssignableFrom(new BigDecimal(random.nextLong()).getClass(), Number.class))
+            .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Integer(random.nextInt()).getClass(), Number.class))
+        .isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new BigInteger("00").getClass(), Number.class)).isTrue();
+    assertThat(
+        TypeUtils.isAssignableFrom(new AtomicInteger(random.nextInt()).getClass(), Number.class))
+            .isTrue();
+
+    // Comparable Interface
+    assertThat(TypeUtils.isAssignableFrom(new PdxString("").getClass(), Comparable.class)).isTrue();
+    assertThat(TypeUtils.isAssignableFrom(new Long(random.nextLong()).getClass(), Comparable.class))
+        .isTrue();
+    assertThat(
+        TypeUtils.isAssignableFrom(new Integer(random.nextInt()).getClass(), Comparable.class))
+            .isTrue();
+  }
+
+  @Test
+  public void isTypeConvertibleShouldDelegateToIsAssignableFromMethodForNonWrappedTypesAndNullSourceType() {
+    PowerMockito.spy(TypeUtils.class);
+
+    // Special classes (Enum, Object, Class, Interface) and srcType as null.
+    assertThat(TypeUtils.isTypeConvertible(null, Enum.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(Enum.class, Object.class);
+
+    assertThat(TypeUtils.isTypeConvertible(null, Object.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(Object.class, Object.class);
+
+    assertThat(TypeUtils.isTypeConvertible(null, Class.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(Class.class, Object.class);
+
+    assertThat(TypeUtils.isTypeConvertible(null, TimeUnit.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(TimeUnit.class, Object.class);
+
+    assertThat(TypeUtils.isTypeConvertible(null, Serializable.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(Serializable.class, Object.class);
+
+    // Regular, non java wrapped classes.
+    assertThat(TypeUtils.isTypeConvertible(AtomicInteger.class, Number.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(AtomicInteger.class, Number.class);
+
+    assertThat(TypeUtils.isTypeConvertible(NumericComparator.class, Comparator.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(NumericComparator.class, Comparator.class);
+
+    assertThat(TypeUtils.isTypeConvertible(PartitionedRegion.class, LocalRegion.class)).isTrue();
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.isAssignableFrom(PartitionedRegion.class, LocalRegion.class);
+  }
+
+  @Test
+  public void isTypeConvertibleShouldReturnTrueForBooleanPrimitivesAndWrappers() {
+    assertThat(TypeUtils.isTypeConvertible(null, Boolean.class)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Boolean.TYPE, Boolean.TYPE)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Boolean.TYPE, Boolean.class)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Boolean.class, Boolean.TYPE)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Boolean.class, Boolean.class)).isTrue();
+  }
+
+  @Test
+  public void isTypeConvertibleShouldReturnTrueForNumericPrimitivesAndWrappers() {
+    for (int i = 0; i < TypeUtils._numericPrimitiveClasses.size(); i++) {
+      Class sourceType = TypeUtils._numericPrimitiveClasses.get(i);
+
+      TypeUtils._numericPrimitiveClasses.stream().skip(i).forEachOrdered(
+          destType -> assertThat(TypeUtils.isTypeConvertible(sourceType, destType)).isTrue());
+      TypeUtils._numericWrapperClasses.stream().skip(i).limit(1).forEachOrdered(
+          destType -> assertThat(TypeUtils.isTypeConvertible(sourceType, destType)).isTrue());
+    }
+
+    for (int i = 0; i < TypeUtils._numericWrapperClasses.size(); i++) {
+      Class sourceType = TypeUtils._numericWrapperClasses.get(i);
+
+      TypeUtils._numericPrimitiveClasses.stream().skip(i).forEachOrdered(
+          destType -> assertThat(TypeUtils.isTypeConvertible(sourceType, destType)).isTrue());
+      TypeUtils._numericWrapperClasses.stream().skip(i).limit(1).forEachOrdered(
+          destType -> assertThat(TypeUtils.isTypeConvertible(sourceType, destType)).isTrue());
+    }
+  }
+
+  @Test
+  public void isTypeConvertibleShouldReturnTrueForCharacterPrimitivesAndWrappers() {
+    assertThat(TypeUtils.isTypeConvertible(null, Character.class)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Character.TYPE, Character.TYPE)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Character.TYPE, Character.class)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Character.class, Character.TYPE)).isTrue();
+    assertThat(TypeUtils.isTypeConvertible(Character.class, Character.class)).isTrue();
+  }
+
+  @Test
+  public void areTypesConvertibleShouldThrowExceptionWhenTheCollectionSizesAreDifferent() {
+    Class[] srcTypes = new Class[] {Byte.class};
+    Class[] destTypes = new Class[] {Integer.class, Long.class};
+    assertThatThrownBy(() -> TypeUtils.areTypesConvertible(srcTypes, destTypes))
+        .isInstanceOf(IllegalArgumentException.class)
+        .hasMessage("Arguments 'srcTypes' and 'destTypes' must be of same length");
+  }
+
+  @Test
+  public void areTypesConvertibleShouldReturnTrueIfAllTypesWithinTheCollectionsAreConvertible() {
+    Class[] srcTypes =
+        new Class[] {null, Byte.TYPE, Character.TYPE, Boolean.TYPE, NumericComparator.class};
+    Class[] destTypes =
+        new Class[] {Object.class, Integer.TYPE, Character.class, Boolean.class, Comparator.class};
+
+    assertThat(TypeUtils.areTypesConvertible(srcTypes, destTypes)).isTrue();
+  }
+
+  @Test
+  public void areTypesConvertibleShouldReturnFalseIfAtLeastOneTypeWithinTheCollectionsIsNotConvertible() {
+    Class[] srcTypes =
+        new Class[] {null, Byte.TYPE, Character.TYPE, Boolean.TYPE, NumericComparator.class};
+    Class[] destTypes =
+        new Class[] {Object.class, Integer.TYPE, Character.class, Object.class, Comparator.class};
+
+    assertThat(TypeUtils.areTypesConvertible(srcTypes, destTypes)).isFalse();
+  }
+
+  @Test
+  public void getObjectTypeShouldReturnTheProperTypeImplementation() {
+    // Object
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Object.class)).resolves(Object.class)
+        .isObject();
+
+    // Collections
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Vector.class)).resolves(Vector.class)
+        .isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(HashSet.class)).resolves(HashSet.class)
+        .isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(ArrayList.class)).resolves(ArrayList.class)
+        .isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(PriorityQueue.class))
+        .resolves(PriorityQueue.class).isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(LinkedBlockingQueue.class))
+        .resolves(LinkedBlockingQueue.class).isCollectionOf(ObjectTypeImpl.class);
+
+    // Typed Collections
+    Integer[] integers = new Integer[] {};
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(integers.getClass()))
+        .resolves(Integer[].class).isCollectionOf(ObjectTypeImpl.class);
+
+    Vector[] vectors = new Vector[] {};
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(vectors.getClass()))
+        .resolves(Vector[].class).isCollectionOf(CollectionTypeImpl.class);
+
+    // Regions
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Region.class)).resolves(Region.class)
+        .isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(BucketRegion.class))
+        .resolves(BucketRegion.class).isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(AbstractRegion.class))
+        .resolves(AbstractRegion.class).isCollectionOf(ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(PartitionedRegion.class))
+        .resolves(PartitionedRegion.class).isCollectionOf(ObjectTypeImpl.class);
+
+    // Regular Maps
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Map.class)).resolves(Map.class)
+        .isMapOf(ObjectTypeImpl.class, ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(HashMap.class)).resolves(HashMap.class)
+        .isMapOf(ObjectTypeImpl.class, ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Hashtable.class)).resolves(Hashtable.class)
+        .isMapOf(ObjectTypeImpl.class, ObjectTypeImpl.class);
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(ConcurrentHashMap.class))
+        .resolves(ConcurrentHashMap.class).isMapOf(ObjectTypeImpl.class, ObjectTypeImpl.class);
+
+    // Other Classes
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(String.class)).resolves(String.class)
+        .isObject();
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(Integer.class)).resolves(Integer.class)
+        .isObject();
+    ObjectTypeAssert.assertThat(TypeUtils.getObjectType(BigDecimal.class))
+        .resolves(BigDecimal.class).isObject();
+  }
+
+  @Test
+  public void getRegionEntryTypeShouldReturnTheProperTypeImplementation() {
+    ObjectTypeAssert.assertThat(TypeUtils.getRegionEntryType(mock(Region.class)))
+        .resolves(Region.Entry.class).isObject();
+    ObjectTypeAssert.assertThat(TypeUtils.getRegionEntryType(mock(BucketRegion.class)))
+        .resolves(Region.Entry.class).isObject();
+    ObjectTypeAssert.assertThat(TypeUtils.getRegionEntryType(mock(AbstractRegion.class)))
+        .resolves(Region.Entry.class).isObject();
+    ObjectTypeAssert.assertThat(TypeUtils.getRegionEntryType(mock(PartitionedRegion.class)))
+        .resolves(Region.Entry.class).isObject();
+  }
+
+  @Test
+  public void booleanCompareShouldThrowExceptionIfValuesAreNotInstancesOfBoolean() {
+    assertThatThrownBy(() -> TypeUtils.booleanCompare(true, new Object(), anyInt()))
+        .isInstanceOf(TypeMismatchException.class)
+        .hasMessageMatching("^Booleans can only be compared with booleans$");
+    assertThatThrownBy(() -> TypeUtils.booleanCompare(new Object(), false, anyInt()))
+        .isInstanceOf(TypeMismatchException.class)
+        .hasMessageMatching("^Booleans can only be compared with booleans$");
+    assertThatThrownBy(() -> TypeUtils.booleanCompare(new Object(), new Object(), anyInt()))
+        .isInstanceOf(TypeMismatchException.class)
+        .hasMessageMatching("^Booleans can only be compared with booleans$");
+  }
+
+  @Test
+  public void booleanCompareShouldThrowExceptionForNonEqualityComparisonOperators() {
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!equalityOperators.contains(token)) {
+          assertThatThrownBy(() -> TypeUtils.booleanCompare(true, false, token))
+              .isInstanceOf(TypeMismatchException.class)
+              .hasMessageMatching("^Boolean values can only be compared with = or <>$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  @Test
+  public void booleanCompareShouldReturnCorrectlyForEqualityComparisonOperators()
+      throws TypeMismatchException {
+    assertThat(TypeUtils.booleanCompare(true, true, TOK_EQ)).isTrue();
+    assertThat(TypeUtils.booleanCompare(false, false, TOK_EQ)).isTrue();
+    assertThat(TypeUtils.booleanCompare(true, false, TOK_EQ)).isFalse();
+    assertThat(TypeUtils.booleanCompare(false, true, TOK_EQ)).isFalse();
+
+    assertThat(TypeUtils.booleanCompare(Boolean.TRUE, Boolean.TRUE, TOK_EQ)).isTrue();
+    assertThat(TypeUtils.booleanCompare(Boolean.FALSE, Boolean.FALSE, TOK_EQ)).isTrue();
+    assertThat(TypeUtils.booleanCompare(Boolean.TRUE, Boolean.FALSE, TOK_EQ)).isFalse();
+    assertThat(TypeUtils.booleanCompare(Boolean.FALSE, Boolean.TRUE, TOK_EQ)).isFalse();
+  }
+
+  @Test
+  public void comparingNullValuesShouldReturnBooleanOrUndefined() throws TypeMismatchException {
+    assertThat(TypeUtils.compare(null, null, OQLLexerTokenTypes.TOK_EQ)).isNotNull()
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(null, new Object(), OQLLexerTokenTypes.TOK_EQ)).isNotNull()
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(new Object(), null, OQLLexerTokenTypes.TOK_EQ)).isNotNull()
+        .isEqualTo(Boolean.FALSE);
+
+    assertThat(TypeUtils.compare(null, null, OQLLexerTokenTypes.TOK_NE)).isNotNull()
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(null, new Object(), OQLLexerTokenTypes.TOK_NE)).isNotNull()
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(new Object(), null, OQLLexerTokenTypes.TOK_NE)).isNotNull()
+        .isEqualTo(Boolean.TRUE);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!equalityOperators.contains(token)) {
+          assertThat(TypeUtils.compare(new Object(), null, token))
+              .isEqualTo(QueryService.UNDEFINED);
+        }
+      } catch (IllegalAccessException | TypeMismatchException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  @Test
+  public void comparingUndefinedValuesShouldReturnBooleanOrUndefined()
+      throws TypeMismatchException {
+    assertThat(TypeUtils.compare(QueryService.UNDEFINED, new Object(), OQLLexerTokenTypes.TOK_NE))
+        .isNotNull().isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(new Object(), QueryService.UNDEFINED, OQLLexerTokenTypes.TOK_NE))
+        .isNotNull().isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(QueryService.UNDEFINED, QueryService.UNDEFINED,
+        OQLLexerTokenTypes.TOK_EQ)).isNotNull().isEqualTo(Boolean.TRUE);
+
+    assertThat(TypeUtils.compare(QueryService.UNDEFINED, new Object(), OQLLexerTokenTypes.TOK_EQ))
+        .isNotNull().isEqualTo(QueryService.UNDEFINED);
+    assertThat(TypeUtils.compare(new Object(), QueryService.UNDEFINED, OQLLexerTokenTypes.TOK_EQ))
+        .isNotNull().isEqualTo(QueryService.UNDEFINED);
+    assertThat(TypeUtils.compare(QueryService.UNDEFINED, QueryService.UNDEFINED,
+        OQLLexerTokenTypes.TOK_NE)).isNotNull().isEqualTo(QueryService.UNDEFINED);
+  }
+
+  @Test
+  public void comparingEquivalentPdxStringToStringShouldMatchCorrectly() throws Exception {
+    String theString = "MyString";
+    PdxString pdxString = new PdxString(theString);
+
+    assertThat(TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isInstanceOf(Boolean.class);
+    assertThat((Boolean) TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isTrue();
+
+    assertThat(TypeUtils.compare(theString, pdxString, OQLLexerTokenTypes.TOK_EQ))
+        .isInstanceOf(Boolean.class);
+    assertThat((Boolean) TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isTrue();
+  }
+
+  @Test
+  public void comparingUnequivalentPdxStringToStringShouldNotMatch() throws Exception {
+    String theString = "MyString";
+    PdxString pdxString = new PdxString("AnotherString");
+
+    assertThat(TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isInstanceOf(Boolean.class);
+    assertThat((Boolean) TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isFalse();
+
+    assertThat(TypeUtils.compare(pdxString, theString, OQLLexerTokenTypes.TOK_EQ))
+        .isInstanceOf(Boolean.class);
+    assertThat((Boolean) TypeUtils.compare(theString, pdxString, OQLLexerTokenTypes.TOK_EQ))
+        .isFalse();
+  }
+
+  @Test
+  public void comparingTemporalValuesShouldDelegateToTemporalComparator() {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    // Spies to make sure that other comparison methods are not executed.
+    Date beginningOfTimeAsDate = spy(new Date(0L));
+    Date currentCalendarTimeAsDate = spy(Calendar.getInstance().getTime());
+    java.sql.Date beginningOfTimeAsSqlDate =
+        spy(new java.sql.Date(beginningOfTimeAsDate.getTime()));
+    java.sql.Date currentCalendarTimeAsSqlDate =
+        spy(new java.sql.Date(currentCalendarTimeAsDate.getTime()));
+    java.sql.Time beginningOfTimeAsSqlTime =
+        spy(new java.sql.Time(beginningOfTimeAsDate.getTime()));
+    java.sql.Time currentCalendarTimeAsSqlTime =
+        spy(new java.sql.Time(currentCalendarTimeAsDate.getTime()));
+    java.sql.Timestamp beginningOfTimeAsSqlTimestamp =
+        spy(new java.sql.Timestamp(beginningOfTimeAsDate.getTime()));
+    java.sql.Timestamp currentCalendarTimeAsSqlTimestamp =
+        spy(new java.sql.Timestamp(currentCalendarTimeAsDate.getTime()));
+
+    List<Object> originDates = Arrays.asList(new Object[] {beginningOfTimeAsDate,
+        beginningOfTimeAsSqlDate, beginningOfTimeAsSqlTime, beginningOfTimeAsSqlTimestamp});
+    List<Object> currentDates =
+        Arrays.asList(new Object[] {currentCalendarTimeAsDate, currentCalendarTimeAsSqlDate,
+            currentCalendarTimeAsSqlTime, currentCalendarTimeAsSqlTimestamp});
+
+    originDates.forEach(originDate -> {
+      originDates.forEach(originDate2 -> {
+        try {
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_EQ))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_LT))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_LE))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_GT))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_GE))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(originDate, originDate2, OQLLexerTokenTypes.TOK_NE))
+              .isEqualTo(Boolean.FALSE);
+          verify((Comparable) originDate, times(0)).compareTo(any());
+          verify((Comparable) originDate2, times(0)).compareTo(any());
+          verify(temporalComparator, times(6)).compare(any(), any());
+          reset(temporalComparator);
+        } catch (TypeMismatchException typeMismatchException) {
+          throw new RuntimeException(typeMismatchException);
+        }
+      });
+    });
+
+    originDates.forEach(originDate -> {
+      currentDates.forEach(currentTime -> {
+        try {
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_EQ))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_LT))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_LE))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_GT))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_GE))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(originDate, currentTime, OQLLexerTokenTypes.TOK_NE))
+              .isEqualTo(Boolean.TRUE);
+          verify((Comparable) originDate, times(0)).compareTo(any());
+          verify((Comparable) currentTime, times(0)).compareTo(any());
+          verify(temporalComparator, times(6)).compare(any(), any());
+          reset(temporalComparator);
+        } catch (TypeMismatchException typeMismatchException) {
+          throw new RuntimeException(typeMismatchException);
+        }
+      });
+    });
+
+    currentDates.forEach(currentTime -> {
+      originDates.forEach(originDate -> {
+        try {
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_EQ))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_LT))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_LE))
+              .isEqualTo(Boolean.FALSE);
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_GT))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_GE))
+              .isEqualTo(Boolean.TRUE);
+          assertThat(TypeUtils.compare(currentTime, originDate, OQLLexerTokenTypes.TOK_NE))
+              .isEqualTo(Boolean.TRUE);
+          verify((Comparable) originDate, times(0)).compareTo(any());
+          verify((Comparable) currentTime, times(0)).compareTo(any());
+          verify(temporalComparator, times(6)).compare(any(), any());
+          reset(temporalComparator);
+        } catch (TypeMismatchException typeMismatchException) {
+          throw new RuntimeException(typeMismatchException);
+        }
+      });
+    });
+
+    // Extra check to verify that no other comparison methods were called.
+    verify(numericComparator, times(0)).compare(any(), any());
+  }
+
+  @Test
+  public void comparingTemporalValuesShouldThrowExceptionWhenTheComparisonOperatorIsNotSupported()
+      throws TypeMismatchException {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!comparisonOperators.contains(token)) {
+          assertThatThrownBy(() -> TypeUtils.compare(new Date(), new Date(), token))
+              .isInstanceOf(IllegalArgumentException.class)
+              .hasMessageMatching("^Unknown operator: (.*)$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+
+    verify(temporalComparator, times(fields.length - comparisonOperators.size())).compare(any(),
+        any());
+  }
+
+  @Test
+  public void comparingTemporalValuesForWhichTheComparatorThrowsClassCastExceptionShouldReturnBooleanWhenTheComparisonOperatorIsSupported()
+      throws TypeMismatchException {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    TemporalComparator temporalComparator = mock(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+    when(temporalComparator.compare(any(), any())).thenThrow(new ClassCastException(""));
+
+    assertThat(TypeUtils.compare(new Date(), new Date(), OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(new Date(), new Date(), OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!equalityOperators.contains(token)) {
+          assertThatThrownBy(() -> TypeUtils.compare(new Date(), new Date(), token))
+              .isInstanceOf(TypeMismatchException.class).hasMessageMatching(
+                  "^Unable to compare object of type ' (.*) ' with object of type ' (.*) '$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  @Test
+  public void comparingNumericValuesShouldDelegateToNumericComparator() {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    // Can't spy final classes, nor primitives.
+    long lLong = Short.MIN_VALUE;
+    long hLong = Short.MAX_VALUE;
+    int lInteger = Short.MIN_VALUE;
+    int hInteger = Short.MAX_VALUE;
+    float lFloat = Short.MIN_VALUE;
+    float hFloat = Short.MAX_VALUE;
+    short lShort = Short.MIN_VALUE;
+    short hShort = Short.MAX_VALUE;
+    double lDouble = Short.MIN_VALUE;
+    double hDouble = Short.MAX_VALUE;
+    Long lowestLong = new Long(Short.MIN_VALUE);
+    Long highestLong = new Long(Short.MAX_VALUE);
+    Float lowestFloat = new Float(Short.MIN_VALUE);
+    Float highestFloat = new Float(Short.MAX_VALUE);
+    Short lowestShort = new Short(Short.MIN_VALUE);
+    Short highestShort = new Short(Short.MAX_VALUE);
+    Double lowestDouble = new Double(Short.MIN_VALUE);
+    Double highestDouble = new Double(Short.MAX_VALUE);
+    Integer lowestInteger = new Integer(Short.MIN_VALUE);
+    Integer highestInteger = new Integer(Short.MAX_VALUE);
+    AtomicLong lowestAtomicLong = new AtomicLong(Short.MIN_VALUE);
+    AtomicLong highestAtomicLong = new AtomicLong(Short.MAX_VALUE);
+    BigDecimal lowestBigDecimal = BigDecimal.valueOf(Short.MIN_VALUE);
+    BigDecimal highestBigDecimal = BigDecimal.valueOf(Short.MAX_VALUE);
+    BigInteger lowestBigInteger = BigInteger.valueOf(Short.MIN_VALUE);
+    BigInteger highestBigInteger = BigInteger.valueOf(Short.MAX_VALUE);
+    AtomicInteger lowestAtomicInteger = new AtomicInteger(Short.MIN_VALUE);
+    AtomicInteger highestAtomicInteger = new AtomicInteger(Short.MAX_VALUE);
+
+    List<Number> lowestNumbers = Arrays.asList(new Number[] {lInteger, lLong, lFloat, lShort,
+        lDouble, lowestLong, lowestFloat, lowestShort, lowestDouble, lowestInteger,
+        lowestAtomicLong, lowestBigDecimal, lowestBigInteger, lowestAtomicInteger});
+    List<Number> highestNumbers = Arrays.asList(new Number[] {hInteger, hLong, hFloat, hShort,
+        hDouble, highestLong, highestFloat, highestShort, highestDouble, highestInteger,
+        highestAtomicLong, highestBigDecimal, highestBigInteger, highestAtomicInteger});
+
+    lowestNumbers.forEach(lowest -> {
+      lowestNumbers.stream().filter(number -> number.getClass() != lowest.getClass())
+          .forEach((lowest2) -> {
+            try {
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_EQ))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_LT))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_NE))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_LE))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_GT))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowest, lowest2, OQLLexerTokenTypes.TOK_GE))
+                  .isEqualTo(Boolean.TRUE);
+              verify(numericComparator, times(6)).compare(any(), any());
+              reset(numericComparator);
+            } catch (TypeMismatchException typeMismatchException) {
+              throw new RuntimeException(typeMismatchException);
+            }
+          });
+    });
+
+    lowestNumbers.forEach(lowestNumber -> {
+      highestNumbers.stream().filter(number -> number.getClass() != lowestNumber.getClass())
+          .forEach((highestNumber) -> {
+            try {
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_EQ))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_LT))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_GT))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_LE))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_GE))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(lowestNumber, highestNumber, OQLLexerTokenTypes.TOK_NE))
+                  .isEqualTo(Boolean.TRUE);
+              verify(numericComparator, times(6)).compare(any(), any());
+              reset(numericComparator);
+            } catch (TypeMismatchException typeMismatchException) {
+              throw new RuntimeException(typeMismatchException);
+            }
+          });
+    });
+
+    highestNumbers.forEach(highestNumer -> {
+      lowestNumbers.stream().filter(number -> number.getClass() != highestNumer.getClass())
+          .forEach((lowestNumber) -> {
+            try {
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_EQ))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_LT))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_LE))
+                  .isEqualTo(Boolean.FALSE);
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_NE))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_GT))
+                  .isEqualTo(Boolean.TRUE);
+              assertThat(TypeUtils.compare(highestNumer, lowestNumber, OQLLexerTokenTypes.TOK_GE))
+                  .isEqualTo(Boolean.TRUE);
+              verify(numericComparator, times(6)).compare(any(), any());
+              reset(numericComparator);
+            } catch (TypeMismatchException typeMismatchException) {
+              throw new RuntimeException(typeMismatchException);
+            }
+          });
+    });
+
+    // Extra check to verify that no other comparison methods were called.
+    verify(temporalComparator, times(0)).compare(any(), any());
+  }
+
+  @Test
+  public void comparingNumericValuesShouldThrowExceptionWhenTheComparisonOperatorIsNotSupported()
+      throws TypeMismatchException {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!comparisonOperators.contains(token)) {
+          assertThatThrownBy(() -> TypeUtils.compare(new Integer("20"), new Double("20.12"), token))
+              .isInstanceOf(IllegalArgumentException.class)
+              .hasMessageMatching("^Unknown operator: (.*)$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+
+    verify(numericComparator, times(fields.length - comparisonOperators.size())).compare(any(),
+        any());
+  }
+
+  @Test
+  public void comparingNumericValuesForWhichTheComparatorThrowsClassCastExceptionShouldReturnBooleanWhenTheComparisonOperatorIsSupported()
+      throws TypeMismatchException {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    NumericComparator numericComparator = mock(NumericComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    when(numericComparator.compare(any(), any())).thenThrow(new ClassCastException(""));
+
+    assertThat(
+        TypeUtils.compare(new Integer("20"), new BigDecimal("100"), OQLLexerTokenTypes.TOK_NE))
+            .isEqualTo(Boolean.TRUE);
+    assertThat(
+        TypeUtils.compare(new Integer("20"), new BigDecimal("100"), OQLLexerTokenTypes.TOK_EQ))
+            .isEqualTo(Boolean.FALSE);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!equalityOperators.contains(token)) {
+          assertThatThrownBy(
+              () -> TypeUtils.compare(new Integer("20"), new BigDecimal("100"), token))
+                  .isInstanceOf(TypeMismatchException.class).hasMessageMatching(
+                      "^Unable to compare object of type ' (.*) ' with object of type ' (.*) '$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  @Test
+  public void comparingBooleanValuesShouldDelegateToBooleanCompareImplementation()
+      throws TypeMismatchException {
+    // We use spies to track the execution of wanted and unwanted methods.
+    PowerMockito.spy(TypeUtils.class);
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    assertThat(TypeUtils.compare(true, Boolean.TRUE, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.TRUE);
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.booleanCompare(true, Boolean.TRUE, OQLLexerTokenTypes.TOK_EQ);
+
+    assertThat(TypeUtils.compare(Boolean.TRUE, true, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.FALSE);
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.booleanCompare(true, Boolean.TRUE, OQLLexerTokenTypes.TOK_NE);
+
+    assertThat(TypeUtils.compare(true, Boolean.FALSE, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.booleanCompare(true, Boolean.FALSE, OQLLexerTokenTypes.TOK_EQ);
+
+    assertThat(TypeUtils.compare(Boolean.FALSE, true, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    PowerMockito.verifyStatic(TypeUtils.class, times(1));
+    TypeUtils.booleanCompare(Boolean.FALSE, true, OQLLexerTokenTypes.TOK_NE);
+
+    // Extra check to verify that no other comparison methods were called.
+    verify(numericComparator, times(0)).compare(any(), any());
+    verify(temporalComparator, times(0)).compare(any(), any());
+  }
+
+  /**
+   * Test class that implements the Comparable interface.
+   */
+  class ComparableObject implements Comparable {
+    Integer id;
+
+    public ComparableObject(Integer id) {
+      this.id = id;
+    }
+
+    @Override
+    public int compareTo(Object o) {
+      return this.id.compareTo(((ComparableObject) o).id);
+    }
+  }
+
+  @Test
+  public void comparingComparableInstancesShouldDelegateToDefaultCompareToMethod()
+      throws TypeMismatchException {
+    PowerMockito.spy(TypeUtils.class);
+    Comparable startValue = spy(new ComparableObject(0));
+    Comparable finishValue = spy(new ComparableObject(10));
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_LT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_LE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_GT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_GE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(startValue, startValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.FALSE);
+    verify(startValue, times(6)).compareTo(startValue);
+    reset(startValue);
+
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_LT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_LE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_GT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_GE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(finishValue, finishValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.FALSE);
+    verify(finishValue, times(6)).compareTo(finishValue);
+    reset(finishValue);
+
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_LT))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_LE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_GT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_GE))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(startValue, finishValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    verify(startValue, times(6)).compareTo(finishValue);
+    reset(startValue);
+
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_LT))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_LE))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_GT))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_GE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(finishValue, startValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    verify(finishValue, times(6)).compareTo(startValue);
+    reset(finishValue);
+
+    // Extra check to verify that no other comparison methods were called.
+    verify(numericComparator, times(0)).compare(any(), any());
+    verify(temporalComparator, times(0)).compare(any(), any());
+  }
+
+  @Test
+  public void comparingComparableValuesShouldThrowExceptionWhenTheComparisonOperatorIsNotSupported()
+      throws TypeMismatchException {
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!comparisonOperators.contains(token)) {
+          assertThatThrownBy(
+              () -> TypeUtils.compare(mock(Comparable.class), mock(Comparable.class), token))
+                  .isInstanceOf(IllegalArgumentException.class)
+                  .hasMessageMatching("^Unknown operator: (.*)$");
+        }
+      } catch (Exception exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  @Test
+  public void comparingComparableValuesForWhichTheCompareMethodThrowsClassCastExceptionShouldReturnBooleanWhenTheComparisonOperatorIsSupported()
+      throws TypeMismatchException {
+    ComparableObject comparableValue = mock(ComparableObject.class);
+    when(comparableValue.compareTo(any())).thenThrow(new ClassCastException(""));
+
+    assertThat(
+        TypeUtils.compare(comparableValue, mock(Comparable.class), OQLLexerTokenTypes.TOK_NE))
+            .isEqualTo(Boolean.TRUE);
+    assertThat(
+        TypeUtils.compare(comparableValue, mock(Comparable.class), OQLLexerTokenTypes.TOK_EQ))
+            .isEqualTo(Boolean.FALSE);
+
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!equalityOperators.contains(token)) {
+          assertThatThrownBy(
+              () -> TypeUtils.compare(comparableValue, mock(Comparable.class), token))
+                  .isInstanceOf(TypeMismatchException.class).hasMessageMatching(
+                      "^Unable to compare object of type ' (.*) ' with object of type ' (.*) '$");
+        }
+      } catch (Exception exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+
+  /**
+   * Arbitrary Test class (equals method can not be mocked).
+   */
+  public class ArbitraryObject {
+    String id;
+    AtomicInteger equalsInvocations;
+
+    public ArbitraryObject(String id) {
+      this.id = id;
+      this.equalsInvocations = new AtomicInteger(0);
+    }
+
+    public Integer getInvocationsAmount() {
+      return this.equalsInvocations.get();
+    }
+
+    public void resetInvocationsAmount() {
+      this.equalsInvocations.set(0);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      this.equalsInvocations.incrementAndGet();
+
+      if (this == o) {
+        return true;
+      }
+
+      if (!(o instanceof ArbitraryObject)) {
+        return false;
+      }
+
+      ArbitraryObject that = (ArbitraryObject) o;
+
+      return Objects.equals(id, that.id);
+    }
+
+    @Override
+    public int hashCode() {
+      return Objects.hash(id);
+    }
+  }
+
+  @Test
+  public void comparingArbitraryObjectsShouldDelegateToDefaultEqualsMethod()
+      throws TypeMismatchException {
+    PowerMockito.spy(TypeUtils.class);
+    ArbitraryObject aValue = new ArbitraryObject("0");
+    ArbitraryObject anotherValue = new ArbitraryObject("1");
+    NumericComparator numericComparator = spy(NumericComparator.class);
+    TemporalComparator temporalComparator = spy(TemporalComparator.class);
+    PowerMockito.when(TypeUtils.getNumericComparator()).thenReturn(numericComparator);
+    PowerMockito.when(TypeUtils.getTemporalComparator()).thenReturn(temporalComparator);
+
+    assertThat(TypeUtils.compare(aValue, aValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(aValue, aValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(aValue.getInvocationsAmount()).isEqualTo(2);
+    aValue.resetInvocationsAmount();
+
+    assertThat(TypeUtils.compare(aValue, anotherValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(anotherValue, aValue, OQLLexerTokenTypes.TOK_NE))
+        .isEqualTo(Boolean.TRUE);
+    assertThat(TypeUtils.compare(aValue, anotherValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(TypeUtils.compare(anotherValue, aValue, OQLLexerTokenTypes.TOK_EQ))
+        .isEqualTo(Boolean.FALSE);
+    assertThat(aValue.getInvocationsAmount()).isEqualTo(2);
+    assertThat(anotherValue.getInvocationsAmount()).isEqualTo(2);
+    aValue.resetInvocationsAmount();
+    anotherValue.resetInvocationsAmount();
+
+    // Extra check to verify that no other comparison methods were called.
+    verify(numericComparator, times(0)).compare(any(), any());
+    verify(temporalComparator, times(0)).compare(any(), any());
+  }
+
+  @Test
+  public void comparingArbitraryObjectsUsingAnUnsupportedComparisonOperatorShouldThrowException() {
+    OQLLexerTokenTypes tempInstance = new OQLLexerTokenTypes() {};
+    Field[] fields = OQLLexerTokenTypes.class.getDeclaredFields();
+
+    Arrays.stream(fields).forEach(field -> {
+      try {
+        int token = field.getInt(tempInstance);
+        if (!comparisonOperators.contains(token)) {
+          assertThatThrownBy(() -> TypeUtils.compare(new ArbitraryObject("0"),
+              new ArbitraryObject("0"),
+              token)).isInstanceOf(TypeMismatchException.class).hasMessageMatching(
+                  "^Unable to use a relational comparison operator to compare an instance of class ' (.*) ' with an instance of ' (.*) '$");
+        }
+      } catch (IllegalAccessException exception) {
+        throw new RuntimeException(exception);
+      }
+    });
+  }
+}
diff --git a/geode-core/src/test/resources/org/apache/geode/codeAnalysis/excludedClasses.txt b/geode-core/src/test/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
index da96ce7..1a99416 100644
--- a/geode-core/src/test/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
+++ b/geode-core/src/test/resources/org/apache/geode/codeAnalysis/excludedClasses.txt
@@ -81,3 +81,9 @@ org/apache/geode/distributed/internal/RuntimeDistributionConfigImpl
 org/apache/geode/pdx/internal/PdxInstanceEnum
 org/apache/geode/pdx/internal/PdxInstanceImpl
 org/apache/geode/pdx/internal/WritablePdxInstanceImpl
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy$1
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy$2
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy$3
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy$4
+org/apache/geode/cache/query/internal/types/TypeUtils$ComparisonStrategy$5

-- 
To stop receiving notification emails like this one, please contact
jasonhuynh@apache.org.