org.hibernate.search.util.impl.ReflectionHelper Maven / Gradle / Ivy
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or .
*/
package org.hibernate.search.util.impl;
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.LinkedList;
import java.util.List;
import org.hibernate.annotations.common.reflection.XClass;
import org.hibernate.annotations.common.reflection.XMember;
import org.hibernate.annotations.common.reflection.XProperty;
import org.hibernate.search.annotations.Factory;
import org.hibernate.search.util.StringHelper;
import org.hibernate.search.util.logging.impl.Log;
import org.hibernate.search.util.logging.impl.LoggerFactory;
import java.lang.invoke.MethodHandles;
/**
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public abstract class ReflectionHelper {
private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );
private static final Object[] EMPTY_ARRAY = new Object[0];
private ReflectionHelper() {
}
/**
* Get attribute name out of member unless overridden by name
.
*
* @param member XMember
from which to extract the name.
* @param name Override value which will be returned in case it is not empty.
*
* @return attribute name out of member unless overridden by name
.
*/
public static String getAttributeName(XMember member, String name) {
return StringHelper.isNotEmpty( name ) ?
name :
member.getName(); //explicit field name
}
/**
* Always use this method to set accessibility regardless of the visibility.
* @param member the {@link XMember} to check
*/
public static void setAccessible(XMember member) {
try {
// always set accessible to true as it bypass the security model checks
// at execution time and is faster.
member.setAccessible( true );
}
catch (SecurityException se) {
if ( !Modifier.isPublic( member.getModifiers() ) ) {
throw se;
}
}
}
/**
* Always use this method to set accessibility regardless of the visibility.
* @param member the {@link AccessibleObject} to change
*/
public static void setAccessible(AccessibleObject member) {
try {
// always set accessible to true as it bypass the security model checks
// at execution time and is faster.
member.setAccessible( true );
}
catch (SecurityException se) {
if ( !Modifier.isPublic( ( (Member) member ).getModifiers() ) ) {
throw se;
}
}
}
public static Object getMemberValue(Object bean, XMember getter) {
Object value;
try {
//EMPTY_ARRAY used to avoid excessive allocations of empty arrays as
//the invoke method accepts varargs
value = getter.invoke( bean, EMPTY_ARRAY );
}
catch (Exception e) {
throw new IllegalStateException( "Could not get property value", e );
}
return value;
}
/**
* Creates the class hierarchy for a given {@code XClass}.
*
* @param clazz the class for which to create the hierarchy
*
* @return the list of classes in the hierarchy starting at {@code java.lang.Object}
*/
public static List createXClassHierarchy(XClass clazz) {
List hierarchy = new LinkedList<>();
XClass next;
for ( XClass previousClass = clazz; previousClass != null; previousClass = next ) {
next = previousClass.getSuperclass();
if ( next != null ) {
hierarchy.add( 0, previousClass ); // append to head to create a list in top-down iteration order
}
}
return hierarchy;
}
/**
* Checks whether the specified class contains any Search-specific annotations.
*
* This method will return {@code true} if such an annotation is detected
* in the class itself or in one of its superclass, either applied directly on the class,
* on a field, or on a method.
*
* @param mappedClass the {@code XClass} to check for Search annotations
*
* @return Returns {@code true} if the class contains at least one Search annotation, {@code false} otherwise
*/
public static boolean containsSearchAnnotations(XClass mappedClass) {
List hierarchy = createXClassHierarchy( mappedClass );
for ( XClass clazz : hierarchy ) {
if ( containsLocalSearchAnnotation( clazz ) ) {
return true;
}
}
return false;
}
/**
* Checks whether the specified class contains any locally-declared, Search-specific annotations.
*
* This method will return {@code true} if such an annotation is detected
* in the class itself, either applied directly on the class, on a field, or on a method.
*
Only fields and methods declared by the specified class are taken into account:
* inherited fields and methods are ignored.
*
* @param mappedClass the {@code XClass} to check for Search annotations
*
* @return Returns {@code true} if the class contains at least one Search annotation, {@code false} otherwise
*/
private static boolean containsLocalSearchAnnotation(XClass mappedClass) {
// check the type annotations
if ( containsSearchAnnotation( mappedClass.getAnnotations() ) ) {
return true;
}
for ( XProperty method : mappedClass.getDeclaredProperties( XClass.ACCESS_PROPERTY ) ) {
if ( containsSearchAnnotation( method.getAnnotations() ) ) {
return true;
}
}
for ( XProperty field : mappedClass.getDeclaredProperties( XClass.ACCESS_FIELD ) ) {
if ( containsSearchAnnotation( field.getAnnotations() ) ) {
return true;
}
}
return false;
}
/**
* Checks if the annotation is a Search annotation by comparing the package of the annotation.
*
* @param annotation the annotation to check
*
* @return Returns {@code true} if the annotation is a Search annotation, {@code false} otherwise
*/
public static boolean isSearchAnnotation(Annotation annotation) {
Package p = annotation.annotationType().getPackage();
return p != null && "org.hibernate.search.annotations".equals( p.getName() );
}
/**
* Creates an instance of the specified class and returns it. If {@code checkForFactoryAnnotation == true}, the created
* instance is checked for a {@code @Factory} annotated method. If one exists the factory method is invoked and the
* return value returns as expected instances.
*
* @param clazz the class to instantiate
* @param checkForFactoryAnnotation whether to check for {@code @Factory} annotated methods
* @return the created instance
*/
public static Object createInstance(Class> clazz, boolean checkForFactoryAnnotation) {
// create the instance
Object instance = ClassLoaderHelper.untypedInstanceFromClass( clazz, null );
if ( !checkForFactoryAnnotation ) {
return instance;
}
// check for a factory annotation
int numberOfFactoryMethodsFound = 0;
for ( Method method : clazz.getMethods() ) {
if ( method.isAnnotationPresent( Factory.class ) ) {
if ( numberOfFactoryMethodsFound == 1 ) {
throw LOG.multipleFactoryMethodsInClass( clazz.getName() );
}
if ( method.getReturnType() == void.class ) {
throw LOG.factoryMethodsMustReturnAnObject( clazz.getName(), method.getName() );
}
try {
instance = method.invoke( instance );
}
catch (IllegalAccessException e) {
throw LOG.unableToAccessMethod( clazz.getName(), method.getName() );
}
catch (InvocationTargetException e) {
throw LOG.exceptionDuringFactoryMethodExecution( e, clazz.getName(), method.getName() );
}
numberOfFactoryMethodsFound++;
}
}
return instance;
}
/**
* Checks whether the specified type is a floating point type.
*
* @param type the type to check
* @return {@code true} if the specified type is an floating point type or a wrapper thereof. {@code false} otherwise.
*/
public static boolean isFloatingPointType(Class> type) {
return float.class.equals( type )
|| Float.class.equals( type )
|| double.class.equals( type )
|| Double.class.equals( type );
}
/**
* Checks whether the specified type is a integer type.
*
* @param type the type to check
* @return {@code true} if the specified type is an primitive integer type or a wrapper thereof. {@code false} otherwise.
*/
public static boolean isIntegerType(Class> type) {
return byte.class.equals( type )
|| Byte.class.equals( type )
|| short.class.equals( type )
|| Short.class.equals( type )
|| int.class.equals( type )
|| Integer.class.equals( type )
|| long.class.equals( type )
|| Long.class.equals( type );
}
private static boolean containsSearchAnnotation(Annotation[] annotations) {
for ( Annotation annotation : annotations ) {
if ( isSearchAnnotation( annotation ) ) {
return true;
}
}
return false;
}
}