/* * 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.deltaspike.core.util; import javax.enterprise.inject.Typed; import javax.enterprise.util.Nonbinding; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.WildcardType; import java.security.AccessController; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.apache.deltaspike.core.util.securitymanaged.SetAccessiblePrivilegedAction; /** * Utilities for common reflection based actions. Some are basic Java Reflection based, others are CDI based. */ //X TODO: Look at merging this with some of the other classes from CODI, or if they're really needed //X TODO: Also some methods need JavaDoc @Typed() public abstract class ReflectionUtils { private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private ReflectionUtils() { // prevent instantiation } /** * Get all the declared fields on the class hierarchy. This <b>will</b> * return overridden fields. * * @param clazz The class to search * @return the set of all declared fields or an empty set if there are none */ public static Set<Field> getAllDeclaredFields(Class<?> clazz) { HashSet<Field> fields = new HashSet<Field>(); for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { Collections.addAll(fields, c.getDeclaredFields()); } return fields; } /** * Search the class hierarchy for a field with the given name. Will return * the nearest match, starting with the class specified and searching up the * hierarchy. * * @param clazz The class to search * @param name The name of the field to search for * @return The field found, or null if no field is found */ public static Field tryToFindDeclaredField(Class<?> clazz, String name) { for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { try { return c.getDeclaredField(name); } catch (NoSuchFieldException e) { // No-op, we continue looking up the class hierarchy } } return null; } /** * Get all the declared methods on the class hierarchy. This <b>will</b> * return overridden methods. * * @param clazz The class to search * @return the set of all declared methods or an empty set if there are none */ public static Set<Method> getAllDeclaredMethods(Class<?> clazz) { HashSet<Method> methods = new HashSet<Method>(); for (Class<?> c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { Collections.addAll(methods, c.getDeclaredMethods()); } return methods; } private static String buildInvokeMethodErrorMessage(Method method, Object obj, Object... args) { StringBuilder message = new StringBuilder(String.format( "Details: Exception invoking method [%s] on object [%s], using arguments [", method.getName(), obj)); if (args != null) { for (int i = 0; i < args.length; i++) { message.append(i > 0 ? ", " : "").append(args[i]); } } message.append("]"); return message.toString(); } /** * <p> * Invoke the method on the instance, with any arguments specified, casting * the result of invoking the method to the expected return type. * </p> * <p/> * <p> * This method wraps {@link Method#invoke(Object, Object...)}, converting the * checked exceptions that {@link Method#invoke(Object, Object...)} specifies * to runtime exceptions. * </p> * <p/> * <p> * If instructed, this method attempts to set the accessible flag of the method in a * {@link java.security.PrivilegedAction} before invoking the method. * </p> * * @param setAccessible flag indicating whether method should first be set as * accessible * @param method the method to invoke * @param instance the instance to invoke the method * @param args the arguments to the method * @return the result of invoking the method, or null if the method's return * type is void * @throws RuntimeException if this <code>Method</code> object enforces Java * language access control and the underlying method is * inaccessible or if the underlying method throws an exception or * if the initialization provoked by this method fails. * @throws IllegalArgumentException if the method is an instance method and * the specified <code>instance</code> argument is not an instance * of the class or interface declaring the underlying method (or * of a subclass or implementor thereof); if the number of actual * and formal parameters differ; if an unwrapping conversion for * primitive arguments fails; or if, after possible unwrapping, a * parameter value cannot be converted to the corresponding formal * parameter type by a method invocation conversion. * @throws NullPointerException if the specified <code>instance</code> is * null and the method is an instance method. * @throws ClassCastException if the result of invoking the method cannot be * cast to the expectedReturnType * @throws ExceptionInInitializerError if the initialization provoked by this * method fails. * @see Method#invoke(Object, Object...) */ public static <T> T invokeMethod(Object instance, Method method, Class<T> expectedReturnType, boolean setAccessible, Object... args) { if (setAccessible && !method.isAccessible()) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new SetAccessiblePrivilegedAction(method)); } else { method.setAccessible(true); } } try { return expectedReturnType.cast(method.invoke(instance, args)); } catch (InvocationTargetException e) { //re-visit DELTASPIKE-299 before changing this part ExceptionUtils.throwAsRuntimeException(e.getCause()); //won't happen return null; } catch (Exception e) { String customMessage = createCustomMessage(e, method, instance, args); ExceptionUtils.changeAndThrowException(e, customMessage); //won't happen return null; } } private static String createCustomMessage(Exception e, Method method, Object targetObject, Object... arguments) { return e.getMessage() + buildInvokeMethodErrorMessage(method, targetObject, arguments); } /** * Extract the raw type, given a type. * * @param <T> the type * @param type the type to extract the raw type from * @return the raw type, or null if the raw type cannot be determined. */ @SuppressWarnings("unchecked") public static <T> Class<T> getRawType(Type type) { if (type instanceof Class<?>) { return (Class<T>) type; } else if (type instanceof ParameterizedType && ((ParameterizedType) type).getRawType() instanceof Class<?>) { return (Class<T>) ((ParameterizedType) type).getRawType(); } return null; } /** * Check if a class is serializable. * * @param clazz The class to check * @return true if the class implements serializable or is a primitive (needed for type {@link Void} */ public static boolean isSerializable(Class<?> clazz) { return clazz.isPrimitive() || Serializable.class.isAssignableFrom(clazz); } /** * Checks if class is final * * @param clazz The class to check * @return True if final, false otherwise */ public static boolean isFinal(Class<?> clazz) { return Modifier.isFinal(clazz.getModifiers()); } /** * Checks if member is final * * @param member The member to check * @return True if final, false otherwise */ public static boolean isFinal(Member member) { return Modifier.isFinal(member.getModifiers()); } /** * Checks if member is private * * @param member The member to check * @return True if final, false otherwise */ public static boolean isPrivate(Member member) { return Modifier.isPrivate(member.getModifiers()); } public static boolean isPackagePrivate(int mod) { return !(Modifier.isPrivate(mod) || Modifier.isProtected(mod) || Modifier.isPublic(mod)); } /** * Checks if type is static * * @param type Type to check * @return True if static, false otherwise */ public static boolean isStatic(Class<?> type) { return Modifier.isStatic(type.getModifiers()); } /** * Checks if member is static * * @param member Member to check * @return True if static, false otherwise */ public static boolean isStatic(Member member) { return Modifier.isStatic(member.getModifiers()); } public static boolean isTransient(Member member) { return Modifier.isTransient(member.getModifiers()); } /** * Checks if a method is abstract */ public static boolean isAbstract(Method method) { return Modifier.isAbstract(method.getModifiers()); } /** * Gets the actual type arguments of a class * * @param clazz The class to examine * @return The type arguments */ public static Type[] getActualTypeArguments(Class<?> clazz) { if (clazz == null) { throw new IllegalArgumentException("null isn't supported"); } return clazz.getTypeParameters(); } /** * Gets the actual type arguments of a Type * * @param type The type to examine * @return The type arguments */ public static Type[] getActualTypeArguments(Type type) { if (type instanceof Class) { return getActualTypeArguments((Class)type); } throw new IllegalArgumentException((type != null ? type.getClass().getName() : "null") + " isn't supported"); } /** * Checks if raw type is array type * * @param rawType The raw type to check * @return True if array, false otherwise */ public static boolean isArrayType(Class<?> rawType) { return rawType.isArray(); } /** * Checks if type is parameterized type * * @param type The type to check * @return True if parameterized, false otherwise */ public static boolean isParameterizedType(Class<?> type) { return type.getTypeParameters().length > 0; } public static boolean isParameterizedTypeWithWildcard(Class<?> type) { if (isParameterizedType(type)) { return containsWildcards(type.getTypeParameters()); } else { return false; } } private static boolean containsWildcards(Type[] types) { for (Type type : types) { if (type instanceof WildcardType) { return true; } } return false; } public static boolean isPrimitive(Type type) { Class<?> rawType = getRawType(type); return rawType != null && rawType.isPrimitive(); } public static int calculateHashCodeOfAnnotation(Annotation annotation, boolean skipNonbindingMembers) { Class annotationClass = annotation.annotationType(); // the hashCode of an Annotation is calculated solely via the hashCodes // of it's members. If there are no members, it is 0. // thus we first need to get the annotation-class hashCode int hashCode = calculateHashCodeOfType(annotationClass); // and now add the hashCode of all it's Nonbinding members // the following algorithm is defined by the Annotation class definition // see the JavaDoc for Annotation! // we only change it so far that we skip evaluating @Nonbinding members final Method[] members = annotationClass.getDeclaredMethods(); for (Method member : members) { if (skipNonbindingMembers && member.isAnnotationPresent(Nonbinding.class)) { // ignore the non binding continue; } // Member value final Object object = invokeMethod(annotation, member, Object.class, true, EMPTY_OBJECT_ARRAY); final int value; if (object.getClass().isArray()) { Class<?> type = object.getClass().getComponentType(); if (type.isPrimitive()) { if (Long.TYPE == type) { value = Arrays.hashCode((long[]) object); } else if (Integer.TYPE == type) { value = Arrays.hashCode((int[])object); } else if (Short.TYPE == type) { value = Arrays.hashCode((short[])object); } else if (Double.TYPE == type) { value = Arrays.hashCode((double[])object); } else if (Float.TYPE == type) { value = Arrays.hashCode((float[])object); } else if (Boolean.TYPE == type) { value = Arrays.hashCode((boolean[])object); } else if (Byte.TYPE == type) { value = Arrays.hashCode((byte[])object); } else if (Character.TYPE == type) { value = Arrays.hashCode((char[])object); } else { value = 0; } } else { value = Arrays.hashCode((Object[])object); } } else { value = object.hashCode(); } hashCode = 29 * hashCode + value; hashCode = 29 * hashCode + member.getName().hashCode(); } return hashCode; } /** * We need this method as some weird JVMs return 0 as hashCode for classes. * In that case we return the hashCode of the String. */ public static int calculateHashCodeOfType(Type type) { int typeHash = type.hashCode(); if (typeHash == 0 && type instanceof Class) { return ((Class)type).getName().hashCode(); // the type.toString() is always the same: "java.lang.Class@<hexid>" // was: return type.toString().hashCode(); } return typeHash; } }