2121
2222import java .lang .reflect .Method ;
2323import java .lang .reflect .Modifier ;
24+ import java .util .Map ;
25+ import java .util .Objects ;
26+ import java .util .Optional ;
27+ import java .util .concurrent .ConcurrentHashMap ;
2428
2529import org .assertj .core .configuration .ConfigurationProvider ;
2630import org .assertj .core .util .VisibleForTesting ;
3337 */
3438public final class Introspection {
3539
40+ // We want to cache negative results (i.e. absence of methods) to avoid same overhead on subsequent lookups
41+ // However ConcurrentHashMap does not permit nulls - Optional allows caching of 'missing' values
42+ private static final Map <MethodKey , Optional <Method >> METHOD_CACHE = new ConcurrentHashMap <>();
43+
3644 private static boolean bareNamePropertyMethods = true ;
3745
3846 /**
@@ -49,16 +57,19 @@ public final class Introspection {
4957 public static Method getPropertyGetter (String propertyName , Object target ) {
5058 checkNotNullOrEmpty (propertyName );
5159 requireNonNull (target );
52- Method getter ;
60+ Method getter = findGetter (propertyName , target );
61+ if (getter == null ) {
62+ throw new IntrospectionError (propertyNotFoundErrorMessage ("No getter for property %s in %s" , propertyName , target ));
63+ }
64+ if (!isPublic (getter .getModifiers ())) {
65+ throw new IntrospectionError (propertyNotFoundErrorMessage ("No public getter for property %s in %s" , propertyName , target ));
66+ }
5367 try {
54- getter = findGetter (propertyName , target );
55- if (isPublic (getter .getModifiers ())) {
56- // force access for static class with public getter
57- getter .setAccessible (true );
58- }
68+ // force access for static class with public getter
69+ getter .setAccessible (true );
5970 getter .invoke (target );
6071 } catch (Exception t ) {
61- throw new IntrospectionError (propertyNotFoundErrorMessage (propertyName , target ), t );
72+ throw new IntrospectionError (propertyNotFoundErrorMessage ("Unable to find property %s in %s" , propertyName , target ), t );
6273 }
6374 return getter ;
6475 }
@@ -73,13 +84,10 @@ public static boolean canIntrospectExtractBareNamePropertyMethods() {
7384 return bareNamePropertyMethods ;
7485 }
7586
76- private static String propertyNotFoundErrorMessage (String propertyName , Object target ) {
87+ private static String propertyNotFoundErrorMessage (String message , String propertyName , Object target ) {
7788 String targetTypeName = target .getClass ().getName ();
7889 String property = quote (propertyName );
79- Method getter = findGetter (propertyName , target );
80- if (getter == null ) return format ("No getter for property %s in %s" , property , targetTypeName );
81- if (!isPublic (getter .getModifiers ())) return format ("No public getter for property %s in %s" , property , targetTypeName );
82- return format ("Unable to find property %s in %s" , property , targetTypeName );
90+ return format (message , property , targetTypeName );
8391 }
8492
8593 private static Method findGetter (String propertyName , Object target ) {
@@ -102,19 +110,47 @@ private static boolean isValidGetter(Method method) {
102110 }
103111
104112 private static Method findMethod (String name , Object target ) {
105- Class <?> clazz = target .getClass ();
113+ final MethodKey methodKey = new MethodKey (name , target .getClass ());
114+ return METHOD_CACHE .computeIfAbsent (methodKey , Introspection ::findMethodByKey ).orElse (null );
115+ }
116+
117+ private static Optional <Method > findMethodByKey (MethodKey key ) {
106118 // try public methods only
119+ Class <?> clazz = key .clazz ;
107120 try {
108- return clazz .getMethod (name );
121+ return Optional . of ( clazz .getMethod (key . name ) );
109122 } catch (NoSuchMethodException | SecurityException ignored ) {}
110123 // search all methods
111124 while (clazz != null ) {
112125 try {
113- return clazz .getDeclaredMethod (name );
126+ return Optional . of ( clazz .getDeclaredMethod (key . name ) );
114127 } catch (NoSuchMethodException | SecurityException ignored ) {}
115128 clazz = clazz .getSuperclass ();
116129 }
117- return null ;
130+ return Optional .empty ();
131+ }
132+
133+ private static final class MethodKey {
134+ private final String name ;
135+ private final Class <?> clazz ;
136+
137+ private MethodKey (final String name , final Class <?> clazz ) {
138+ this .name = name ;
139+ this .clazz = clazz ;
140+ }
141+
142+ @ Override
143+ public boolean equals (final Object o ) {
144+ if (this == o ) return true ;
145+ if (o == null || getClass () != o .getClass ()) return false ;
146+ final MethodKey methodKey = (MethodKey )o ;
147+ return Objects .equals (name , methodKey .name ) && Objects .equals (clazz , methodKey .clazz );
148+ }
149+
150+ @ Override
151+ public int hashCode () {
152+ return Objects .hash (name , clazz );
153+ }
118154 }
119155
120156 private Introspection () {}
0 commit comments