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