/* * 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 java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.Map; 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.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; import org.apache.geode.pdx.internal.PdxInstanceEnum; import org.apache.geode.pdx.internal.PdxString; /** * Utilities for casting and comparing values of possibly differing types, testing and cloning query * literals. * * @version $Revision: 1.1 $ */ public class TypeUtils implements OQLLexerTokenTypes { private static List _numericPrimitiveClasses = Arrays.asList( new Class[] {Byte.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE}); private static List _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 */ 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; } // 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; } } 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 { 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)) { /* * @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 = * getNumericComparator(); boolean b; if (obj1.equals(Float.NaN) || obj1.equals(Double.NaN)) * { 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 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))); } } 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; } } public static Comparator getTemporalComparator() { return new TemporalComparator(); } public static Comparator getNumericComparator() { return new NumericComparator(); } public static Comparator getExtendedNumericComparator() { return new ExtendedNumericComparator(); } public static Object indexKeyFor(Object obj) throws TypeMismatchException { if (obj == null) return null; if (obj instanceof Byte) return Integer.valueOf(((Byte) obj).intValue()); 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. * * @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 */ 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; } 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)); // check to see if the classes are assignable if (destType.isAssignableFrom(srcType)) 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)) 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) i = _numericWrapperClasses.indexOf(srcType); int destP = _numericPrimitiveClasses.indexOf(destType); int destW = -1; if (destP < 0) destW = _numericWrapperClasses.indexOf(destType); // same size wrapper if (i >= 0 && destW == i) return true; // same or wider primitive 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) return true; } // no other possibilities return false; } 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; } } // 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; } // 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; // } 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); } public static ObjectType getRegionEntryType(Region rgn) { // just use an ObjectType for now return new ObjectTypeImpl(Region.Entry.class); } }