Skip to content

Commit

Permalink
Speed up reflective comparison
Browse files Browse the repository at this point in the history
 - cache results of Method lookup, both positive and negative
 - use conditions instead of catching exceptions when possible
  • Loading branch information
AndreyNudko committed Aug 26, 2020
1 parent 796862e commit d80cb5c
Showing 1 changed file with 52 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

import org.assertj.core.configuration.ConfigurationProvider;
import org.assertj.core.util.VisibleForTesting;
Expand All @@ -33,6 +37,10 @@
*/
public final class Introspection {

// We want to cache negative results (i.e. absence of methods) to avoid same overhead on subsequent lookups
// However ConcurrentHashMap does not permit nulls - Optional allows caching of 'missing' values
private static final Map<MethodKey, Optional<Method>> METHOD_CACHE = new ConcurrentHashMap<>();

private static boolean bareNamePropertyMethods = true;

/**
Expand All @@ -49,16 +57,19 @@ public final class Introspection {
public static Method getPropertyGetter(String propertyName, Object target) {
checkNotNullOrEmpty(propertyName);
requireNonNull(target);
Method getter;
Method getter = findGetter(propertyName, target);
if (getter == null) {
throw new IntrospectionError(propertyNotFoundErrorMessage("No getter for property %s in %s", propertyName, target));
}
if (!isPublic(getter.getModifiers())) {
throw new IntrospectionError(propertyNotFoundErrorMessage("No public getter for property %s in %s", propertyName, target));
}
try {
getter = findGetter(propertyName, target);
if (isPublic(getter.getModifiers())) {
// force access for static class with public getter
getter.setAccessible(true);
}
// force access for static class with public getter
getter.setAccessible(true);
getter.invoke(target);
} catch (Exception t) {
throw new IntrospectionError(propertyNotFoundErrorMessage(propertyName, target), t);
throw new IntrospectionError(propertyNotFoundErrorMessage("Unable to find property %s in %s", propertyName, target), t);
}
return getter;
}
Expand All @@ -73,13 +84,10 @@ public static boolean canIntrospectExtractBareNamePropertyMethods() {
return bareNamePropertyMethods;
}

private static String propertyNotFoundErrorMessage(String propertyName, Object target) {
private static String propertyNotFoundErrorMessage(String message, String propertyName, Object target) {
String targetTypeName = target.getClass().getName();
String property = quote(propertyName);
Method getter = findGetter(propertyName, target);
if (getter == null) return format("No getter for property %s in %s", property, targetTypeName);
if (!isPublic(getter.getModifiers())) return format("No public getter for property %s in %s", property, targetTypeName);
return format("Unable to find property %s in %s", property, targetTypeName);
return format(message, property, targetTypeName);
}

private static Method findGetter(String propertyName, Object target) {
Expand All @@ -102,19 +110,47 @@ private static boolean isValidGetter(Method method) {
}

private static Method findMethod(String name, Object target) {
Class<?> clazz = target.getClass();
final MethodKey methodKey = new MethodKey(name, target.getClass());
return METHOD_CACHE.computeIfAbsent(methodKey, Introspection::findMethodByKey).orElse(null);
}

private static Optional<Method> findMethodByKey(MethodKey key) {
// try public methods only
Class<?> clazz = key.clazz;
try {
return clazz.getMethod(name);
return Optional.of(clazz.getMethod(key.name));
} catch (NoSuchMethodException | SecurityException ignored) {}
// search all methods
while (clazz != null) {
try {
return clazz.getDeclaredMethod(name);
return Optional.of(clazz.getDeclaredMethod(key.name));
} catch (NoSuchMethodException | SecurityException ignored) {}
clazz = clazz.getSuperclass();
}
return null;
return Optional.empty();
}

private static final class MethodKey {
private final String name;
private final Class<?> clazz;

private MethodKey(final String name, final Class<?> clazz) {
this.name = name;
this.clazz = clazz;
}

@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final MethodKey methodKey = (MethodKey)o;
return Objects.equals(name, methodKey.name) && Objects.equals(clazz, methodKey.clazz);
}

@Override
public int hashCode() {
return Objects.hash(name, clazz);
}
}

private Introspection() {}
Expand Down

0 comments on commit d80cb5c

Please sign in to comment.