All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hibernate.validator.internal.util.ReflectionHelper Maven / Gradle / Ivy

/*
* JBoss, Home of Professional Open Source
* Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* Licensed 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.hibernate.validator.internal.util;

import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.fasterxml.classmate.Filter;
import com.fasterxml.classmate.MemberResolver;
import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.ResolvedTypeWithMembers;
import com.fasterxml.classmate.TypeResolver;
import com.fasterxml.classmate.members.RawMethod;
import com.fasterxml.classmate.members.ResolvedMethod;

import org.hibernate.validator.internal.metadata.raw.ExecutableElement;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.internal.util.privilegedactions.ConstructorInstance;
import org.hibernate.validator.internal.util.privilegedactions.GetAnnotationParameter;
import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader;
import org.hibernate.validator.internal.util.privilegedactions.GetConstructor;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredConstructors;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredField;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredFields;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethod;
import org.hibernate.validator.internal.util.privilegedactions.GetDeclaredMethods;
import org.hibernate.validator.internal.util.privilegedactions.GetMethod;
import org.hibernate.validator.internal.util.privilegedactions.GetMethodFromPropertyName;
import org.hibernate.validator.internal.util.privilegedactions.GetMethods;
import org.hibernate.validator.internal.util.privilegedactions.LoadClass;
import org.hibernate.validator.internal.util.privilegedactions.NewInstance;
import org.hibernate.validator.internal.util.privilegedactions.SetAccessibility;

import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
import static org.hibernate.validator.internal.util.logging.Messages.MESSAGES;

/**
 * Some reflection utility methods. Where necessary calls will be performed as {@code PrivilegedAction} which is necessary
 * for situations where a security manager is in place.
 *
 * @author Hardy Ferentschik
 * @author Gunnar Morling
 * @author Kevin Pollet  (C) 2011 SERLI
 */
public final class ReflectionHelper {

	private static final String PACKAGE_SEPARATOR = ".";
	private static final String ARRAY_CLASS_NAME_PREFIX = "[L";
	private static final String ARRAY_CLASS_NAME_SUFFIX = ";";

	private static final String PROPERTY_ACCESSOR_PREFIX_GET = "get";
	private static final String PROPERTY_ACCESSOR_PREFIX_IS = "is";
	private static final String PROPERTY_ACCESSOR_PREFIX_HAS = "has";
	private static final String[] PROPERTY_ACCESSOR_PREFIXES = {
			PROPERTY_ACCESSOR_PREFIX_GET,
			PROPERTY_ACCESSOR_PREFIX_IS,
			PROPERTY_ACCESSOR_PREFIX_HAS
	};

	private static final Log log = LoggerFactory.make();

	/**
	 * Used for resolving type parameters. Thread-safe.
	 */
	private static final TypeResolver typeResolver = new TypeResolver();

	private static final Map, Class> PRIMITIVE_TO_WRAPPER_TYPES;

	static {
		Map, Class> temp = newHashMap( 9 );

		temp.put( boolean.class, Boolean.class );
		temp.put( char.class, Character.class );
		temp.put( double.class, Double.class );
		temp.put( float.class, Float.class );
		temp.put( long.class, Long.class );
		temp.put( int.class, Integer.class );
		temp.put( short.class, Short.class );
		temp.put( byte.class, Byte.class );
		temp.put( Void.TYPE, Void.TYPE );

		PRIMITIVE_TO_WRAPPER_TYPES = Collections.unmodifiableMap( temp );
	}

	private static final Map, Class> WRAPPER_TO_PRIMITVES_TYPES;

	static {
		Map, Class> temp = newHashMap( 9 );

		temp.put( Boolean.class, boolean.class );
		temp.put( Character.class, char.class );
		temp.put( Double.class, double.class );
		temp.put( Float.class, float.class );
		temp.put( Long.class, long.class );
		temp.put( Integer.class, int.class );
		temp.put( Short.class, short.class );
		temp.put( Byte.class, byte.class );
		temp.put( Void.TYPE, Void.TYPE );

		WRAPPER_TO_PRIMITVES_TYPES = Collections.unmodifiableMap( temp );
	}

	/**
	 * Private constructor in order to avoid instantiation.
	 */
	private ReflectionHelper() {
	}

	public static ClassLoader getClassLoaderFromContext() {
		return run( GetClassLoader.fromContext() );
	}

	public static ClassLoader getClassLoaderFromClass(Class clazz) {
		return run( GetClassLoader.fromClass( clazz ) );
	}

	public static Class loadClass(String className, Class caller) {
		return run( LoadClass.action( className, caller ) );
	}

	public static Class loadClass(String className, String defaultPackage) {
		return loadClass( className, defaultPackage, ReflectionHelper.class );
	}

	public static Class loadClass(String className, String defaultPackage, Class caller) {
		StringBuilder fullyQualifiedClass = new StringBuilder();
		String tmpClassName = className;
		if ( isArrayClassName( className ) ) {
			fullyQualifiedClass.append( ARRAY_CLASS_NAME_PREFIX );
			tmpClassName = getArrayElementClassName( className );
		}

		if ( isQualifiedClass( tmpClassName ) ) {
			fullyQualifiedClass.append( tmpClassName );
		}
		else {
			fullyQualifiedClass.append( defaultPackage );
			fullyQualifiedClass.append( PACKAGE_SEPARATOR );
			fullyQualifiedClass.append( tmpClassName );
		}

		if ( isArrayClassName( className ) ) {
			fullyQualifiedClass.append( ARRAY_CLASS_NAME_SUFFIX );
		}

		return loadClass( fullyQualifiedClass.toString(), caller );
	}

	private static boolean isArrayClassName(String className) {
		return className.startsWith( ARRAY_CLASS_NAME_PREFIX ) && className.endsWith( ARRAY_CLASS_NAME_SUFFIX );
	}

	private static String getArrayElementClassName(String className) {
		return className.substring( 2, className.length() - 1 );
	}

	private static boolean isQualifiedClass(String clazz) {
		return clazz.contains( PACKAGE_SEPARATOR );
	}

	public static  Constructor getConstructor(Class clazz, Class... params) {
		return run( GetConstructor.action( clazz, params ) );
	}

	public static  T newInstance(Class clazz, String message) {
		return run( NewInstance.action( clazz, message ) );
	}

	public static  T newConstructorInstance(Constructor constructor, Object... initArgs) {
		return run( ConstructorInstance.action( constructor, initArgs ) );
	}

	public static  T getAnnotationParameter(Annotation annotation, String parameterName, Class type) {
		return run( GetAnnotationParameter.action( annotation, parameterName, type ) );
	}

	/**
	 * Returns the JavaBeans property name of the given member.
	 * 

* For fields, the field name will be returned. For getter methods, the * decapitalized property name will be returned, with the "get", "is" or "has" * prefix stripped off. Getter methods are methods *

*
    *
  • whose name start with "get" and who have a return type but no parameter * or
  • *
  • whose name starts with "is" and who have no parameter and return * {@code boolean} or
  • *
  • whose name starts with "has" and who have no parameter and return * {@code boolean} (HV-specific, not mandated by JavaBeans spec).
  • *
* * @param member The member for which to get the property name. * * @return The property name for the given member or {@code null} if the * member is neither a field nor a getter method according to the * JavaBeans standard. */ public static String getPropertyName(Member member) { String name = null; if ( member instanceof Field ) { name = member.getName(); } if ( member instanceof Method ) { String methodName = member.getName(); for ( String prefix : PROPERTY_ACCESSOR_PREFIXES ) { if ( methodName.startsWith( prefix ) ) { name = Introspector.decapitalize( methodName.substring( prefix.length() ) ); } } } return name; } /** * Checks whether the given method is a valid JavaBeans getter method, which * is the case if *
    *
  • its name starts with "get" and it has a return type but no parameter or
  • *
  • its name starts with "is", it has no parameter and is returning * {@code boolean} or
  • *
  • its name starts with "has", it has no parameter and is returning * {@code boolean} (HV-specific, not mandated by JavaBeans spec).
  • *
* * @param method The method of interest. * * @return {@code true}, if the given method is a JavaBeans getter method, * {@code false} otherwise. */ public static boolean isGetterMethod(Method method) { if ( method.getParameterTypes().length != 0 ) { return false; } String methodName = method.getName(); // get() if ( methodName.startsWith( PROPERTY_ACCESSOR_PREFIX_GET ) && method.getReturnType() != void.class ) { return true; } //boolean is() else if ( methodName.startsWith( PROPERTY_ACCESSOR_PREFIX_IS ) && method.getReturnType() == boolean.class ) { return true; } //boolean has() else if ( methodName.startsWith( PROPERTY_ACCESSOR_PREFIX_HAS ) && method.getReturnType() == boolean.class ) { return true; } return false; } /** * Returns the member with the given name and type. * * @param clazz The class from which to retrieve the member. Cannot be {@code null}. * @param property The property name without "is", "get" or "has". Cannot be {@code null} or empty. * @param elementType The element type. Either {@code ElementType.FIELD} or {@code ElementType METHOD}. * * @return the member which matching the name and type or {@code null} if no such member exists. */ public static Member getMember(Class clazz, String property, ElementType elementType) { Contracts.assertNotNull( clazz, MESSAGES.classCannotBeNull() ); if ( property == null || property.length() == 0 ) { throw log.getPropertyNameCannotBeNullOrEmptyException(); } if ( !( ElementType.FIELD.equals( elementType ) || ElementType.METHOD.equals( elementType ) ) ) { throw log.getElementTypeHasToBeFieldOrMethodException(); } Member member = null; if ( ElementType.FIELD.equals( elementType ) ) { member = run( GetDeclaredField.action( clazz, property ) ); } else { String methodName = property.substring( 0, 1 ).toUpperCase() + property.substring( 1 ); for ( String prefix : PROPERTY_ACCESSOR_PREFIXES ) { member = run( GetMethod.action( clazz, prefix + methodName ) ); if ( member != null ) { break; } } } return member; } /** * @param member The Member instance for which to retrieve the type. * * @return Returns the Type of the given Field or Method. * * @throws IllegalArgumentException in case member is not a Field or Method. */ public static Type typeOf(Member member) { Type type; if ( member instanceof Field ) { type = ( (Field) member ).getGenericType(); } else if ( member instanceof Method ) { type = ( (Method) member ).getGenericReturnType(); } else if ( member instanceof Constructor ) { type = member.getDeclaringClass(); } //TODO HV-571 change log method name else { throw log.getMemberIsNeitherAFieldNorAMethodException( member ); } if ( type instanceof TypeVariable ) { type = TypeHelper.getErasedType( type ); } return type; } /** * Returns the type of the parameter of the given method with the given parameter index. * * @param executable The executable of interest. * @param parameterIndex The index of the parameter for which the type should be returned. * * @return The erased type. */ public static Type typeOf(ExecutableElement executable, int parameterIndex) { Type type = executable.getGenericParameterTypes()[parameterIndex]; if ( type instanceof TypeVariable ) { type = TypeHelper.getErasedType( type ); } return type; } public static Object getValue(Member member, Object object) { if ( member instanceof Method ) { return getValue( (Method) member, object ); } else if ( member instanceof Field ) { return getValue( (Field) member, object ); } return null; } public static Object getValue(Field field, Object object) { try { return field.get( object ); } catch ( IllegalAccessException e ) { throw log.getUnableToAccessMemberException( field.getName(), e ); } } public static Object getValue(Method method, Object object) { try { return method.invoke( object ); } catch ( IllegalAccessException e ) { throw log.getUnableToAccessMemberException( method.getName(), e ); } catch ( InvocationTargetException e ) { throw log.getUnableToAccessMemberException( method.getName(), e ); } } public static void setAccessibility(Member member) { run( SetAccessibility.action( member ) ); } /** * Determines the type of elements of an Iterable, array or the value of a Map. * * @param type the type to inspect * * @return Returns the type of elements of an Iterable, array or the value of a Map. * null is returned in case the type is not indexable (in the context of JSR 303). */ public static Type getIndexedType(Type type) { Type indexedType = null; if ( isIterable( type ) && type instanceof ParameterizedType ) { ParameterizedType paramType = (ParameterizedType) type; indexedType = paramType.getActualTypeArguments()[0]; } else if ( isMap( type ) && type instanceof ParameterizedType ) { ParameterizedType paramType = (ParameterizedType) type; indexedType = paramType.getActualTypeArguments()[1]; } else if ( TypeHelper.isArray( type ) ) { indexedType = TypeHelper.getComponentType( type ); } return indexedType; } /** * @param type the type to check. * * @return Returns true if type is a iterable type, false otherwise. */ public static boolean isIterable(Type type) { if ( type instanceof Class && Iterable.class.isAssignableFrom( (Class) type ) ) { return true; } if ( type instanceof ParameterizedType ) { return isIterable( ( (ParameterizedType) type ).getRawType() ); } if ( type instanceof WildcardType ) { Type[] upperBounds = ( (WildcardType) type ).getUpperBounds(); return upperBounds.length != 0 && isIterable( upperBounds[0] ); } return false; } /** * @param type the type to check. * * @return Returns true if type is implementing Map, false otherwise. */ public static boolean isMap(Type type) { if ( type instanceof Class && Map.class.isAssignableFrom( (Class) type ) ) { return true; } if ( type instanceof ParameterizedType ) { return isMap( ( (ParameterizedType) type ).getRawType() ); } if ( type instanceof WildcardType ) { Type[] upperBounds = ( (WildcardType) type ).getUpperBounds(); return upperBounds.length != 0 && isMap( upperBounds[0] ); } return false; } /** * @param type the type to check. * * @return Returns true if type is implementing List, false otherwise. */ public static boolean isList(Type type) { if ( type instanceof Class && List.class.isAssignableFrom( (Class) type ) ) { return true; } if ( type instanceof ParameterizedType ) { return isList( ( (ParameterizedType) type ).getRawType() ); } if ( type instanceof WildcardType ) { Type[] upperBounds = ( (WildcardType) type ).getUpperBounds(); return upperBounds.length != 0 && isList( upperBounds[0] ); } return false; } /** * Tries to retrieve the indexed value from the specified object. * * @param value The object from which to retrieve the indexed value. The object has to be non null and * either a collection or array. * @param index The index. The index does not have to be numerical. value could also be a map in which * case the index could also be a string key. * * @return The indexed value or null if value is null or not a collection or array. * null is also returned in case the index does not exist. */ public static Object getIndexedValue(Object value, Integer index) { if ( value == null ) { return null; } Iterator iter; Type type = value.getClass(); if ( isIterable( type ) ) { iter = ( (Iterable) value ).iterator(); } else if ( TypeHelper.isArray( type ) ) { List arrayList = Arrays.asList( value ); iter = arrayList.iterator(); } else { return null; } int i = 0; Object o; while ( iter.hasNext() ) { o = iter.next(); if ( i == index ) { return o; } i++; } return null; } /** * Tries to retrieve the mapped value from the specified object. * * @param value The object from which to retrieve the mapped value. The object has to be non {@code null} and * must implement the @{code Map} interface. * @param key The map key. index. * * @return The mapped value or {@code null} if {@code value} is {@code null} or not implementing @{code Map}. */ public static Object getMappedValue(Object value, Object key) { if ( !( value instanceof Map ) ) { return null; } Map map = (Map) value; //noinspection SuspiciousMethodCalls return map.get( key ); } /** * Returns the declared field with the specified name or {@code null} if it does not exist. * * @param clazz The class to check. * @param fieldName The field name. * * @return Returns the declared field with the specified name or {@code null} if it does not exist. */ public static Field getDeclaredField(Class clazz, String fieldName) { return run( GetDeclaredField.action( clazz, fieldName ) ); } /** * Returns the fields of the specified class. * * @param clazz The class for which to retrieve the fields. * * @return Returns the fields for this class. */ public static Field[] getDeclaredFields(Class clazz) { return run( GetDeclaredFields.action( clazz ) ); } /** * Returns the method with the specified property name or {@code null} if it does not exist. This method will * prepend 'is' and 'get' to the property name and capitalize the first letter. * * @param clazz The class to check. * @param methodName The property name. * * @return Returns the method with the specified property or {@code null} if it does not exist. */ public static Method getMethodFromPropertyName(Class clazz, String methodName) { return run( GetMethodFromPropertyName.action( clazz, methodName ) ); } /** * Returns the method with the specified name or {@code null} if it does not exist. * * @param clazz The class to check. * @param methodName The method name. * * @return Returns the method with the specified property or {@code null} if it does not exist. */ public static Method getMethod(Class clazz, String methodName) { return run( GetMethod.action( clazz, methodName ) ); } /** * Returns the declared method with the specified name and parameter types or {@code null} if * it does not exist. * * @param clazz The class to check. * @param methodName The method name. * @param parameterTypes The method parameter types. * * @return Returns the declared method with the specified name or {@code null} if it does not exist. */ public static Method getDeclaredMethod(Class clazz, String methodName, Class... parameterTypes) { return run( GetDeclaredMethod.action( clazz, methodName, parameterTypes ) ); } /** * Returns the declared methods of the specified class. * * @param clazz The class for which to retrieve the methods. * * @return Returns the declared methods for this class. */ public static Method[] getDeclaredMethods(Class clazz) { return run( GetDeclaredMethods.action( clazz ) ); } /** * Returns the methods of the specified class (include inherited methods). * * @param clazz The class for which to retrieve the methods. * * @return Returns the methods for this class. */ public static Method[] getMethods(Class clazz) { return run( GetMethods.action( clazz ) ); } /** * Returns the declared constructors of the specified class. * * @param clazz The class for which to retrieve the constructors. * * @return Returns the declared constructors for this class. */ public static Constructor[] getDeclaredConstructors(Class clazz) { return run( GetDeclaredConstructors.action( clazz ) ); } /** * Executes the given privileged action either directly or with privileges * enabled, depending on whether a security manager is around or not. * * @param The return type of the privileged action to run. * @param action The action to run. * * @return The result of the privileged action's execution. */ private static T run(PrivilegedAction action) { return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run(); } /** * Returns the auto-boxed type of a primitive type. * * @param primitiveType the primitive type * * @return the auto-boxed type of a primitive type. In case {@link Void} is * passed (which is considered as primitive type by * {@link Class#isPrimitive()}), {@link Void} will be returned. * * @throws IllegalArgumentException in case the parameter {@code primitiveType} does not * represent a primitive type. */ public static Class boxedType(Class primitiveType) { Class wrapperType = PRIMITIVE_TO_WRAPPER_TYPES.get( primitiveType ); if ( wrapperType == null ) { throw log.getHasToBeAPrimitiveTypeException( primitiveType.getClass() ); } return wrapperType; } /** * Returns the primitive type for a boxed type. * * @param type the boxed type * * @return the primitive type for a auto-boxed type. In case {@link Void} is * passed (which is considered as primitive type by * {@link Class#isPrimitive()}), {@link Void} will be returned. * * @throws IllegalArgumentException in case the parameter {@code primitiveType} does not * represent a primitive type. */ public static Class unBoxedType(Class type) { Class wrapperType = WRAPPER_TO_PRIMITVES_TYPES.get( type ); if ( wrapperType == null ) { throw log.getHasToBeABoxedTypeException( type.getClass() ); } return wrapperType; } /** * Checks, whether {@code subTypeMethod} overrides {@code superTypeMethod}. * * @param subTypeMethod The sub type method (cannot be {@code null}). * @param superTypeMethod The super type method (cannot be {@code null}). * * @return Returns {@code true} if {@code subTypeMethod} overrides {@code superTypeMethod}, * {@code false} otherwise. */ public static boolean overrides(Method subTypeMethod, Method superTypeMethod) { Contracts.assertValueNotNull( subTypeMethod, "subTypeMethod" ); Contracts.assertValueNotNull( superTypeMethod, "superTypeMethod" ); if ( subTypeMethod.equals( superTypeMethod ) ) { return false; } if ( !subTypeMethod.getName().equals( superTypeMethod.getName() ) ) { return false; } if ( subTypeMethod.getParameterTypes().length != superTypeMethod.getParameterTypes().length ) { return false; } if ( !superTypeMethod.getDeclaringClass().isAssignableFrom( subTypeMethod.getDeclaringClass() ) ) { return false; } return parametersResolveToSameTypes( subTypeMethod, superTypeMethod ); } private static boolean parametersResolveToSameTypes(Method subTypeMethod, Method superTypeMethod) { if ( subTypeMethod.getParameterTypes().length == 0 ) { return true; } ResolvedType resolvedSubType = typeResolver.resolve( subTypeMethod.getDeclaringClass() ); MemberResolver memberResolver = new MemberResolver( typeResolver ); memberResolver.setMethodFilter( new SimpleMethodFilter( subTypeMethod, superTypeMethod ) ); ResolvedTypeWithMembers typeWithMembers = memberResolver.resolve( resolvedSubType, null, null ); ResolvedMethod[] resolvedMethods = typeWithMembers.getMemberMethods(); // The ClassMate doc says that overridden methods are flattened to one // resolved method. But that is the case only for methods without any // generic parameters. if ( resolvedMethods.length == 1 ) { return true; } // For methods with generic parameters I have to compare the argument // types (which are resolved) of the two filtered member methods. for ( int i = 0; i < resolvedMethods[0].getArgumentCount(); i++ ) { if ( !resolvedMethods[0].getArgumentType( i ) .equals( resolvedMethods[1].getArgumentType( i ) ) ) { return false; } } return true; } /** * A filter implementation filtering methods matching given methods. * * @author Gunnar Morling */ private static class SimpleMethodFilter implements Filter { private final Method method1; private final Method method2; private SimpleMethodFilter(Method method1, Method method2) { this.method1 = method1; this.method2 = method2; } @Override public boolean include(RawMethod element) { return element.getRawMember().equals( method1 ) || element.getRawMember() .equals( method2 ); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy