/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package com.espertech.esper.util; import com.espertech.esper.client.ConfigurationException; import com.espertech.esper.client.annotation.Hook; import com.espertech.esper.client.annotation.HookType; import com.espertech.esper.collection.Pair; import com.espertech.esper.epl.core.EngineImportException; import com.espertech.esper.epl.core.EngineImportService; import com.espertech.esper.epl.core.MethodResolutionService; import com.espertech.esper.epl.expression.ExprValidationException; import com.espertech.esper.event.EventAdapterException; import com.espertech.esper.type.*; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import java.io.StringWriter; import java.lang.annotation.Annotation; import java.lang.reflect.*; import java.math.BigDecimal; import java.math.BigInteger; import java.util.*; /** * Helper for questions about Java classes such as * <p> what is the boxed type for a primitive type * <p> is this a numeric type. */ public class JavaClassHelper { /** * Returns the boxed class for the given class, or the class itself if already boxed or not a primitive type. * For primitive unboxed types returns the boxed types, e.g. returns java.lang.Integer for passing int.class. * For any other class, returns the class passed. * @param clazz is the class to return the boxed class for * @return boxed variant of the same class */ public static Class getBoxedType(Class clazz) { if (clazz == null) { return clazz; } if (!clazz.isPrimitive()) { return clazz; } if (clazz == boolean.class) { return Boolean.class; } if (clazz == char.class) { return Character.class; } if (clazz == double.class) { return Double.class; } if (clazz == float.class) { return Float.class; } if (clazz == byte.class) { return Byte.class; } if (clazz == short.class) { return Short.class; } if (clazz == int.class) { return Integer.class; } if (clazz == long.class) { return Long.class; } return clazz; } /** * Returns a comma-separated parameter type list in readable form, * considering arrays and null-type parameters. * @param parameters is the parameter types to render * @return rendered list of parameters */ public static String getParameterAsString(Class[] parameters) { StringBuilder builder = new StringBuilder(); String delimiterComma = ", "; String delimiter = ""; for (Class param : parameters) { builder.append(delimiter); builder.append(getParameterAsString(param)); delimiter = delimiterComma; } return builder.toString(); } /** * Returns a parameter as a string text, allowing null values to represent a null * select expression type. * @param param is the parameter type * @return string representation of parameter */ public static String getParameterAsString(Class param) { if (param == null) { return "null (any type)"; } return param.getSimpleName(); } /** * Returns the un-boxed class for the given class, or the class itself if already un-boxed or not a primitive type. * For primitive boxed types returns the unboxed primitive type, e.g. returns int.class for passing Integer.class. * For any other class, returns the class passed. * @param clazz is the class to return the unboxed (or primitive) class for * @return primitive variant of the same class */ public static Class getPrimitiveType(Class clazz) { if (clazz == Boolean.class) { return boolean.class; } if (clazz == Character.class) { return char.class; } if (clazz == Double.class) { return double.class; } if (clazz == Float.class) { return float.class; } if (clazz == Byte.class) { return byte.class; } if (clazz == Short.class) { return short.class; } if (clazz == Integer.class) { return int.class; } if (clazz == Long.class) { return long.class; } return clazz; } /** * Determines if the class passed in is one of the numeric classes. * @param clazz to check * @return true if numeric, false if not */ public static boolean isNumeric(Class clazz) { if ((clazz == Double.class) || (clazz == double.class) || (clazz == BigDecimal.class) || (clazz == BigInteger.class) || (clazz == Float.class) || (clazz == float.class) || (clazz == Short.class) || (clazz == short.class) || (clazz == Integer.class) || (clazz == int.class) || (clazz == Long.class) || (clazz == long.class) || (clazz == Byte.class) || (clazz == byte.class)) { return true; } return false; } /** * Determines if the class passed in is one of the numeric classes and not a floating point. * @param clazz to check * @return true if numeric and not a floating point, false if not */ public static boolean isNumericNonFP(Class clazz) { if ((clazz == Short.class) || (clazz == short.class) || (clazz == Integer.class) || (clazz == int.class) || (clazz == Long.class) || (clazz == long.class) || (clazz == Byte.class) || (clazz == byte.class)) { return true; } return false; } /** * Returns true if 2 classes are assignment compatible. * @param invocationType type to assign from * @param declarationType type to assign to * @return true if assignment compatible, false if not */ public static boolean isAssignmentCompatible(Class invocationType, Class declarationType) { if (invocationType == null) { return true; } if (declarationType.isAssignableFrom(invocationType)) { return true; } if (declarationType.isPrimitive()) { Class parameterWrapperClazz = getBoxedType(declarationType); if (parameterWrapperClazz != null) { if (parameterWrapperClazz.equals(invocationType)) { return true; } } } if (getBoxedType(invocationType) == declarationType) { return true; } Set<Class> widenings = MethodResolver.getWideningConversions().get(declarationType); if (widenings != null) { return widenings.contains(invocationType); } if (declarationType.isInterface()) { if (isImplementsInterface(invocationType, declarationType)) { return true; } } return recursiveIsSuperClass(invocationType, declarationType); } /** * Determines if the class passed in is a boolean boxed or unboxed type. * @param clazz to check * @return true if boolean, false if not */ public static boolean isBoolean(Class clazz) { if ((clazz == Boolean.class) || (clazz == boolean.class)) { return true; } return false; } /** * Returns the coercion type for the 2 numeric types for use in arithmatic. * Note: byte and short types always result in integer. * @param typeOne is the first type * @param typeTwo is the second type * @return coerced type * @throws CoercionException if types don't allow coercion */ public static Class getArithmaticCoercionType(Class typeOne, Class typeTwo) throws CoercionException { Class boxedOne = getBoxedType(typeOne); Class boxedTwo = getBoxedType(typeTwo); if (!isNumeric(boxedOne) || !isNumeric(boxedTwo)) { throw new CoercionException("Cannot coerce types " + typeOne.getName() + " and " + typeTwo.getName()); } if (boxedOne == boxedTwo) { return boxedOne; } if ((boxedOne == BigDecimal.class) || (boxedTwo == BigDecimal.class)) { return BigDecimal.class; } if ( ((boxedOne == BigInteger.class) && JavaClassHelper.isFloatingPointClass(boxedTwo)) || ((boxedTwo == BigInteger.class) && JavaClassHelper.isFloatingPointClass(boxedOne))) { return BigDecimal.class; } if ((boxedOne == BigInteger.class) || (boxedTwo == BigInteger.class)) { return BigInteger.class; } if ((boxedOne == Double.class) || (boxedTwo == Double.class)) { return Double.class; } if ((boxedOne == Float.class) && (!isFloatingPointClass(typeTwo))) { return Double.class; } if ((boxedTwo == Float.class) && (!isFloatingPointClass(typeOne))) { return Double.class; } if ((boxedOne == Long.class) || (boxedTwo == Long.class)) { return Long.class; } return Integer.class; } /** * Coerce the given number to the given type, assuming the type is a Boxed type. Allows coerce to lower resultion number. * Does't coerce to primitive types. * <p> * Meant for statement compile-time use, not for runtime use. * * @param numToCoerce is the number to coerce to the given type * @param resultBoxedType is the boxed result type to return * @return the numToCoerce as a value in the given result type */ public static Number coerceBoxed(Number numToCoerce, Class resultBoxedType) { if (numToCoerce.getClass() == resultBoxedType) { return numToCoerce; } if (resultBoxedType == Double.class) { return numToCoerce.doubleValue(); } if (resultBoxedType == Long.class) { return numToCoerce.longValue(); } if (resultBoxedType == BigInteger.class) { return BigInteger.valueOf(numToCoerce.longValue()); } if (resultBoxedType == BigDecimal.class) { if (JavaClassHelper.isFloatingPointNumber(numToCoerce)) { return new BigDecimal(numToCoerce.doubleValue()); } return new BigDecimal(numToCoerce.longValue()); } if (resultBoxedType == Float.class) { return numToCoerce.floatValue(); } if (resultBoxedType == Integer.class) { return numToCoerce.intValue(); } if (resultBoxedType == Short.class) { return numToCoerce.shortValue(); } if (resultBoxedType == Byte.class) { return numToCoerce.byteValue(); } throw new IllegalArgumentException("Cannot coerce to number subtype " + resultBoxedType.getName()); } /** * Returns true if the Number instance is a floating point number. * @param number to check * @return true if number is Float or Double type */ public static boolean isFloatingPointNumber(Number number) { if ((number instanceof Float) || (number instanceof Double)) { return true; } return false; } /** * Returns true if the supplied type is a floating point number. * @param clazz to check * @return true if primitive or boxed float or double */ public static boolean isFloatingPointClass(Class clazz) { if ((clazz == Float.class) || (clazz == Double.class) || (clazz == float.class) || (clazz == double.class)) { return true; } return false; } /** * Returns for 2 classes to be compared via relational operator the Class type of * common comparison. The output is always Long.class, Double.class, String.class or Boolean.class * depending on whether the passed types are numeric and floating-point. * Accepts primitive as well as boxed types. * @param typeOne is the first type * @param typeTwo is the second type * @return One of Long.class, Double.class or String.class * @throws CoercionException if the types cannot be compared */ public static Class getCompareToCoercionType(Class typeOne, Class typeTwo) throws CoercionException { if ((typeOne == String.class) && (typeTwo == String.class)) { return String.class; } if ( ((typeOne == boolean.class) || ((typeOne == Boolean.class))) && ((typeTwo == boolean.class) || ((typeTwo == Boolean.class))) ) { return Boolean.class; } if (!isJavaBuiltinDataType(typeOne) && (!isJavaBuiltinDataType(typeTwo))) { if (typeOne != typeTwo) { return Object.class; } return typeOne; } if (typeOne == null) { return typeTwo; } if (typeTwo == null) { return typeOne; } if (!isNumeric(typeOne) || !isNumeric(typeTwo)) { String typeOneName = typeOne.getName(); String typeTwoName = typeTwo.getName(); throw new CoercionException("Types cannot be compared: " + typeOneName + " and " + typeTwoName); } return getArithmaticCoercionType(typeOne, typeTwo); } /** * Returns true if the type is one of the big number types, i.e. BigDecimal or BigInteger * @param clazz to check * @return true for big number */ public static boolean isBigNumberType(Class clazz) { if ((clazz == BigInteger.class) || (clazz == BigDecimal.class)) { return true; } return false; } /** * Determines if a number can be coerced upwards to another number class without loss. * <p> * Clients must pass in two classes that are numeric types. * <p> * Any number class can be coerced to double, while only double cannot be coerced to float. * Any non-floating point number can be coerced to long. * Integer can be coerced to Byte and Short even though loss is possible, for convenience. * @param numberClassToBeCoerced the number class to be coerced * @param numberClassToCoerceTo the number class to coerce to * @return true if numbers can be coerced without loss, false if not */ public static boolean canCoerce(Class numberClassToBeCoerced, Class numberClassToCoerceTo) { Class boxedFrom = getBoxedType(numberClassToBeCoerced); Class boxedTo = getBoxedType(numberClassToCoerceTo); if (!isNumeric(numberClassToBeCoerced)) { throw new IllegalArgumentException("Class '" + numberClassToBeCoerced + "' is not a numeric type'"); } if (boxedTo == Float.class) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class) || (boxedFrom == Long.class) || (boxedFrom == Float.class)); } else if (boxedTo == Double.class) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class) || (boxedFrom == Long.class) || (boxedFrom == Float.class) || (boxedFrom == Double.class)); } else if (boxedTo == BigDecimal.class) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class) || (boxedFrom == Long.class) || (boxedFrom == Float.class) || (boxedFrom == Double.class) || (boxedFrom == BigInteger.class) || (boxedFrom == BigDecimal.class)); } else if (boxedTo == BigInteger.class) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class) || (boxedFrom == Long.class) || (boxedFrom == BigInteger.class)); } else if (boxedTo == Long.class) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class) || (boxedFrom == Long.class)); } else if ((boxedTo == Integer.class) || (boxedTo == Short.class) || (boxedTo == Byte.class)) { return ((boxedFrom == Byte.class) || (boxedFrom == Short.class) || (boxedFrom == Integer.class)); } else { throw new IllegalArgumentException("Class '" + numberClassToCoerceTo + "' is not a numeric type'"); } } /** * Returns for the class name given the class name of the boxed (wrapped) type if * the class name is one of the Java primitive types. * @param className is a class name, a Java primitive type or other class * @return boxed class name if Java primitive type, or just same class name passed in if not a primitive type */ public static String getBoxedClassName(String className) { if (className.equals(char.class.getName())) { return Character.class.getName(); } if (className.equals(byte.class.getName())) { return Byte.class.getName(); } if (className.equals(short.class.getName())) { return Short.class.getName(); } if (className.equals(int.class.getName())) { return Integer.class.getName(); } if (className.equals(long.class.getName())) { return Long.class.getName(); } if (className.equals(float.class.getName())) { return Float.class.getName(); } if (className.equals(double.class.getName())) { return Double.class.getName(); } if (className.equals(boolean.class.getName())) { return Boolean.class.getName(); } return className; } /** * Returns true if the class passed in is a Java built-in data type (primitive or wrapper) including String and 'null'. * @param clazz to check * @return true if built-in data type, or false if not */ public static boolean isJavaBuiltinDataType(Class clazz) { if (clazz == null) { return true; } Class clazzBoxed = getBoxedType(clazz); if (isNumeric(clazzBoxed)) { return true; } if (isBoolean(clazzBoxed)) { return true; } if (clazzBoxed.equals(String.class)) { return true; } if ((clazzBoxed.equals(char.class)) || (clazzBoxed.equals(Character.class))) { return true; } if (clazzBoxed.equals(void.class)) { return true; } return false; } // null values are allowed and represent and unknown type /** * Determines a common denominator type to which one or more types can be casted or coerced. * For use in determining the result type in certain expressions (coalesce, case). * <p> * Null values are allowed as part of the input and indicate a 'null' constant value * in an expression tree. Such as value doesn't have any type and can be ignored in * determining a result type. * <p> * For numeric types, determines a coercion type that all types can be converted to * via the method getArithmaticCoercionType. * <p> * Indicates that there is no common denominator type by throwing {@link CoercionException}. * @param types is an array of one or more types, which can be Java built-in (primitive or wrapper) * or user types * @return common denominator type if any can be found, for use in comparison * @throws CoercionException when no coercion type could be determined */ public static Class getCommonCoercionType(Class[] types) throws CoercionException { if (types.length < 1) { throw new IllegalArgumentException("Unexpected zero length array"); } if (types.length == 1) { return getBoxedType(types[0]); } // Reduce to non-null types List<Class> nonNullTypes = new ArrayList<Class>(); for (int i = 0; i < types.length; i++) { if (types[i] != null) { nonNullTypes.add(types[i]); } } types = nonNullTypes.toArray(new Class[nonNullTypes.size()]); if (types.length == 0) { return null; // only null types, result is null } if (types.length == 1) { return getBoxedType(types[0]); } // Check if all String if (types[0] == String.class) { for (int i = 0; i < types.length; i++) { if (types[i] != String.class) { throw new CoercionException("Cannot coerce to String type " + types[i].getName()); } } return String.class; } // Convert to boxed types for (int i = 0; i < types.length; i++) { types[i] = getBoxedType(types[i]); } // Check if all boolean if (types[0] == Boolean.class) { for (int i = 0; i < types.length; i++) { if (types[i] != Boolean.class) { throw new CoercionException("Cannot coerce to Boolean type " + types[i].getName()); } } return Boolean.class; } // Check if all char if (types[0] == Character.class) { for (Class type : types) { if (type != Character.class) { throw new CoercionException("Cannot coerce to Boolean type " + type.getName()); } } return Character.class; } // Check if all the same non-Java builtin type, i.e. Java beans etc. boolean isAllBuiltinTypes = true; boolean isAllNumeric = true; for (Class type : types) { if (!isNumeric(type) && (!isJavaBuiltinDataType(type))) { isAllBuiltinTypes = false; } } // handle all built-in types if (!isAllBuiltinTypes) { for (Class type : types) { if (types[0] == type) { continue; } if (isJavaBuiltinDataType(type)) { throw new CoercionException("Cannot coerce to " + types[0].getName() + " type " + type.getName()); } if (type != types[0]) { return Object.class; } } return types[0]; } // test for numeric if (!isAllNumeric) { throw new CoercionException("Cannot coerce to numeric type " + types[0].getName()); } // Use arithmatic coercion type as the final authority, considering all types Class result = getArithmaticCoercionType(types[0], types[1]); int count = 2; while(count < types.length) { result = getArithmaticCoercionType(result, types[count]); count++; } return result; } /** * Returns the class given a fully-qualified class name. * @param className is the fully-qualified class name, java primitive types included. * @return class for name * @throws ClassNotFoundException if the class cannot be found */ public static Class getClassForName(String className) throws ClassNotFoundException { if (className.equals(boolean.class.getName())) { return boolean.class; } if (className.equals(char.class.getName())) { return char.class; } if (className.equals(double.class.getName())) { return double.class; } if (className.equals(float.class.getName())) { return float.class; } if (className.equals(byte.class.getName())) { return byte.class; } if (className.equals(short.class.getName())) { return short.class; } if (className.equals(int.class.getName())) { return int.class; } if (className.equals(long.class.getName())) { return long.class; } ClassLoader cl = Thread.currentThread().getContextClassLoader(); return Class.forName(className, true, cl); } /** * Returns the boxed class for the given classname, recognizing all primitive and abbreviations, * uppercase and lowercase. * <p> * Recognizes "int" as Integer.class and "strIng" as String.class, and "Integer" as Integer.class, and so on. * @param className is the name to recognize * @return class * @throws EventAdapterException is throw if the class cannot be identified */ public static Class getClassForSimpleName(String className) throws EventAdapterException { if (("string".equals(className.toLowerCase().trim())) || ("varchar".equals(className.toLowerCase().trim())) || ("varchar2".equals(className.toLowerCase().trim()))) { return String.class; } if (("integer".equals(className.toLowerCase().trim())) || ("int".equals(className.toLowerCase().trim()))) { return Integer.class; } if ("bool".equals(className.toLowerCase().trim())) { return Boolean.class; } if ("character".equals(className.toLowerCase().trim())) { return Character.class; } // use the boxed type for primitives String boxedClassName = JavaClassHelper.getBoxedClassName(className.trim()); try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return Class.forName(boxedClassName, true, cl); } catch (ClassNotFoundException ex) { // expected } boxedClassName = JavaClassHelper.getBoxedClassName(className.toLowerCase().trim()); try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return Class.forName(boxedClassName, true, cl); } catch (ClassNotFoundException ex) { return null; } } /** * Returns the class for a Java primitive type name, ignoring case, and considering String as a primitive. * @param typeName is a potential primitive Java type, or some other type name * @return class for primitive type name, or null if not a primitive type. */ public static Class getPrimitiveClassForName(String typeName) { typeName = typeName.toLowerCase(); if (typeName.equals("boolean")) { return boolean.class; } if (typeName.equals("char")) { return char.class; } if (typeName.equals("double")) { return double.class; } if (typeName.equals("float")) { return float.class; } if (typeName.equals("byte")) { return byte.class; } if (typeName.equals("short")) { return short.class; } if (typeName.equals("int")) { return int.class; } if (typeName.equals("long")) { return long.class; } if (typeName.equals("string")) { return String.class; } return null; } /** * Parse the String using the given Java built-in class for parsing. * @param clazz is the class to parse the value to * @param text is the text to parse * @return value matching the type passed in */ public static Object parse(Class clazz, String text) { Class classBoxed = JavaClassHelper.getBoxedType(clazz); if (classBoxed == String.class) { return text; } if (classBoxed == Character.class) { return text.charAt(0); } if (classBoxed == Boolean.class) { return BoolValue.parseString(text.toLowerCase().trim()); } if (classBoxed == Byte.class) { return ByteValue.parseString(text.trim()); } if (classBoxed == Short.class) { return ShortValue.parseString(text.trim()); } if (classBoxed == Long.class) { return LongValue.parseString(text.trim()); } if (classBoxed == Float.class) { return FloatValue.parseString(text.trim()); } if (classBoxed == Double.class) { return DoubleValue.parseString(text.trim()); } if (classBoxed == Integer.class) { return IntValue.parseString(text.trim()); } return null; } /** * Method to check if a given class, and its superclasses and interfaces (deep), implement a given interface. * @param clazz to check, including all its superclasses and their interfaces and extends * @param interfaceClass is the interface class to look for * @return true if such interface is implemented by any of the clazz or its superclasses or * extends by any interface and superclasses (deep check) */ public static boolean isImplementsInterface(Class clazz, Class interfaceClass) { if (!(interfaceClass.isInterface())) { throw new IllegalArgumentException("Interface class passed in is not an interface"); } boolean resultThisClass = recursiveIsImplementsInterface(clazz, interfaceClass); if (resultThisClass) { return true; } return recursiveSuperclassImplementsInterface(clazz, interfaceClass); } /** * Method to check if a given class, and its superclasses and interfaces (deep), implement a given interface or extend a given class. * @param extendorOrImplementor is the class to inspects its extends and implements clauses * @param extendedOrImplemented is the potential interface, or superclass, to check * @return true if such interface is implemented by any of the clazz or its superclasses or * extends by any interface and superclasses (deep check) */ public static boolean isSubclassOrImplementsInterface(Class extendorOrImplementor, Class extendedOrImplemented) { if (extendorOrImplementor.equals(extendedOrImplemented)) { return true; } if (extendedOrImplemented.isInterface()) { return recursiveIsImplementsInterface(extendorOrImplementor, extendedOrImplemented) || recursiveSuperclassImplementsInterface(extendorOrImplementor, extendedOrImplemented); } return recursiveIsSuperClass(extendorOrImplementor, extendedOrImplemented); } private static boolean recursiveIsSuperClass(Class clazz, Class superClass) { if (clazz == null) { return false; } if (clazz.isPrimitive()) { return false; } Class mySuperClass = clazz.getSuperclass(); if (mySuperClass == superClass) { return true; } if (mySuperClass == Object.class) { return false; } return recursiveIsSuperClass(mySuperClass, superClass); } private static boolean recursiveSuperclassImplementsInterface(Class clazz, Class interfaceClass) { Class superClass = clazz.getSuperclass(); if ((superClass == null) || (superClass == Object.class)) { return false; } boolean result = recursiveIsImplementsInterface(superClass, interfaceClass); if (result) { return result; } return recursiveSuperclassImplementsInterface(superClass, interfaceClass); } private static boolean recursiveIsImplementsInterface(Class clazz, Class interfaceClass) { if (clazz == interfaceClass) { return true; } Class[] interfaces = clazz.getInterfaces(); if (interfaces == null) { return false; } for (Class implementedInterface : interfaces) { if (implementedInterface == interfaceClass) { return true; } boolean result = recursiveIsImplementsInterface(implementedInterface, interfaceClass); if (result) { return result; } } return false; } /** * Looks up the given class and checks that it implements or extends the required interface, * and instantiates an object. * @param implementedOrExtendedClass is the class that the looked-up class should extend or implement * @param className of the class to load, check type and instantiate * @return instance of given class, via newInstance * @throws ClassInstantiationException if the type does not match or the class cannot be loaded or an object instantiated */ public static Object instantiate(Class implementedOrExtendedClass, String className) throws ClassInstantiationException { Class clazz; try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); clazz = Class.forName(className, true, cl); } catch (ClassNotFoundException ex) { throw new ClassInstantiationException("Unable to load class '" + className + "', class not found", ex); } if (!JavaClassHelper.isSubclassOrImplementsInterface(clazz, implementedOrExtendedClass)) { if (implementedOrExtendedClass.isInterface()) { throw new ClassInstantiationException("Class '" + className + "' does not implement interface '" + implementedOrExtendedClass.getName() + "'"); } throw new ClassInstantiationException("Class '" + className + "' does not extend '" + implementedOrExtendedClass.getName() + "'"); } Object obj; try { obj = clazz.newInstance(); } catch (InstantiationException ex) { throw new ClassInstantiationException("Unable to instantiate from class '" + className + "' via default constructor", ex); } catch (IllegalAccessException ex) { throw new ClassInstantiationException("Illegal access when instantiating class '" + className + "' via default constructor", ex); } return obj; } /** * Populates all interface and superclasses for the given class, recursivly. * @param clazz to reflect upon * @param result set of classes to populate */ public static void getSuper(Class clazz, Set<Class> result) { getSuperInterfaces(clazz, result); getSuperClasses(clazz, result); } /** * Returns true if the simple class name is the class name of the fully qualified classname. * <p>This method does not verify validity of class and package names, it uses simple string compare * inspecting the trailing part of the fully qualified class name. * @param simpleClassName simple class name * @param fullyQualifiedClassname fully qualified class name contains package name and simple class name * @return true if simple class name of the fully qualified class name, false if not */ public static boolean isSimpleNameFullyQualfied(String simpleClassName, String fullyQualifiedClassname) { if ((fullyQualifiedClassname.endsWith("." + simpleClassName)) || (fullyQualifiedClassname.equals(simpleClassName))) { return true; } return false; } /** * Returns true if the Class is a fragmentable type, i.e. not a primitive or boxed type or * any of the common built-in types or does not implement Map. * @param propertyType type to check * @return true if fragmentable */ public static boolean isFragmentableType(Class propertyType) { if (propertyType == null) { return false; } if (propertyType.isArray()) { return isFragmentableType(propertyType.getComponentType()); } if (JavaClassHelper.isJavaBuiltinDataType(propertyType)) { return false; } if (propertyType.isEnum()) { return false; } if (JavaClassHelper.isImplementsInterface(propertyType, Map.class)) { return false; } if (propertyType == Node.class) { return false; } if (propertyType == NodeList.class) { return false; } if (propertyType == Object.class) { return false; } if (propertyType == Calendar.class) { return false; } if (propertyType == Date.class) { return false; } if (propertyType == java.sql.Date.class) { return false; } if (propertyType == java.sql.Time.class) { return false; } if (propertyType == java.sql.Timestamp.class) { return false; } return true; } public static Class[] getSuperInterfaces(Class clazz) { Set<Class> interfaces = new HashSet<Class>(); Class[] declaredInterfaces = clazz.getInterfaces(); for (int i = 0; i < declaredInterfaces.length; i++) { interfaces.add(declaredInterfaces[i]); getSuperInterfaces(declaredInterfaces[i], interfaces); } Set<Class> superClasses = new HashSet<Class>(); getSuperClasses(clazz, superClasses); for (Class superClass : superClasses) { declaredInterfaces = superClass.getInterfaces(); for (int i = 0; i < declaredInterfaces.length; i++) { interfaces.add(declaredInterfaces[i]); getSuperInterfaces(declaredInterfaces[i], interfaces); } } return interfaces.toArray(new Class[declaredInterfaces.length]); } public static void getSuperInterfaces(Class clazz, Set<Class> result) { Class interfaces[] = clazz.getInterfaces(); for (int i = 0; i < interfaces.length; i++) { result.add(interfaces[i]); getSuperInterfaces(interfaces[i], result); } } private static void getSuperClasses(Class clazz, Set<Class> result) { Class superClass = clazz.getSuperclass(); if (superClass == null) { return; } result.add(superClass); getSuper(superClass, result); } /** * Returns the generic type parameter of a return value by a field or method. * @param method method or null if field * @param field field or null if method * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericReturnType(Method method, Field field, boolean isAllowNull) { if (method == null) { return getGenericFieldType(field, isAllowNull); } else { return getGenericReturnType(method, isAllowNull); } } /** * Returns the second generic type parameter of a return value by a field or method. * @param method method or null if field * @param field field or null if method * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericReturnTypeMap(Method method, Field field, boolean isAllowNull) { if (method == null) { return getGenericFieldTypeMap(field, isAllowNull); } else { return getGenericReturnTypeMap(method, isAllowNull); } } /** * Returns the generic type parameter of a return value by a method. * @param method method or null if field * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericReturnType(Method method, boolean isAllowNull) { Type t = method.getGenericReturnType(); Class result = getGenericType(t, 0); if (!isAllowNull && result == null) { return Object.class; } return result; } /** * Returns the second generic type parameter of a return value by a field or method. * @param method method or null if field * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericReturnTypeMap(Method method, boolean isAllowNull) { Type t = method.getGenericReturnType(); Class result = getGenericType(t, 1); if (!isAllowNull && result == null) { return Object.class; } return result; } /** * Returns the generic type parameter of a return value by a field. * @param field field or null if method * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericFieldType(Field field, boolean isAllowNull) { Type t = field.getGenericType(); Class result = getGenericType(t, 0); if (!isAllowNull && result == null) { return Object.class; } return result; } /** * Returns the generic type parameter of a return value by a field or method. * @param field field or null if method * @param isAllowNull whether null is allowed as a return value or expected Object.class * @return generic type parameter */ public static Class getGenericFieldTypeMap(Field field, boolean isAllowNull) { Type t = field.getGenericType(); Class result = getGenericType(t, 1); if (!isAllowNull && result == null) { return Object.class; } return result; } public static Class getGenericType(Type t, int index) { if (t == null) { return null; } if (!(t instanceof ParameterizedType)) { return null; } ParameterizedType ptype = (ParameterizedType) t; if ((ptype.getActualTypeArguments() == null) || (ptype.getActualTypeArguments().length < (index + 1))) { return Object.class; } Type typeParam = ptype.getActualTypeArguments()[index]; if (!(typeParam instanceof Class)) { return Object.class; } return (Class) typeParam; } /** * Returns an instance of a hook as specified by an annotation. * @param annotations to search * @param hookType type to look for * @param interfaceExpected interface required * @param resolution for resolving references, optional, if not provided then using Class.forName * @return hook instance * @throws ExprValidationException if instantiation failed */ public static Object getAnnotationHook(Annotation[] annotations, HookType hookType, Class interfaceExpected, MethodResolutionService resolution) throws ExprValidationException { if (annotations == null) { return null; } String hookClass = null; for (int i = 0; i < annotations.length; i++) { if (!(annotations[i] instanceof Hook)) { continue; } Hook hook = (Hook) annotations[i]; if (hook.type() != hookType) { continue; } hookClass = hook.hook(); } if (hookClass == null) { return null; } Class clazz; try { if (resolution == null) { clazz = Class.forName(hookClass); } else { clazz = resolution.resolveClass(hookClass); } } catch (Exception e) { throw new ExprValidationException("Failed to resolve hook provider of hook type '" + hookType + "' import '" + hookClass + "' :" + e.getMessage()); } if (!JavaClassHelper.isImplementsInterface(clazz, interfaceExpected)) { throw new ExprValidationException("Hook provider for hook type '" + hookType + "' " + "class '" + clazz.getName() + "' does not implement the required '" + interfaceExpected.getSimpleName() + "' interface"); } Object hook; try { hook = clazz.newInstance(); } catch (Exception e) { throw new ExprValidationException("Failed to instantiate hook provider of hook type '" + hookType + "' " + "class '" + clazz.getName() + "' :" + e.getMessage()); } return hook; } /** * Resolve a string constant as a possible enumeration value, returning null if not resolved. * @param constant to resolve * @param methodResolutionService for statement-level use to resolve enums, can be null * @param engineImportService for engine-level use to resolve enums, can be null * @return null or enumeration value * @throws ExprValidationException if there is an error accessing the enum */ public static Object resolveIdentAsEnumConst(String constant, MethodResolutionService methodResolutionService, EngineImportService engineImportService) throws ExprValidationException { int lastDotIndex = constant.lastIndexOf('.'); if (lastDotIndex == -1) { return null; } String className = constant.substring(0, lastDotIndex); String constName = constant.substring(lastDotIndex + 1); Class clazz; try { if (engineImportService != null) { clazz = engineImportService.resolveClass(className); } else { clazz = methodResolutionService.resolveClass(className); } } catch (EngineImportException e) { return null; } Field field; try { field = clazz.getField(constName); } catch (NoSuchFieldException e) { return null; } int modifiers = field.getModifiers(); if (Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers)) { try { return field.get(null); } catch (IllegalAccessException e) { throw new ExprValidationException("Exception accessing field '" + field.getName() + "': " + e.getMessage(), e); } } return null; } public static Class getArrayType(Class resultType) { return Array.newInstance(resultType, 0).getClass(); } public static String getClassNameFullyQualPretty(Class clazz) { if (clazz == null) { return "null"; } if (clazz.isArray()) { return clazz.getComponentType().getName() + "(Array)"; } return clazz.getName(); } public static String getClassNameFullyQualPrettyWithClassloader(Class clazz) { String name = getClassNameFullyQualPretty(clazz); String classloader = getClassLoaderId(clazz.getClassLoader()); return name + "(loaded by " + classloader + ")"; } public static String getClassLoaderId(ClassLoader classLoader) { if (classLoader == null) { return "(classloader is null)"; } return classLoader.getClass().getName() + "@" + System.identityHashCode(classLoader); } public static Method getMethodByName(Class clazz, String methodName) { for (Method m : clazz.getMethods()) { if (m.getName().equals(methodName)) { return m; } } throw new IllegalStateException("Expected '" + methodName + "' method not found on interface '" + clazz.getName()); } public static String printInstance(Object instance, boolean fullyQualified) { if (instance == null) { return "(null)"; } StringWriter writer = new StringWriter(); writeInstance(writer, instance, fullyQualified); return writer.toString(); } public static void writeInstance(StringWriter writer, Object instance, boolean fullyQualified) { if (instance == null) { writer.write("(null)"); return; } String className; if (fullyQualified) { className = instance.getClass().getName(); } else { className = instance.getClass().getSimpleName(); } writeInstance(writer, className, instance); } public static void writeInstance(StringWriter writer, String title, Object instance) { writer.write(title); writer.write("@"); if (instance == null) { writer.write("(null)"); } else { writer.write(Integer.toHexString(System.identityHashCode(instance))); } } public static String getMessageInvocationTarget(String statementName, Method method, String classOrPropertyName, Object[] args, InvocationTargetException e) { String parameters = args == null ? "null" : Arrays.toString(args); if (args != null) { Class[] methodParameters = method.getParameterTypes(); for (int i = 0; i < methodParameters.length; i++) { if (methodParameters[i].isPrimitive() && args[i] == null) { return "NullPointerException invoking method '" + method.getName() + "' of class '" + classOrPropertyName + "' in parameter " + i + " passing parameters " + parameters + " for statement '" + statementName + "': The method expects a primitive " + methodParameters[i].getSimpleName() + " value but received a null value"; } } } return "Invocation exception when invoking method '" + method.getName() + "' of class '" + classOrPropertyName + "' passing parameters " + parameters + " for statement '" + statementName + "': " + e.getTargetException().getClass().getSimpleName() + " : " + e.getTargetException().getMessage(); } public static boolean isDatetimeClass(Class inputType) { if (inputType == null) { return false; } if ((!JavaClassHelper.isSubclassOrImplementsInterface(inputType, Calendar.class)) && (!JavaClassHelper.isSubclassOrImplementsInterface(inputType, Date.class)) && (JavaClassHelper.getBoxedType(inputType) != Long.class)) { return false; } return true; } public static Map<String, Object> getClassObjectFromPropertyTypeNames(Properties properties) { Map<String, Object> propertyTypes = new LinkedHashMap<String, Object>(); for(Map.Entry<Object, Object> entry : properties.entrySet()) { String className = (String) entry.getValue(); if ("string".equals(className)) { className = String.class.getName(); } // use the boxed type for primitives String boxedClassName = JavaClassHelper.getBoxedClassName(className); Class clazz; try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); clazz = Class.forName(boxedClassName, true, cl); } catch (ClassNotFoundException ex) { throw new ConfigurationException("Unable to load class '" + boxedClassName + "', class not found", ex); } propertyTypes.put((String) entry.getKey(), clazz); } return propertyTypes; } public static Class getClassInClasspath(String classname) { try { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class clazz = Class.forName(classname, true, cl); return clazz; } catch (ClassNotFoundException ex) { return null; } } public static boolean isSignatureCompatible(Class<?>[] one, Class<?>[] two) { if (Arrays.equals(one, two)) { return true; } if (one.length != two.length) { return false; } for (int i = 0; i < one.length; i++) { Class oneClass = one[i]; Class twoClass = two[i]; if (!JavaClassHelper.isAssignmentCompatible(oneClass, twoClass)) { return false; } } return true; } public static Method findRequiredMethod(Class clazz, String methodName) { Method found = null; for (Method m : clazz.getMethods()) { if (m.getName().equals(methodName)) { found = m; break; } } if (found == null) { throw new IllegalArgumentException("Not found method '" + methodName + "'"); } return found; } public static List<Method> findMethodsByNameStartsWith(Class clazz, String methodName) { Method methods[] = clazz.getMethods(); List<Method> result = new ArrayList<Method>(); for (Method method : methods) { if (method.getName().startsWith(methodName)) { result.add(method); } } return result; } public static List<Annotation> getAnnotations(Class<? extends Annotation> annotationClass, Annotation[] annotations) { List<Annotation> result = null; for (Annotation annotation : annotations) { if (annotation.annotationType() == annotationClass) { if (result == null) { result = new ArrayList<Annotation>(); } result.add(annotation); } } if (result == null) { return Collections.emptyList(); } return result; } public static boolean isAnnotationListed(Class<? extends Annotation> annotationClass, Annotation[] annotations) { return !getAnnotations(annotationClass, annotations).isEmpty(); } public static Set<Field> findAnnotatedFields(Class targetClass, Class<? extends Annotation> annotation) { Set<Field> fields = new LinkedHashSet<Field>(); findFieldInternal(targetClass, annotation, fields); // superclass fields Class clazz = targetClass; while (true) { clazz = clazz.getSuperclass(); if (clazz == Object.class || clazz == null) { break; } findFieldInternal(clazz, annotation, fields); } return fields; } private static void findFieldInternal(Class currentClass, Class<? extends Annotation> annotation, Set<Field> fields) { for (Field field : currentClass.getDeclaredFields()) { if (isAnnotationListed(annotation, field.getDeclaredAnnotations())) { fields.add(field); } } } public static Set<Method> findAnnotatedMethods(Class targetClass, Class<? extends Annotation> annotation) { Set<Method> methods = new LinkedHashSet<Method>(); findAnnotatedMethodsInternal(targetClass, annotation, methods); // superclass fields Class clazz = targetClass; while (true) { clazz = clazz.getSuperclass(); if (clazz == Object.class || clazz == null) { break; } findAnnotatedMethodsInternal(clazz, annotation, methods); } return methods; } private static void findAnnotatedMethodsInternal(Class currentClass, Class<? extends Annotation> annotation, Set<Method> methods) { for (Method method : currentClass.getDeclaredMethods()) { if (isAnnotationListed(annotation, method.getDeclaredAnnotations())) { methods.add(method); } } } public static void setFieldForAnnotation(Object target, Class<? extends Annotation> annotation, Object value) { boolean found = setFieldForAnnotation(target, annotation, value, target.getClass()); if (!found) { Class superClass = target.getClass().getSuperclass(); while (!found) { found = setFieldForAnnotation(target, annotation, value, superClass); if (!found) { superClass = superClass.getSuperclass(); } if (superClass == Object.class || superClass == null) { break; } } } } private static boolean setFieldForAnnotation(Object target, Class<? extends Annotation> annotation, Object value, Class currentClass) { boolean found = false; for (Field field : currentClass.getDeclaredFields()) { if (isAnnotationListed(annotation, field.getDeclaredAnnotations())) { field.setAccessible(true); try { field.set(target, value); } catch (IllegalAccessException e) { throw new RuntimeException("Failed to set field " + field + " on class " + currentClass.getName() + ": " + e.getMessage(), e); } return true; } } return found; } public static Pair<String, Boolean> isGetArrayType(String type) { int index = type.indexOf('['); if (index == -1){ return new Pair<String, Boolean>(type, false); } String typeOnly = type.substring(0, index); return new Pair<String, Boolean>(typeOnly.trim(), true); } }