name.remal.reflection.HierarchyUtils Maven / Gradle / Ivy
package name.remal.reflection;
import static java.lang.reflect.Modifier.isFinal;
import static java.lang.reflect.Modifier.isPrivate;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Collections.singletonList;
import static name.remal.ArrayUtils.indexOf;
import static name.remal.UncheckedCast.uncheckedCast;
import com.google.common.reflect.TypeToken;
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.Set;
import name.remal.gradle_plugins.api.RelocateClasses;
import name.remal.reflection.ClassLoaderUtils.ClassLoaderWrapper;
import org.jetbrains.annotations.NotNull;
@RelocateClasses(TypeToken.class)
public class HierarchyUtils {
public static int compareByHierarchySize(@NotNull Class> class1, @NotNull Class> class2) {
return Integer.compare(getHierarchy(class1).size(), getHierarchy(class2).size());
}
@NotNull
public static List<@NotNull Package> getPackageHierarchy(@NotNull Class> clazz) {
List result = new ArrayList<>();
ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper(clazz.getClassLoader());
String name = clazz.getName();
int lastDotPos;
while (0 < (lastDotPos = name.lastIndexOf('.'))) {
name = name.substring(0, lastDotPos);
Package pckg = classLoaderWrapper.getPackageOrNull(name);
if (pckg != null) result.add(pckg);
}
return result;
}
@NotNull
public static List<@NotNull Class super T>> getSuperClassesHierarchy(@NotNull Class clazz) {
List> result = new ArrayList<>();
Class super T> curClass = clazz;
while (curClass != null) {
result.add(curClass);
if (Object.class == curClass) break;
curClass = curClass.getSuperclass();
}
return result;
}
@NotNull
public static List<@NotNull Class super T>> getHierarchy(@NotNull Class clazz) {
Set> hierarchy = new LinkedHashSet<>();
Queue> queue = new LinkedList<>();
queue.add(clazz);
while (true) {
Class super T> curClass = queue.poll();
if (curClass == null) break;
if (hierarchy.add(curClass)) {
Class super T> superclass = curClass.getSuperclass();
if (superclass != null) {
queue.add(superclass);
}
for (Class interfaceClass : curClass.getInterfaces()) {
queue.add(uncheckedCast(interfaceClass));
}
}
}
return new ArrayList<>(hierarchy);
}
@NotNull
public static List<@NotNull Type> getGenericHierarchy(@NotNull Class clazz) {
TypeToken typeToken = TypeToken.of(clazz);
List> hierarchy = getHierarchy(clazz);
List result = new ArrayList<>(hierarchy.size());
for (Class super T> parentClass : hierarchy) {
result.add(typeToken.getSupertype(parentClass).getType());
}
return result;
}
public static boolean canBeOverridden(@NotNull Method method) {
if (isStatic(method.getModifiers())) return false;
if (isPrivate(method.getModifiers())) return false;
if (isFinal(method.getModifiers())) return false;
if (isFinal(method.getDeclaringClass().getModifiers())) return false;
return true;
}
@NotNull
public static List<@NotNull Method> getHierarchy(@NotNull Method method) {
if (isPrivate(method.getModifiers()) || isStatic(method.getModifiers())) return singletonList(method);
Class> declaringClass = method.getDeclaringClass();
if (declaringClass.getSuperclass() == null && 0 == declaringClass.getInterfaces().length) return singletonList(method);
List result = new ArrayList<>();
result.add(method);
final List> hierarchyWithoutDeclaring;
{
List> hierarchy = uncheckedCast(getHierarchy(declaringClass));
hierarchyWithoutDeclaring = hierarchy.subList(1, hierarchy.size());
}
if (0 == method.getParameterCount()) {
for (Class> clazz : hierarchyWithoutDeclaring) {
for (Method curMethod : clazz.getDeclaredMethods()) {
if (curMethod.isSynthetic()) continue;
if (!canBeOverridden(curMethod)) continue;
if (curMethod.getParameterCount() != method.getParameterCount()) continue;
if (!Objects.equals(curMethod.getName(), method.getName())) continue;
result.add(curMethod);
break;
}
}
} else {
TypeToken> typeToken = TypeToken.of(declaringClass);
for (Class> clazz : hierarchyWithoutDeclaring) {
TypeToken> classTypeToken = null;
for (Method curMethod : clazz.getDeclaredMethods()) {
if (curMethod.isSynthetic()) continue;
if (!canBeOverridden(curMethod)) continue;
if (curMethod.getParameterCount() != method.getParameterCount()) continue;
if (!Objects.equals(curMethod.getName(), method.getName())) continue;
boolean areParamsMatch = true;
//noinspection ConstantConditions
if (classTypeToken == null) {
classTypeToken = typeToken.getSupertype(uncheckedCast(clazz));
}
for (int i = 0; i < curMethod.getParameterCount(); ++i) {
Class> methodClass = method.getParameterTypes()[i];
Type curType = curMethod.getGenericParameterTypes()[i];
TypeToken> curTypeToken = classTypeToken.resolveType(curType);
Class> curClass = curTypeToken.getRawType();
if (!Objects.equals(methodClass, curClass)) {
areParamsMatch = false;
break;
}
}
if (areParamsMatch) {
result.add(curMethod);
break;
}
}
}
}
return result;
}
public static boolean isOverriddenBy(@NotNull Method parentMethod, @NotNull Method childMethod) {
if (Objects.equals(parentMethod, childMethod)) return true;
if (!canBeOverridden(parentMethod)) return false;
if (isStatic(childMethod.getModifiers()) || isPrivate(childMethod.getModifiers())) return false;
if (parentMethod.getParameterCount() != childMethod.getParameterCount()) return false;
if (!Objects.equals(parentMethod.getName(), childMethod.getName())) return false;
if (!parentMethod.getDeclaringClass().isAssignableFrom(childMethod.getDeclaringClass())) return false;
if (0 == parentMethod.getParameterCount()) return true;
if (!hasGenericParameters(parentMethod) && !hasGenericParameters(childMethod)) {
return Arrays.equals(parentMethod.getParameterTypes(), childMethod.getParameterTypes());
}
TypeToken> childTypeToken = TypeToken.of(childMethod.getDeclaringClass());
TypeToken> parentTypeToken = childTypeToken.getSupertype(uncheckedCast(parentMethod.getDeclaringClass()));
for (int i = 0; i < parentMethod.getParameterCount(); ++i) {
if (!Objects.equals(
childMethod.getParameterTypes()[i],
parentTypeToken.resolveType(parentMethod.getGenericParameterTypes()[i]).getRawType()
)) {
return false;
}
}
return true;
}
private static boolean hasGenericParameters(@NotNull Method method) {
for (Type type : method.getGenericParameterTypes()) {
if (!(type instanceof Class)) return true;
}
return false;
}
@NotNull
public static List<@NotNull Parameter> getHierarchy(@NotNull Parameter parameter) {
List result = new ArrayList<>();
result.add(parameter);
Executable declaringExecutable = parameter.getDeclaringExecutable();
if (declaringExecutable instanceof Method) {
List hierarchy = getHierarchy((Method) declaringExecutable);
if (2 <= hierarchy.size()) {
int index = indexOf(declaringExecutable.getParameters(), parameter);
for (Method method : hierarchy.subList(1, hierarchy.size())) {
result.add(method.getParameters()[index]);
}
}
}
return result;
}
@NotNull
public static List<@NotNull Method> getAllNotOverriddenMethods(@NotNull Class> type) {
List result = new ArrayList<>();
for (Class> curClass : getHierarchy(type)) {
List methodsToAdd = new ArrayList<>();
for (Method method : curClass.getDeclaredMethods()) {
boolean isOverridden = false;
if (canBeOverridden(method)) {
for (Method methodToCheck : result) {
if (isOverriddenBy(method, methodToCheck)) {
isOverridden = true;
break;
}
}
}
if (!isOverridden) {
methodsToAdd.add(method);
}
}
result.addAll(methodsToAdd);
}
return result;
}
}