com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.github.helenusdriver.commons.lang3.reflect.ReflectionUtils.java

Source

/*
 * Copyright (C) 2015-2015 The Helenus Driver Project Authors.
 *
 * 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 com.github.helenusdriver.commons.lang3.reflect;

import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.lang.reflect.AnnotatedElement;
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.io.File;
import java.io.IOException;

import java.net.JarURLConnection;
import java.net.URL;

import java.security.AccessController;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import com.github.helenusdriver.annotation.Keyable;

/**
 * The <code>ReflectionUtils</code> class provides reflection utilities.
 *
 * @copyright 2015-2015 The Helenus Driver Project Authors
 *
 * @author  The Helenus Driver Project Authors
 * @version 1 - Jan 15, 2015 - paouelle - Creation
 *
 * @since 1.0
 */
public class ReflectionUtils {
    /**
     * Reflection factory for obtaining constructors used to instantiate blank
     * object whether or not a default constructor was defined by the user.
     *
     * @author paouelle
     */
    @SuppressWarnings("all")
    private static final sun.reflect.ReflectionFactory reflFactory = (sun.reflect.ReflectionFactory) AccessController
            .doPrivileged(new sun.reflect.ReflectionFactory.GetReflectionFactoryAction());

    /**
     * Finds the first class from a given class' hierarchy that is annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <T> the type of the class to start searching from
     *
     * @param  clazz the class from which to search
     * @param  annotationClass the annotation to search for
     * @return the first class in <code>clazz</code>'s hierarchy annotated with
     *         the specified annotation or <code>null</code> if none is found
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotationClass</code> is <code>null</code>
     */
    public static <T> Class<? super T> findFirstClassAnnotatedWith(Class<T> clazz,
            Class<? extends Annotation> annotationClass) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(annotationClass, "invalid null annotation class");
        Class<? super T> c = clazz;

        do {
            if (c.getDeclaredAnnotationsByType(annotationClass).length > 0) {
                return c;
            }
            c = c.getSuperclass();
        } while (c != null);
        return null;
    }

    /**
     * Finds the first class from a given class' hierarchy that is not annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <T> the type of the class t start searching from
     *
     * @param  clazz the class from which to search
     * @param  annotationClass the annotation to search for
     * @return the non-<code>null</code> first class in <code>clazz</code>'s
     *         hierarchy not annotated with the specified annotation
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotationClass</code> is <code>null</code>
     */
    public static <T> Class<? super T> findFirstClassNotAnnotatedWith(Class<T> clazz,
            Class<? extends Annotation> annotationClass) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(annotationClass, "invalid null annotation class");
        Class<? super T> c = clazz;

        while (c.getDeclaredAnnotationsByType(annotationClass).length > 0) {
            c = c.getSuperclass();
        }
        return c;
    }

    /**
     * Gets the serialization constructor for the given class based on the given
     * base class. The specified base class is expected to have a default constructor
     * to use for initializing the hierarchy from that point on. The constructor
     * can have any visibility (i.e. public, protected, package private, or private).
     * <p>
     * <i>Note:</i> The returned constructor results in having a proper object of
     * the given class instantiated without calling any of the constructors from
     * its class or super classes. The fields of all classes in the hierarchy will
     * not be initialized; thus set using default Java initialization (i.e.
     * <code>null</code>, 0, 0L, false, ...). This is how Java creates new
     * serialized objects before calling the <code>readObject()</code> method of
     * each of the subclasses.
     *
     * @author paouelle
     *
     * @param <T> the class for which to get the serialization constructor
     *
     * @param  clazz the class for which to get the serialization constructor
     * @param  bclass the base class to start the initialization from in the
     *         returned serialization constructor
     * @return the non-<code>null</code> serialization constructor for the given
     *         class
     * @throws NullPointerException if <code>clazz</code> or <code>bclass</code>
     *         is <code>null</code>
     * @throws IllegalArgumentException if <code>bclass</code> is not a base class
     *         for <code>clazz</code>
     * @throws SecurityException if the request is denied
     */
    public static <T> Constructor<T> getSerializationConstructorFromBaseClass(Class<T> clazz, Class<?> bclass) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(bclass, "invalid null base class");
        org.apache.commons.lang3.Validate.isTrue(bclass.isAssignableFrom(clazz),
                bclass.getName() + " is not a superclass of " + clazz.getName());
        final Constructor<?> ctor;

        try { // find the default ctor for the base class
            ctor = bclass.getDeclaredConstructor();
            ctor.setAccessible(true);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("missing default constructor for base class: " + bclass.getName(), e);
        }
        @SuppressWarnings({ "restriction", "unchecked" })
        final Constructor<T> nc = (Constructor<T>) ReflectionUtils.reflFactory.newConstructorForSerialization(clazz,
                ctor);

        nc.setAccessible(true);
        return nc;
    }

    /**
     * Gets the serialization constructor for the given class based on the given
     * annotation. The annotation is used to find the first class in its hierarchy
     * that is not annotated with the annotation and expect a default constructor
     * to use for initializing the hierarchy from that point on. The constructor
     * can have any visibility (i.e. public, protected, package private, or private).
     * <p>
     * <i>Note:</i> The returned constructor results in having a proper object of
     * the given class instantiated without calling any of the constructors from
     * its class or super classes. The fields of all classes in the hierarchy will
     * not be initialized; thus set using default Java initialization (i.e.
     * <code>null</code>, 0, 0L, false, ...). This is how Java creates new
     * serialized objects before calling the <code>readObject()</code> method of
     * each of the subclasses.
     *
     * @author paouelle
     *
     * @param <T> the class for which to get the serialization constructor
     *
     * @param  clazz the class for which to get the serialization constructor
     * @param  annotationClass the annotation to search with
     * @return the non-<code>null</code> serialization constructor for the given
     *         class
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotationClass</code> is <code>null</code>
     * @throws IllegalStateException if unable to find a default constructor for
     *         the first base class not annotated with <code>annotationClass</code>
     * @throws SecurityException if the request is denied
     */
    public static <T> Constructor<T> getSerializationConstructorFromAnnotation(Class<T> clazz,
            Class<? extends Annotation> annotationClass) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(annotationClass, "invalid null annotation class");
        final Class<?> bclass = ReflectionUtils.findFirstClassNotAnnotatedWith(clazz, annotationClass);
        return ReflectionUtils.getSerializationConstructorFromBaseClass(clazz, bclass);
    }

    /**
     * Gets the declared members of the given type from the specified class.
     *
     * @author paouelle
     *
     * @param <T> the type of member
     *
     * @param  type the type of members to retrieve
     * @param  clazz the class from which to retrieve the members
     * @return the non-<code>null</code> members of the given type from the
     *         specified class
     * @throws NullPointerException if <code>type</code> or
     *         <code>clazz</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>type</code> is not
     *         {@link Field}, {@link Method}, or {@link Constructor}
     */
    @SuppressWarnings("unchecked")
    public static <T extends Member> T[] getDeclaredMembers(Class<T> type, Class<?> clazz) {
        org.apache.commons.lang3.Validate.notNull(type, "invalid null member type");
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        if (type == Field.class) {
            return (T[]) clazz.getDeclaredFields();
        } else if (type == Method.class) {
            return (T[]) clazz.getDeclaredMethods();
        } else if (type == Constructor.class) {
            return (T[]) clazz.getDeclaredConstructors();
        } else {
            throw new IllegalArgumentException("invalid member class: " + type.getName());
        }
    }

    /**
     * Gets all members of a given type (up the super class hierarchy) annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <T> the type of members to retrieve (either {@link Field},
     *            {@link Method}, or {@link Constructor})
     *
     * @param  type the type of members to retrieve
     * @param  clazz the class from which to find all annotated members
     * @param  annotation the annotation for which to find all members
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all annotated members
     * @throws NullPointerException if <code>type</code>,
     *         <code>clazz</code> or <code>annotation</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>type</code> is not
     *         {@link Field}, {@link Method}, or {@link Constructor}
     */
    public static <T extends Member> List<T> getAllMembersAnnotatedWith(Class<T> type, Class<?> clazz,
            Class<? extends Annotation> annotation, boolean up) {
        org.apache.commons.lang3.Validate.notNull(type, "invalid null member type");
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(annotation, "invalid null annotation class");
        final LinkedList<Class<?>> classes = new LinkedList<>();

        if (up) {
            while (clazz != null) {
                classes.push(clazz);
                clazz = clazz.getSuperclass();
            }
        } else {
            classes.push(clazz);
        }
        final List<T> members = new ArrayList<>(12);

        while (!classes.isEmpty()) {
            clazz = classes.pop();
            for (final T m : ReflectionUtils.getDeclaredMembers(type, clazz)) {
                if ((m instanceof AnnotatedElement)
                        && ((AnnotatedElement) m).getAnnotationsByType(annotation).length > 0) {
                    members.add(m);
                }
            }
        }
        return members;
    }

    /**
     * Gets all fields (up the super class hierarchy) annotated with the specified
     * annotation.
     *
     * @author paouelle
     *
     * @param  clazz the class from which to find all annotated fields
     * @param  annotation the annotation for which to find all fields
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all annotated fields
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotation</code> is <code>null</code>
     */
    public static List<Field> getAllFieldsAnnotatedWith(Class<?> clazz, Class<? extends Annotation> annotation,
            boolean up) {
        return ReflectionUtils.getAllMembersAnnotatedWith(Field.class, clazz, annotation, up);
    }

    /**
     * Gets all methods (up the super class hierarchy) annotated with the specified
     * annotation.
     *
     * @author paouelle
     *
     * @param  clazz the class from which to find all annotated methods
     * @param  annotation the annotation for which to find all methods
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all annotated methods
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotation</code> is <code>null</code>
     */
    public static List<Method> getAllMethodsAnnotatedWith(Class<?> clazz, Class<? extends Annotation> annotation,
            boolean up) {
        return ReflectionUtils.getAllMembersAnnotatedWith(Method.class, clazz, annotation, up);
    }

    /**
     * Gets all members of a given type (up the super class hierarchy) annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <T> the type of members to retrieve (either {@link Field},
     *            {@link Method}, or {@link Constructor})
     * @param <A> the type of annotation to search for
     *
     * @param  type the type of members to retrieve
     * @param  clazz the class from which to find all annotated members
     * @param  annotation the annotation for which to find all members
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a non-<code>null</code> map in the provided order for all annotated
     *         members with their found annotations
     * @throws NullPointerException if <code>type</code>,
     *         <code>clazz</code> or <code>annotation</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>type</code> is not
     *         {@link Field}, {@link Method}, or {@link Constructor}
     */
    public static <T extends Member, A extends Annotation> Map<T, A[]> getAllAnnotationsForMembersAnnotatedWith(
            Class<T> type, Class<?> clazz, Class<A> annotation, boolean up) {
        org.apache.commons.lang3.Validate.notNull(type, "invalid null member type");
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        org.apache.commons.lang3.Validate.notNull(annotation, "invalid null annotation class");
        final LinkedList<Class<?>> classes = new LinkedList<>();

        if (up) {
            while (clazz != null) {
                classes.push(clazz);
                clazz = clazz.getSuperclass();
            }
        } else {
            classes.push(clazz);
        }
        final Map<T, A[]> members = new LinkedHashMap<>(12);

        while (!classes.isEmpty()) {
            clazz = classes.pop();
            for (final T m : ReflectionUtils.getDeclaredMembers(type, clazz)) {
                if (m instanceof AnnotatedElement) {
                    final A[] as = ((AnnotatedElement) m).getAnnotationsByType(annotation);

                    if (as.length > 0) {
                        members.put(m, as);
                    }
                }
            }
        }
        return members;
    }

    /**
     * Gets all fields of a given type (up the super class hierarchy) annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <A> the type of annotation to search for
     *
     * @param  clazz the class from which to find all annotated fields
     * @param  annotation the annotation for which to find all fields
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a non-<code>null</code> map in the provided order for all annotated
     *         fields with their found annotations
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotation</code> is <code>null</code>
     */
    public static <A extends Annotation> Map<Field, A[]> getAllAnnotationsForFieldsAnnotatedWith(Class<?> clazz,
            Class<A> annotation, boolean up) {
        return ReflectionUtils.getAllAnnotationsForMembersAnnotatedWith(Field.class, clazz, annotation, up);
    }

    /**
     * Gets all methods of a given type (up the super class hierarchy) annotated
     * with the specified annotation.
     *
     * @author paouelle
     *
     * @param <A> the type of annotation to search for
     *
     * @param  clazz the class from which to find all annotated methods
     * @param  annotation the annotation for which to find all methods
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a non-<code>null</code> map in the provided order for all annotated
     *         methods with their found annotations
     * @throws NullPointerException if <code>clazz</code> or
     *         <code>annotation</code> is <code>null</code>
     */
    public static <A extends Annotation> Map<Method, A[]> getAllAnnotationsForMethodsAnnotatedWith(Class<?> clazz,
            Class<A> annotation, boolean up) {
        return ReflectionUtils.getAllAnnotationsForMembersAnnotatedWith(Method.class, clazz, annotation, up);
    }

    /**
     * Gets annotations that are associated with the specified element. If there
     * are no annotations associated with the element, the return value is an
     * empty map. The difference between this method and
     * {@link AnnotatedElement#getAnnotationsByType(Class)} is that this method
     * works on annotation classes that are annotated with the {@link Keyable}
     * annotation in order to determine the key of each annotation. For this to
     * work, the annotated class must defined a key element as defined in the
     * {@link Keyable} annotation of the same return type as specified in
     * <code>keyClass</code>. If its argument is a repeatable annotation type
     * (JLS 9.6), this method, attempts to find one or more annotations of that
     * type by "looking through" a container annotation. The caller of this method
     * is free to modify the returned map; it will have no effect on the maps
     * returned to other callers.
     * <p>
     * This method will look for both the specified annotation and if the
     * annotation is annotated with {@link Repeatable}, it will also look for this
     * containing annotation which must have a <code>value()</code>
     * element with an array type of the specified annotation. The resulting
     * array will always start with the annotation itself if found followed by
     * all of those provided by the containing annotation.
     *
     * @author paouelle
     *
     * @param <K> the type of the keys to find
     * @param <T> the type of the annotation to query for and return if present
     *
     * @param  keyClass the class of the keys to find and return
     * @param  annotationClass the type of annotations to retrieve
     * @param  annotatedElement the element from which to retrieve the annotations
     * @return a non-<code>null</code> ordered map of annotations associated with the
     *         given element properly keyed as defined in each annotation (may be
     *         empty if none found)
     * @throws NullPointerException if <code>annotatedElement</code>,
     *         <code>annotationClass</code> or <code>keyClass</code> is
     *         <code>null</code>
     * @throws IllegalArgumentException if <code>annotationClass</code> is not
     *         annotated with {@link Keyable} or if the containing annotation
     *         doesn't define a <code>value()</code> element returning an array
     *         of type <code>annotationClass</code> or if <code>annotationClass</code>
     *         doesn't define an element named as specified in its {@link Keyable}
     *         annotation that returns a value of the same class as <code>keyClass</code>
     *         or again if a duplicated keyed annotation is found
     */
    @SuppressWarnings("unchecked")
    public static <K, T extends Annotation> Map<K, T> getAnnotationsByType(Class<K> keyClass,
            Class<T> annotationClass, AnnotatedElement annotatedElement) {
        org.apache.commons.lang3.Validate.notNull(annotationClass, "invalid null annotation class");
        org.apache.commons.lang3.Validate.notNull(annotatedElement, "invalid null annotation element");
        org.apache.commons.lang3.Validate.notNull(keyClass, "invalid null key class");
        final Keyable k = annotationClass.getAnnotation(Keyable.class);

        org.apache.commons.lang3.Validate.isTrue(k != null, "annotation @%s not annotated with @Keyable",
                annotationClass.getName());
        final Method km;

        try {
            km = annotationClass.getMethod(k.value());
        } catch (NoSuchMethodException e) {
            throw new IllegalArgumentException(
                    "annotation key element @" + annotationClass.getName() + "." + k.value() + "() not found", e);
        }
        org.apache.commons.lang3.Validate.isTrue(keyClass.isAssignableFrom(km.getReturnType()),
                "annotation key element @%s.%s() doesn't return class: %s", annotationClass.getName(), k.value(),
                keyClass.getName());
        final T[] as = annotatedElement.getAnnotationsByType(annotationClass);

        if (as.length == 0) {
            return Collections.emptyMap();
        }
        final Map<K, T> map = new LinkedHashMap<>(as.length);

        for (T a : as) {
            final K ak;

            try {
                ak = (K) km.invoke(a);
            } catch (IllegalAccessException e) { // not expected
                throw new IllegalStateException(e);
            } catch (InvocationTargetException e) {
                final Throwable t = e.getTargetException();

                if (t instanceof Error) {
                    throw (Error) t;
                } else if (t instanceof RuntimeException) {
                    throw (RuntimeException) t;
                } else { // not expected
                    throw new IllegalStateException(e);
                }
            }
            org.apache.commons.lang3.Validate.isTrue(map.put(ak, a) == null,
                    "duplicate key '%s' found in annotation @%s", ak, annotationClass.getName());
        }
        return map;
    }

    /**
     * Gets all declared members of a given type (up the super class hierarchy).
     *
     * @author paouelle
     *
     * @param <T> the type of members to retrieve (either {@link Field},
     *            {@link Method}, or {@link Constructor})
     *
     * @param  type the type of members to retrieve
     * @param  clazz the class from which to find all declared members
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all declared members
     * @throws NullPointerException if <code>type</code> or
     *         <code>clazz</code> is <code>null</code>
     * @throws IllegalArgumentException if <code>type</code> is not
     *         {@link Field}, {@link Method}, or {@link Constructor}
     */
    public static <T extends Member> List<T> getAllDeclaredMembers(Class<T> type, Class<?> clazz, boolean up) {
        org.apache.commons.lang3.Validate.notNull(type, "invalid null member type");
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        final LinkedList<Class<?>> classes = new LinkedList<>();

        if (up) {
            while (clazz != null) {
                classes.push(clazz);
                clazz = clazz.getSuperclass();
            }
        } else {
            classes.push(clazz);
        }
        final List<T> members = new ArrayList<>(12);

        while (!classes.isEmpty()) {
            clazz = classes.pop();
            for (final T m : ReflectionUtils.getDeclaredMembers(type, clazz)) {
                members.add(m);
            }
        }
        return members;
    }

    /**
     * Gets all declared fields (up the super class hierarchy).
     *
     * @author paouelle
     *
     * @param  clazz the class from which to find all declared fields
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all declared fields
     * @throws NullPointerException if <code>clazz</code> is <code>null</code>
     */
    public static List<Field> getAllDeclaredFields(Class<?> clazz, boolean up) {
        return ReflectionUtils.getAllDeclaredMembers(Field.class, clazz, up);
    }

    /**
     * Gets all declared methods (up the super class hierarchy).
     *
     * @author paouelle
     *
     * @param  clazz the class from which to find all declared methods
     * @param  up <code>true</code> to look up the class hierarchy;
     *         <code>false</code> to only look at the specified class level
     * @return a list in the provided order for all declared methods
     * @throws NullPointerException if <code>clazz</code> is <code>null</code>
     */
    public static List<Method> getAllDeclaredMethods(Class<?> clazz, boolean up) {
        return ReflectionUtils.getAllDeclaredMembers(Method.class, clazz, up);
    }

    /**
     * Gets the raw class from the provided type.
     *
     * @author paouelle
     *
     * @param  t the type for which to get the raw class
     * @return the raw class for the given type
     */
    public static Class<?> getRawClass(Type t) {
        Type tt = t;

        while (!(tt instanceof Class<?>)) {
            if (tt instanceof ParameterizedType) {
                tt = ((ParameterizedType) t).getRawType();
            } else {
                throw new IllegalStateException("there should be a raw class for: " + t);
            }
        }
        return (Class<?>) tt;
    }

    /**
     * Gets the hierarchy of a given class.
     *
     * @author paouelle
     *
     * @param <T> the type of the class to get the hierarchy for
     *
     * @param  clazz the class to get the hierarchy for
     * @return a list of all classes in the given class's hierarchy starting with
     *         the specified class
     */
    public static <T> List<Class<?>> getClassHierarchy(Class<?> clazz) {
        org.apache.commons.lang3.Validate.notNull(clazz, "invalid null class");
        final LinkedList<Class<?>> classes = new LinkedList<>();

        while (clazz != null) {
            classes.push(clazz);
            clazz = clazz.getSuperclass();
        }
        return classes;
    }

    /**
     * Finds all classes defined in a given package using the current classloader.
     *
     * @author paouelle
     *
     * @param  pkg the package from which to find all defined classes
     * @return the non-<code>null</code> collection of all classes defined in the
     *         given package
     * @throws NullPointerException if <code>pkg</code> is <code>null</code>
     */
    public static Collection<Class<?>> findClasses(String pkg) {
        return ReflectionUtils.findClasses(pkg, Thread.currentThread().getContextClassLoader());
    }

    /**
     * Finds all classes defined in a given package.
     *
     * @author paouelle
     *
     * @param  pkg the package from which to find all defined classes
     * @param  cl the classloader to find the classes with
     * @return the non-<code>null</code> collection of all classes defined in the
     *         given package
     * @throws NullPointerException if <code>pkg</code> or <code>cl</code> is
     *         <code>null</code>
     */
    public static Collection<Class<?>> findClasses(String pkg, ClassLoader cl) {
        org.apache.commons.lang3.Validate.notNull(pkg, "invalid null pkg");
        final String scannedPath = pkg.replace('.', File.separatorChar);
        final Enumeration<URL> resources;

        try {
            resources = cl.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException("Unable to get resources from path '" + scannedPath
                    + "'. Are you sure the given '" + pkg + "' package exists?", e);
        }
        final List<Class<?>> classes = new LinkedList<>();

        while (resources.hasMoreElements()) {
            final URL url = resources.nextElement();

            if ("jar".equals(url.getProtocol())) {
                ReflectionUtils.findClassesFromJar(classes, url, scannedPath, cl);
            } else if ("file".equals(url.getProtocol())) {
                final File file = new File(url.getFile());

                ReflectionUtils.findClassesFromFile(classes, file, pkg, cl);
            } else {
                throw new IllegalArgumentException("package is provided by an unknown url: " + url);
            }
        }
        return classes;
    }

    /**
     * Find all classes from a given jar file and add them to the provided
     * list.
     *
     * @author paouelle
     *
     * @param classes the non-<code>null</code> collection of classes where to add new
     *        found classes
     * @param url the non-<code>null</code> url for the jar where to find classes
     * @param resource the non-<code>null</code> resource being scanned that
     *        corresponds to given package
     * @param cl the classloader to find the classes with
     */
    private static void findClassesFromJar(Collection<Class<?>> classes, URL url, String resource, ClassLoader cl) {
        try {
            final JarURLConnection conn = (JarURLConnection) url.openConnection();
            final URL jurl = conn.getJarFileURL();

            try (final JarInputStream jar = new JarInputStream(jurl.openStream());) {
                while (true) {
                    final JarEntry entry = jar.getNextJarEntry();

                    if (entry == null) {
                        break;
                    }
                    final String name = entry.getName();

                    if (name.endsWith(".class") && name.startsWith(resource)) {
                        final String cname = name.substring(0, name.length() - 6 // 6 for .class
                        ).replace(File.separatorChar, '.');

                        try {
                            classes.add(cl.loadClass(cname));
                        } catch (ClassNotFoundException e) { // ignore it
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new IllegalArgumentException("unable to find classes in package", e);
        }
    }

    /**
     * Find all classes from a given file or directory and add them to the provided
     * list.
     *
     * @author paouelle
     *
     * @param classes the non-<code>null</code> collection of classes where to add new
     *        found classes
     * @param file the non-<code>null</code> file or directory from which to find
     *        classes
     * @param resource the non-<code>null</code> resource being scanned that
     *        corresponds to given file or directory
     * @param cl the classloader to find the classes with
     */
    private static void findClassesFromFile(Collection<Class<?>> classes, File file, String resource,
            ClassLoader cl) {
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                ReflectionUtils.findClassesFromFile(classes, f, resource + "." + f.getName(), cl);
            }
        } else if (resource.endsWith(".class")) {
            final String cname = resource.substring(0, resource.length() - 6); // 6 for .class

            try {
                classes.add(cl.loadClass(cname));
            } catch (ClassNotFoundException e) { // ignore it
            }
        }
    }

    /**
     * Finds all resources defined in a given package using the current classloader.
     *
     * @author paouelle
     *
     * @param  pkg the package from which to find all defined resources
     * @return the non-<code>null</code> collection of all resources defined in the
     *         given package
     * @throws NullPointerException if <code>pkg</code> is <code>null</code>
     */
    public static Collection<URL> findResources(String pkg) {
        return ReflectionUtils.findResources(pkg, Thread.currentThread().getContextClassLoader());
    }

    /**
     * Finds all resources defined in a given package.
     *
     * @author paouelle
     *
     * @param  pkg the package from which to find all defined resources
     * @param  cl the classloader to find the resources with
     * @return the non-<code>null</code> collection of all resources defined in the
     *         given package
     * @throws NullPointerException if <code>pkg</code> or <code>cl</code> is
     *         <code>null</code>
     */
    public static Collection<URL> findResources(String pkg, ClassLoader cl) {
        org.apache.commons.lang3.Validate.notNull(pkg, "invalid null pkg");
        final String scannedPath = pkg.replace('.', File.separatorChar);
        final Enumeration<URL> resources;

        try {
            resources = cl.getResources(scannedPath);
        } catch (IOException e) {
            throw new IllegalArgumentException("Unable to get resources from path '" + scannedPath
                    + "'. Are you sure the given '" + pkg + "' package exists?", e);
        }
        final List<URL> urls = new LinkedList<>();

        while (resources.hasMoreElements()) {
            final URL url = resources.nextElement();

            if ("jar".equals(url.getProtocol())) {
                ReflectionUtils.findResourcesFromJar(urls, url, scannedPath, cl);
            } else if ("file".equals(url.getProtocol())) {
                final File file = new File(url.getFile());

                ReflectionUtils.findResourcesFromFile(urls, file, scannedPath, cl);
            } else {
                throw new IllegalArgumentException("package is provided by an unknown url: " + url);
            }
        }
        return urls;
    }

    /**
     * Find all resources from a given jar file and add them to the provided
     * list.
     *
     * @author paouelle
     *
     * @param urls the non-<code>null</code> collection of resources where to add new
     *        found resources
     * @param url the non-<code>null</code> url for the jar where to find resources
     * @param resource the non-<code>null</code> resource being scanned that
     *        corresponds to given package
     * @param cl the classloader to find the resources with
     */
    private static void findResourcesFromJar(Collection<URL> urls, URL url, String resource, ClassLoader cl) {
        try {
            final JarURLConnection conn = (JarURLConnection) url.openConnection();
            final URL jurl = conn.getJarFileURL();

            try (final JarInputStream jar = new JarInputStream(jurl.openStream());) {
                while (true) {
                    final JarEntry entry = jar.getNextJarEntry();

                    if (entry == null) {
                        break;
                    }
                    final String name = entry.getName();

                    if (name.startsWith(resource)) {
                        final URL u = cl.getResource(name);

                        if (u != null) {
                            urls.add(u);
                        }
                    }
                }
            }
        } catch (IOException e) {
            throw new IllegalArgumentException("unable to find resources in package", e);
        }
    }

    /**
     * Find all resources from a given file or directory and add them to the provided
     * list.
     *
     * @author paouelle
     *
     * @param urls the non-<code>null</code> collection of resources where to add new
     *        found resources
     * @param file the non-<code>null</code> file or directory from which to find
     *        resources
     * @param resource the non-<code>null</code> resource being scanned that
     *        corresponds to given file or directory
     * @param cl the classloader to find the resources with
     */
    private static void findResourcesFromFile(Collection<URL> urls, File file, String resource, ClassLoader cl) {
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                ReflectionUtils.findResourcesFromFile(urls, f, resource + File.separatorChar + f.getName(), cl);
            }
        } else {
            final URL u = cl.getResource(resource);

            if (u != null) {
                urls.add(u);
            }
        }
    }

    /**
     * Prevents instantiation of a new <code>class</code> object.
     *
     * @author paouelle
     *
     * @throws IllegalStateException always thrown
     */
    private ReflectionUtils() {
        throw new IllegalStateException("invalid constructor called");
    }
}