Skip to content

Commit 6bdfe75

Browse files
authored
Remove Use of Optional when a MethodHandle isn't Found (Azure#29103)
1 parent 2e63908 commit 6bdfe75

File tree

2 files changed

+47
-27
lines changed

2 files changed

+47
-27
lines changed

sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/HeaderCollectionHandler.java

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,18 @@
1515
import java.util.HashMap;
1616
import java.util.Locale;
1717
import java.util.Map;
18-
import java.util.Optional;
1918
import java.util.concurrent.ConcurrentHashMap;
2019

2120
/*
2221
* Internal helper class that helps manage converting headers into their header collection.
2322
*/
2423
final 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
}

sdk/core/azure-core/src/main/java/com/azure/core/implementation/jackson/ObjectMapperShim.java

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import java.util.List;
2828
import java.util.Locale;
2929
import java.util.Map;
30-
import java.util.Optional;
3130
import java.util.Set;
3231
import java.util.concurrent.ConcurrentHashMap;
3332
import java.util.function.BiConsumer;
@@ -45,9 +44,12 @@ public final class ObjectMapperShim {
4544
private static final int CACHE_SIZE_LIMIT = 10000;
4645

4746
private static final Map<Type, JavaType> TYPE_TO_JAVA_TYPE_CACHE = new ConcurrentHashMap<>();
48-
private static final Map<Type, Optional<MethodHandle>> TYPE_TO_STRONGLY_TYPED_HEADERS_CONSTRUCTOR_CACHE
47+
private static final Map<Type, MethodHandle> TYPE_TO_STRONGLY_TYPED_HEADERS_CONSTRUCTOR_CACHE
4948
= new ConcurrentHashMap<>();
5049

50+
// Dummy constant that indicates an HttpHeaders-based constructor wasn't found for the Type.
51+
private static final MethodHandle NO_CONSTRUCTOR_HANDLE = MethodHandles.identity(ObjectMapperShim.class);
52+
5153
/**
5254
* Creates and configures JSON {@code ObjectMapper} capable of serializing azure.core types, with flattening and
5355
* additional properties support.
@@ -298,10 +300,10 @@ public <T> T deserialize(HttpHeaders headers, Type deserializedHeadersType) thro
298300
}
299301

300302
try {
301-
Optional<MethodHandle> constructor = getFromHeadersConstructorCache(deserializedHeadersType);
303+
MethodHandle constructor = getFromHeadersConstructorCache(deserializedHeadersType);
302304

303-
if (constructor.isPresent()) {
304-
return (T) constructor.get().invokeWithArguments(headers);
305+
if (constructor != NO_CONSTRUCTOR_HANDLE) {
306+
return (T) constructor.invokeWithArguments(headers);
305307
}
306308
} catch (Throwable throwable) {
307309
if (throwable instanceof Error) {
@@ -421,7 +423,7 @@ private static JavaType getFromTypeCache(Type key, Function<Type, JavaType> comp
421423
return TYPE_TO_JAVA_TYPE_CACHE.computeIfAbsent(key, compute);
422424
}
423425

424-
private static Optional<MethodHandle> getFromHeadersConstructorCache(Type key) {
426+
private static MethodHandle getFromHeadersConstructorCache(Type key) {
425427
if (TYPE_TO_STRONGLY_TYPED_HEADERS_CONSTRUCTOR_CACHE.size() >= CACHE_SIZE_LIMIT) {
426428
TYPE_TO_STRONGLY_TYPED_HEADERS_CONSTRUCTOR_CACHE.clear();
427429
}
@@ -430,25 +432,23 @@ private static Optional<MethodHandle> getFromHeadersConstructorCache(Type key) {
430432
try {
431433
Class<?> headersClass = TypeUtil.getRawClass(type);
432434
MethodHandles.Lookup lookup = ReflectionUtilsApi.INSTANCE.getLookupToUse(headersClass);
433-
return Optional.of(lookup.unreflectConstructor(headersClass.getDeclaredConstructor(HttpHeaders.class)));
435+
return lookup.unreflectConstructor(headersClass.getDeclaredConstructor(HttpHeaders.class));
434436
} catch (Throwable throwable) {
435437
if (throwable instanceof Error) {
436438
throw (Error) throwable;
437439
}
438440

439-
// In a previous implementation the cache was a Map<Type, MethodHandle> and null was being returned
440-
// here in an attempt to indicate that there is no HttpHeaders-based declared constructor.
441-
// Unfortunately, null isn't a valid indicator to computeIfAbsent that a computation has been performed
442-
// and this cache would never effectively be a cache as compute would always be performed when there
443-
// was no matching constructor.
441+
// In a previous implementation compute returned null here in an attempt to indicate that there is no
442+
// HttpHeaders-based declared constructor. Unfortunately, null isn't a valid indicator to
443+
// computeIfAbsent that a computation has been performed and this cache would never effectively be a
444+
// cache as compute would always be performed when there was no matching constructor.
444445
//
445-
// Now the implementation for the cache is Map<Type, Optional<MethodHandle>> and Optional.empty is
446-
// returned when there is no matching HttpHeaders-based constructor. This now results in this case
447-
// properly inserting into the cache and only running when a new type is seen or the cache is cleared
448-
// due to reaching capacity.
446+
// Now the implementation returns a dummy constant when there is no matching HttpHeaders-based
447+
// constructor. This now results in this case properly inserting into the cache and only running when a
448+
// new type is seen or the cache is cleared due to reaching capacity.
449449
//
450450
// With this change, benchmarking deserialize(HttpHeaders, Type) saw a 20% performance improvement.
451-
return Optional.empty();
451+
return NO_CONSTRUCTOR_HANDLE;
452452
}
453453
});
454454
}

0 commit comments

Comments
 (0)