/***************************************************************************
* Copyright 2009-2012 by Christian Ihle *
* kontakt@usikkert.net *
* *
* This file is part of KouInject. *
* *
* KouInject is free software; you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 3 of *
* the License, or (at your option) any later version. *
* *
* KouInject 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 KouInject. *
* If not, see <http://www.gnu.org/licenses/>. *
***************************************************************************/
package net.usikkert.kouinject.util;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* Class with utility methods for the reflection api.
*
* @author Christian Ihle
*/
public class ReflectionUtils {
/**
* Gets all the methods from a class and its superclasses.
*
* <p>The methods are sorted from superclass to subclass, with methods in the superclass first,
* then methods in subclasses.</p>
*
* @param clazz The class to find the methods in.
* @return List with all the methods found.
*/
public List<Method> findAllMethods(final Class<?> clazz) {
final List<Method> methods = new ArrayList<Method>();
if (clazz.equals(Object.class)) {
return methods;
}
final Method[] declaredMethods = clazz.getDeclaredMethods();
methods.addAll(Arrays.asList(declaredMethods));
if (!clazz.getSuperclass().equals(Object.class)) {
methods.addAll(findAllMethods(clazz.getSuperclass()));
}
return methods;
}
/**
* Gets all members from a class and its superclasses.
*
* <p>The members are sorted from superclass to subclass, with fields
* and methods in the superclass first, then fields and methods in subclasses.</p>
*
* @param clazz The class to find the members in.
* @return List with all the members found.
*/
public List<Member> findAllMembers(final Class<?> clazz) {
final List<Member> members = new ArrayList<Member>();
if (clazz.equals(Object.class)) {
return members;
}
final Field[] declaredFields = clazz.getDeclaredFields();
members.addAll(Arrays.asList(declaredFields));
final Method[] declaredMethods = clazz.getDeclaredMethods();
members.addAll(Arrays.asList(declaredMethods));
if (!clazz.getSuperclass().equals(Object.class)) {
members.addAll(0, findAllMembers(clazz.getSuperclass()));
}
return members;
}
/**
* Checks if the method is overridden by any of the methods in the list of candidates.
*
* @param method The method that may be overridden.
* @param candidates A list of methods that may override the first method.
* These methods should be from the same class as the first method, including inherited methods.
* @return If the method is overridden by any of the candidates.
*/
public boolean isOverridden(final Method method, final List<Method> candidates) {
for (final Method candidate : candidates) {
if (isOverridden(method, candidate)) {
return true;
}
}
return false;
}
/**
* Checks if a method has been overridden by a candidate.
*
* <p>Rules for overriding:</p>
* <ul>
* <li>Candidate must be in a subclass of the class with method.</li>
* <li>The method names must be the same.</li>
* <li>The parameters must be the same.</li>
* <li>The return type must be the same or a subclass.</li>
* <li>Access modifier must be same or less restrictive.</li>
* <li>Method can not be private, final or static.</li>
* <li>Candidate can not be private or static.</li>
* <li>Candidate must be in the same package as a method with default access.</li>
* </ul>
*
* @param method The method that will be checked if it's been overridden.
* @param candidate The method to check if it's overriding.
* @return If method has been overridden by candidate.
*/
public boolean isOverridden(final Method method, final Method candidate) {
if (!hasOverridableAccessModifiers(method, candidate)) {
return false;
}
if (!isSubClassOf(candidate.getDeclaringClass(), method.getDeclaringClass())) {
return false;
}
if (!hasTheSameName(method, candidate)) {
return false;
}
if (!hasTheSameParameters(method, candidate)) {
return false;
}
return true;
}
/**
* Checks if the method can be overridden by the candidate based on the modifiers of the methods.
*
* <p>Private, static or final methods can not be overridden. Package private (default) methods can be
* overridden only if both methods are from classes in the same package.</p>
*
* @param method The method that may be overridden.
* @param candidate The method that may override the first method.
* @return If the candidate could override the first method, based on the modifiers of the methods.
*/
public boolean hasOverridableAccessModifiers(final Method method, final Method candidate) {
if (isFinal(method) || isPrivate(method) || isStatic(method)
|| isPrivate(candidate) || isStatic(candidate)) {
return false;
}
if (isPackagePrivate(method)) {
return isInTheSamePackage(method, candidate);
}
return true;
}
/**
* Checks if the potentialSubclass is one of the subclasses of the potentialSuperclass.
*
* @param potentialSubclass The class that will be checked to find out if it's a subclass of the potentialSuperclass.
* @param potentialSuperclass The class that the potentialSubclass will be checked against.
* @return If potentialSubclass is a subclass of potentialSuperclass.
*/
public boolean isSubClassOf(final Class<?> potentialSubclass, final Class<?> potentialSuperclass) {
if (potentialSubclass.getSuperclass() != null) {
if (potentialSubclass.getSuperclass().equals(potentialSuperclass)) {
return true;
}
return isSubClassOf(potentialSubclass.getSuperclass(), potentialSuperclass);
}
return false;
}
/**
* Checks if the method has the same name as the candidate.
*
* @param method First method to compare the name of.
* @param candidate Second method to compare the name of.
* @return If both methods have the same name.
*/
public boolean hasTheSameName(final Method method, final Method candidate) {
return method.getName().equals(candidate.getName());
}
/**
* Checks if the method has the same number of parameters, the same order of parameters,
* and the exact same type of parameters as the candidate.
*
* @param method First method to compare the parameters of.
* @param candidate Second method to compare the parameters of.
* @return If both methods have the same parameters.
*/
public boolean hasTheSameParameters(final Method method, final Method candidate) {
final Class<?>[] methodParameters = method.getParameterTypes();
final Class<?>[] candidateParameters = candidate.getParameterTypes();
if (methodParameters.length != candidateParameters.length) {
return false;
}
for (int i = 0; i < methodParameters.length; i++) {
final Class<?> methodParameter = methodParameters[i];
final Class<?> candidateParameter = candidateParameters[i];
if (!methodParameter.equals(candidateParameter)) {
return false;
}
}
return true;
}
/**
* Checks if the method is an a class in the same package as the class of the candidate.
*
* @param method The first method to compare the package of.
* @param candidate The second method to compare the package of.
* @return If both methods are from classes in the same package.
*/
public boolean isInTheSamePackage(final Method method, final Method candidate) {
final Package methodPackage = method.getDeclaringClass().getPackage();
final Package candidatePackage = candidate.getDeclaringClass().getPackage();
return methodPackage.equals(candidatePackage);
}
/**
* Checks if the member is static.
*
* @param member The member to check.
* @return If the member is static.
*/
public boolean isStatic(final Member member) {
return Modifier.isStatic(member.getModifiers());
}
/**
* Checks if the member is final.
*
* @param member The member to check.
* @return If the member is final.
*/
public boolean isFinal(final Member member) {
return Modifier.isFinal(member.getModifiers());
}
/**
* Checks if the member is private.
*
* @param member The member to check.
* @return If the member is private.
*/
public boolean isPrivate(final Member member) {
return Modifier.isPrivate(member.getModifiers());
}
/**
* Checks if the member is package private. Also called the default modifier, as no modifiers are present.
*
* @param member The member to check.
* @return If the member is package private.
*/
public boolean isPackagePrivate(final Member member) {
return !isPublic(member) && !isProtected(member) && !isPrivate(member);
}
/**
* Checks if the member is protected.
*
* @param member The member to check.
* @return If the member is protected.
*/
public boolean isProtected(final Member member) {
return Modifier.isProtected(member.getModifiers());
}
/**
* Checks if the member is public.
*
* @param member The member to check.
* @return If the member is public.
*/
public boolean isPublic(final Member member) {
return Modifier.isPublic(member.getModifiers());
}
/**
* Checks if the class is abstract.
*
* @param clazz The class to check.
* @return If the class is abstract.
*/
public boolean isAbstract(final Class<?> clazz) {
return Modifier.isAbstract(clazz.getModifiers());
}
/**
* Checks if the class is a normal class.
*
* <p>A normal class is not:</p>
* <ul>
* <li>An enum or annotation.</li>
* <li>An interface or abstract class.</li>
* <li>An anonymous or inner class.</li>
* <li>A class generated by the compiler (synthetic).</li>
* </ul>
*
* @param clazz The class to check.
* @return If the class is normal.
*/
public boolean isNormalClass(final Class<?> clazz) {
return !clazz.isAnonymousClass() && !clazz.isMemberClass() && !clazz.isAnnotation()
&& !clazz.isEnum() && !clazz.isInterface() && !isAbstract(clazz) && !clazz.isSynthetic();
}
}