1515import java .util .HashMap ;
1616import java .util .Locale ;
1717import java .util .Map ;
18- import java .util .Optional ;
1918import java .util .concurrent .ConcurrentHashMap ;
2019
2120/*
2221 * Internal helper class that helps manage converting headers into their header collection.
2322 */
2423final class HeaderCollectionHandler {
2524 private static final int CACHE_SIZE_LIMIT = 10000 ;
26- private static final Map <Field , Optional <MethodHandle >> FIELD_TO_SETTER_CACHE = new ConcurrentHashMap <>();
25+ private static final Map <Field , MethodHandle > FIELD_TO_SETTER_CACHE = new ConcurrentHashMap <>();
26+
27+ // Dummy constant that indicates no setter was found for the Field.
28+ private static final MethodHandle NO_SETTER_HANDLE = MethodHandles .identity (HeaderCollectionHandler .class );
29+
2730 private final String prefix ;
2831 private final int prefixLength ;
2932 private final Map <String , String > values ;
@@ -83,14 +86,14 @@ private boolean usePublicSetter(Object deserializedHeaders, ClientLogger logger)
8386 final String clazzSimpleName = clazz .getSimpleName ();
8487 final String fieldName = declaringField .getName ();
8588
86- Optional < MethodHandle > setterHandler = getFromCache (declaringField , clazz , clazzSimpleName , fieldName , logger );
89+ MethodHandle setterHandler = getFromCache (declaringField , clazz , clazzSimpleName , fieldName , logger );
8790
88- if (! setterHandler . isPresent () ) {
91+ if (setterHandler == NO_SETTER_HANDLE ) {
8992 return false ;
9093 }
9194
9295 try {
93- setterHandler .get (). invokeWithArguments (deserializedHeaders , values );
96+ setterHandler .invokeWithArguments (deserializedHeaders , values );
9497 logger .verbose ("Set header collection {} on class {} using MethodHandle." , fieldName , clazzSimpleName );
9598
9699 return true ;
@@ -109,7 +112,7 @@ private static String getPotentialSetterName(String fieldName) {
109112 return "set" + fieldName .substring (0 , 1 ).toUpperCase (Locale .ROOT ) + fieldName .substring (1 );
110113 }
111114
112- private static Optional < MethodHandle > getFromCache (Field key , Class <?> clazz , String clazzSimpleName ,
115+ private static MethodHandle getFromCache (Field key , Class <?> clazz , String clazzSimpleName ,
113116 String fieldName , ClientLogger logger ) {
114117 if (FIELD_TO_SETTER_CACHE .size () >= CACHE_SIZE_LIMIT ) {
115118 FIELD_TO_SETTER_CACHE .clear ();
@@ -121,7 +124,16 @@ private static Optional<MethodHandle> getFromCache(Field key, Class<?> clazz, St
121124 lookupToUse = ReflectionUtilsApi .INSTANCE .getLookupToUse (clazz );
122125 } catch (Exception ex ) {
123126 logger .verbose ("Failed to retrieve MethodHandles.Lookup for field {}." , field , ex );
124- return Optional .empty ();
127+
128+ // In a previous implementation compute returned null here in an attempt to indicate that there is no
129+ // setter for the field. Unfortunately, null isn't a valid indicator to computeIfAbsent that a
130+ // computation has been performed and this cache would never effectively be a cache as compute would
131+ // always be performed when there was no setter for the field.
132+ //
133+ // Now the implementation returns a dummy constant when there is no setter for the field. This now
134+ // results in this case properly inserting into the cache and only running when a new type is seen or
135+ // the cache is cleared due to reaching capacity.
136+ return NO_SETTER_HANDLE ;
125137 }
126138
127139 String setterName = getPotentialSetterName (fieldName );
@@ -132,7 +144,7 @@ private static Optional<MethodHandle> getFromCache(Field key, Class<?> clazz, St
132144
133145 logger .verbose ("Using MethodHandle for setter {} on class {}." , setterName , clazzSimpleName );
134146
135- return Optional . of ( handle ) ;
147+ return handle ;
136148 } catch (ReflectiveOperationException ex ) {
137149 logger .verbose ("Failed to retrieve MethodHandle for setter {} on class {}." , setterName ,
138150 clazzSimpleName , ex );
@@ -145,13 +157,21 @@ private static Optional<MethodHandle> getFromCache(Field key, Class<?> clazz, St
145157 logger .verbose ("Using unreflected MethodHandle for setter {} on class {}." , setterName ,
146158 clazzSimpleName );
147159
148- return Optional . of ( handle ) ;
160+ return handle ;
149161 } catch (ReflectiveOperationException ex ) {
150162 logger .verbose ("Failed to unreflect MethodHandle for setter {} on class {}." , setterName ,
151163 clazzSimpleName , ex );
152164 }
153165
154- return Optional .empty ();
166+ // In a previous implementation compute returned null here in an attempt to indicate that there is no setter
167+ // for the field. Unfortunately, null isn't a valid indicator to computeIfAbsent that a computation has been
168+ // performed and this cache would never effectively be a cache as compute would always be performed when
169+ // there was no setter for the field.
170+ //
171+ // Now the implementation returns a dummy constant when there is no setter for the field. This now results
172+ // in this case properly inserting into the cache and only running when a new type is seen or the cache is
173+ // cleared due to reaching capacity.
174+ return NO_SETTER_HANDLE ;
155175 });
156176 }
157177}
0 commit comments