org.batoo.common.reflect.ReflectHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.batoo.common.reflect.ReflectHelper.java

Source

/*
 * Copyright (c) 2012-2013, Batu Alp Ceylan
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */
package org.batoo.common.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
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.TypeVariable;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.batoo.common.BatooException;
import org.batoo.common.log.BLogger;
import org.batoo.common.log.BLoggerFactory;

import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * @author hceylan
 * 
 * @since 2.0.1
 */
@SuppressWarnings("restriction")
public class ReflectHelper {

    private static final BLogger LOG = BLoggerFactory.getLogger(ReflectHelper.class);

    private static final String GET_PREFIX = "get";
    private static final String IS_PREFIX = "is";

    static final sun.misc.Unsafe unsafe;

    static {
        unsafe = ReflectHelper.getUnSafe();
    }

    private static Class<?> checkAndReturn(Class<?> originalType, Method m, Class<?> actualType) {
        if (!originalType.isAssignableFrom(actualType)) {
            ReflectHelper.LOG.warn("Method {0} must return type of {1}", m, originalType.getName());
        }

        return actualType;
    }

    /**
     * Converts the number into number Type
     * 
     * @param value
     *            the number value
     * @param numberType
     *            the number type
     * @return the converted number value
     * 
     * @since 2.0.1
     */
    public static Number convertNumber(Number value, Class<?> numberType) {
        if (value == null) {
            return null;
        }

        if (numberType.isAssignableFrom(value.getClass())) {
            return value;
        }

        if ((numberType == Integer.class) || (numberType == Integer.TYPE)) {
            return value.intValue();
        }

        if ((numberType == Long.class) || (numberType == Long.TYPE)) {
            return value.longValue();
        }

        if ((numberType == Short.class) || (numberType == Short.TYPE)) {
            return value.shortValue();
        }

        if ((numberType == Byte.class) || (numberType == Byte.TYPE)) {
            return value.byteValue();
        }

        if ((numberType == Float.class) || (numberType == Float.TYPE)) {
            return value.floatValue();
        }

        if ((numberType == Double.class) || (numberType == Double.TYPE)) {
            return value.doubleValue();
        }

        if (numberType == BigDecimal.class) {
            return BigDecimal.valueOf(value.doubleValue());
        }

        if (numberType == BigInteger.class) {
            return BigInteger.valueOf(value.longValue());
        }

        throw new IllegalArgumentException(numberType + " not supported");
    }

    /**
     * Creates and returns a fast constructor accessor.
     * 
     * @param constructor
     *            the original constructor
     * @return the constructor accessor
     * 
     * @since 2.0.1
     */
    public static ConstructorAccessor createConstructor(Constructor<?> constructor) {
        try {
            Class.forName("sun.reflect.ConstructorAccessor");
        } catch (final ClassNotFoundException e) {
            return new SimpleConstructorAccessor(constructor);
        }

        return ReflectHelper.createConstructorImpl(constructor);
    }

    private static ConstructorAccessor createConstructorImpl(Constructor<?> constructor) {
        try {
            final Class<?> magClass = Class.forName("sun.reflect.MethodAccessorGenerator");
            final Constructor<?> c = magClass.getDeclaredConstructors()[0];
            final Method generateMethod = magClass.getMethod("generateConstructor", Class.class, Class[].class,
                    Class[].class, Integer.TYPE);

            ReflectHelper.setAccessible(c, true);
            ReflectHelper.setAccessible(generateMethod, true);

            try {
                final Object mag = c.newInstance();

                return new SunConstructorAccessor(
                        generateMethod.invoke(mag, constructor.getDeclaringClass(), constructor.getParameterTypes(),
                                constructor.getExceptionTypes(), constructor.getModifiers()));
            } finally {
                ReflectHelper.setAccessible(c, false);
                ReflectHelper.setAccessible(generateMethod, false);
            }
        } catch (final Exception e) {
            throw new RuntimeException("Constructor generation failed", e);
        }
    }

    /**
     * Returns the qualified name for the member.
     * 
     * @param member
     *            the member to qualify
     * @return the qualified name for the member
     * 
     * @since 2.0.1
     */
    public static String createMemberName(Member member) {
        return member.getDeclaringClass().getName() + "." + member.getName();
    }

    /**
     * Returns the accessor for the member
     * 
     * @param javaMember
     *            the java member
     * @return the accessor
     * 
     * @since 2.0.1
     */
    public static AbstractAccessor getAccessor(Member javaMember) {
        if (javaMember instanceof Field) {
            return ReflectHelper.unsafe != null ? new UnsafeFieldAccessor((Field) javaMember)
                    : new FieldAccessor((Field) javaMember);
        } else {
            String name = javaMember.getName().startsWith(ReflectHelper.IS_PREFIX)
                    ? javaMember.getName().substring(2)
                    : javaMember.getName().substring(3);

            name = StringUtils.uncapitalize(name);

            final Class<?> declaringClass = javaMember.getDeclaringClass();
            final PropertyDescriptor[] properties = ReflectHelper.getProperties(declaringClass);
            for (final PropertyDescriptor descriptor : properties) {
                if (descriptor.getName().equals(name)) {
                    return new PropertyAccessor(descriptor);
                }
            }

            Field field;
            try {
                field = javaMember.getDeclaringClass().getDeclaredField(StringUtils.uncapitalize(name));
                return new FieldAccessor(field);
            } catch (final Exception e) {
                throw new BatooException("Cannot find instance variable field "
                        + javaMember.getDeclaringClass().getName() + "." + name);
            }
        }
    }

    /**
     * Returns the actual type of the attribute, resolving generic types if necessary.
     * 
     * @param clazz
     *            the actual class
     * @param attributeName
     *            the name of the attribute
     * @param originalType
     *            th original type of the attribute
     * @return the actual type of the attribute
     * @param <T>
     *            the xpected type
     * 
     * @since 2.0.1
     */
    @SuppressWarnings("unchecked")
    public static <T> Class<T> getActualType(Class<?> clazz, String attributeName, Class<?> originalType) {
        if (originalType.isPrimitive()) {
            return (Class<T>) originalType;
        }

        final String methodName = "get" + StringUtils.capitalize(attributeName);

        final Class<T> actualType = (Class<T>) ReflectHelper.getTypeImpl(clazz, originalType, methodName,
                Lists.<Type>newArrayList());

        return actualType;
    }

    /**
     * Returns the annotation instance if the <code>member</code> has the <code>annotation</code>.
     * 
     * @param member
     *            the member
     * @param annotation
     *            the type of the annotation instance to return
     * @return the <code>annotation</code> instance if the <code>member</code> has the annotation or null
     * 
     * @param <A>
     *            the class of annotation
     * 
     * @since 2.0.1
     */
    public static <A extends Annotation> A getAnnotation(final Member member, final Class<A> annotation) {
        if (member instanceof Field) {
            return ((Field) member).getAnnotation(annotation);
        }

        if (member instanceof Method) {
            return ((Method) member).getAnnotation(annotation);
        }

        throw new IllegalArgumentException("Member is neither field nor method: " + member);
    }

    /**
     * Returns the actual generic type of a class's type parameter of the <code>member</code>.
     * <p>
     * if the <code>member</code> is a field then field's generic types are checked. Otherwise the <code>member</code> is treated a a method
     * and its return type's is checked.
     * <p>
     * 
     * @param member
     *            the member
     * @param index
     *            the index number of the generic parameter
     * @return the class of generic type
     * 
     * @param <X>
     *            the type of the class
     * 
     * @since 2.0.1
     */
    @SuppressWarnings("unchecked")
    public static <X> Class<X> getGenericType(Member member, int index) {
        Type type;

        if (member instanceof Field) {
            final Field field = (Field) member;
            type = field.getGenericType();

        } else {
            final Method method = (Method) member;
            type = method.getGenericReturnType();
        }

        // if not a parameterized type return null
        if (!(type instanceof ParameterizedType)) {
            return null;
        }

        final ParameterizedType parameterizedType = (ParameterizedType) type;

        final Type[] types = parameterizedType.getActualTypeArguments();

        return (Class<X>) ((types != null) && (index < types.length) ? types[index] : null);
    }

    /**
     * Returns the type class of the <code>member</code>.
     * 
     * @param member
     *            the member
     * @return the <code>member</code>'s type as java class
     * 
     * @since 2.0.1
     */
    public static Class<?> getMemberType(Member member) {
        if (member instanceof Field) {
            return ((Field) member).getType();
        }

        return ((Method) member).getReturnType();
    }

    /**
     * Returns the property descriptors for the class.
     * 
     * @param clazz
     *            the class
     * @return the property descriptors
     * 
     * @since 2.0.1
     */
    public static PropertyDescriptor[] getProperties(Class<?> clazz) {
        final List<PropertyDescriptor> properties = Lists.newArrayList();

        final Method[] methodList = clazz.getDeclaredMethods();

        // check each method.
        for (final Method method : methodList) {
            if (method == null) {
                continue;
            }

            // skip static and private methods.
            final int mods = method.getModifiers();
            if (Modifier.isStatic(mods) || !Modifier.isPublic(mods) || method.isBridge() || method.isSynthetic()) {
                continue;
            }

            final String name = method.getName();

            if (method.getParameterTypes().length == 0) {
                if (name.startsWith(ReflectHelper.GET_PREFIX)) {
                    properties.add(new PropertyDescriptor(clazz, name.substring(3), method));
                } else if ((method.getReturnType() == boolean.class) && name.startsWith(ReflectHelper.IS_PREFIX)) {
                    properties.add(new PropertyDescriptor(clazz, name.substring(2), method));
                }
            }
        }

        return properties.toArray(new PropertyDescriptor[properties.size()]);
    }

    private static Class<?> getTypeImpl(Class<?> clazz, Class<?> originalType, String methodName,
            List<Type> arguments) {
        final Map<TypeVariable<?>, Type> typeMap = Maps.newHashMap();

        if (arguments.size() > 0) {
            final TypeVariable<?>[] typeParameters = clazz.getTypeParameters();
            for (int i = 0; i < typeParameters.length; i++) {
                typeMap.put(typeParameters[i], arguments.get(i));
            }
        }

        try {
            final Method m = clazz.getDeclaredMethod(methodName);

            final Type type = typeMap.get(m.getGenericReturnType());
            if (type != null) {
                return ReflectHelper.checkAndReturn(originalType, m, (Class<?>) type);
            }

            return ReflectHelper.checkAndReturn(originalType, m, m.getReturnType());
        } catch (final NoSuchMethodException e) {
            final List<Type> typeArguments = Lists.newArrayList();

            if (clazz.getGenericSuperclass() instanceof ParameterizedType) {
                final ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();

                for (final Type typeArgument : parameterizedType.getActualTypeArguments()) {
                    if (typeArgument instanceof TypeVariable) {
                        typeArguments.add(typeMap.get(typeArgument));
                    } else {
                        typeArguments.add(typeArgument);
                    }
                }
            }

            final Class<?> superclass = clazz.getSuperclass();

            if (superclass == Object.class) {
                return originalType;
            }

            return ReflectHelper.getTypeImpl(superclass, originalType, methodName, typeArguments);
        }
    }

    private static sun.misc.Unsafe getUnSafe() {
        try {
            ReflectHelper.LOG.debug("Loading direct access library....");

            final Field field = sun.misc.Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            try {
                return (sun.misc.Unsafe) field.get(null);
            } finally {
                ReflectHelper.LOG.debug("Direct access library loaded successfully");
            }
        } catch (final NoClassDefFoundError e) {
        } catch (final Exception e) {
        }

        ReflectHelper.LOG.warn(
                "Direct access library cannot be loaded! Direct access library only adds some performance, but not necessary to operate. It is safe to ignore...");

        return null;
    }

    /**
     * Returns if the <code>type</code> is a collection type.
     * <p>
     * Note that this method uses equality, not assignability to test.
     * 
     * @param type
     *            the type to check if it is collection
     * @return true if the <code>type</code> is a collection type, false otherwise
     * 
     * @since 2.0.1
     */
    public static boolean isCollection(Class<?> type) {
        return (List.class == type) || (Collection.class == type) || (Set.class == type) || (Map.class == type);
    }

    /**
     * Sets the member's accessibility status.
     * 
     * @param member
     *            the member of which to set accessibility status
     * @param accessible
     *            true to set accessible, false to make it not accessible
     * 
     * @since 2.0.1
     */
    public static void setAccessible(final Member member, final boolean accessible) {
        AccessController.doPrivileged(new PrivilegedAction<Void>() {

            @Override
            public Void run() {
                if (member instanceof Field) {
                    ((Field) member).setAccessible(accessible);
                }

                else if (member instanceof Method) {
                    ((Method) member).setAccessible(accessible);
                }

                else {
                    ((Constructor<?>) member).setAccessible(accessible);
                }

                return null;
            }
        });
    }

    /**
     * Logs warnings for annotations that were ignored.
     * 
     * @param logger
     *            the logger to log the warnings.
     * @param member
     *            the member
     * @param annotations
     *            the set of annotations allowed
     * 
     * @since 2.0.1
     */
    public static void warnAnnotations(BLogger logger, Member member,
            final Set<Class<? extends Annotation>> annotations) {
        final Set<Annotation> existing;

        if (member instanceof Field) {
            existing = Sets.newHashSet(((Field) member).getAnnotations());
        } else {
            existing = Sets.newHashSet(((Method) member).getAnnotations());
        }

        final Set<Annotation> filtered = Sets.filter(existing, new Predicate<Annotation>() {

            @Override
            public boolean apply(Annotation input) {
                for (final Annotation annotation : existing) {
                    if (annotations.contains(annotation.annotationType())) {
                        return false;
                    }
                }

                return true;
            }
        });

        if (filtered.size() > 0) {
            logger.warn("Following annotations on {0} were ignored: {1}", ReflectHelper.createMemberName(member),
                    Joiner.on(", ").join(filtered));
        }
    }
}