Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@
import reactor.core.Exceptions;
import reactor.core.publisher.Mono;

import java.lang.invoke.LambdaMetafactory;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
Expand All @@ -29,16 +26,15 @@
*/
final class ResponseConstructorsCache {
private final ClientLogger logger = new ClientLogger(ResponseConstructorsCache.class);
private final Map<Class<?>, ResponseConstructor> cache = new ConcurrentHashMap<>();
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private final Map<Class<?>, Constructor<? extends Response<?>>> cache = new ConcurrentHashMap<>();

/**
* Identify the suitable constructor for the given response class.
*
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
ResponseConstructor get(Class<? extends Response<?>> responseClass) {
Constructor<? extends Response<?>> get(Class<? extends Response<?>> responseClass) {
return this.cache.computeIfAbsent(responseClass, this::locateResponseConstructor);
}

Expand All @@ -58,41 +54,16 @@ ResponseConstructor get(Class<? extends Response<?>> responseClass) {
* @param responseClass the response class
* @return identified constructor, null if there is no match
*/
private ResponseConstructor locateResponseConstructor(Class<?> responseClass) {
@SuppressWarnings("unchecked")
private Constructor<? extends Response<?>> locateResponseConstructor(Class<?> responseClass) {
Constructor<?>[] constructors = responseClass.getDeclaredConstructors();
// Sort constructors in the "descending order" of parameter count.
Arrays.sort(constructors, Comparator.comparing(Constructor::getParameterCount, (a, b) -> b - a));
for (Constructor<?> constructor : constructors) {
final int paramCount = constructor.getParameterCount();
if (paramCount >= 3 && paramCount <= 5) {
try {
if (paramCount == 3) {
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(3, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc3.METHOD_TYPE,
ResponseFunc3.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type()).getTarget().invoke());
} else if (paramCount == 4) {
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(4, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc4.METHOD_TYPE,
ResponseFunc4.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type()).getTarget().invoke());
} else {
// paramCount == 5
MethodHandle ctrMethodHandle = LOOKUP.unreflectConstructor(constructor);
return new ResponseConstructor(5, LambdaMetafactory.metafactory(LOOKUP,
"apply",
ResponseFunc5.METHOD_TYPE,
ResponseFunc5.SIGNATURE,
ctrMethodHandle,
ctrMethodHandle.type())
.getTarget().invoke());
}
return (Constructor<? extends Response<?>>) constructor;
} catch (Throwable t) {
throw logger.logExceptionAsError(new RuntimeException(t));
}
Expand All @@ -102,131 +73,67 @@ private ResponseConstructor locateResponseConstructor(Class<?> responseClass) {
}

/**
* Type that represent a {@link Response} constructor and can be used to invoke
* the same constructor.
* Invoke the constructor this type represents.
*
* @param constructor the constructor type
* @param decodedResponse the decoded http response
* @param bodyAsObject the http response content
* @return an instance of a {@link Response} implementation
*/
static final class ResponseConstructor {
private final int parameterCount;
private final Object responseFunc;

/**
* Creates ResponseConstructor.
*
* @param parameterCount the constructor parameter count
* @param responseFunc the functional interface which delegate its abstract method
* invocation to the invocation of a {@link Response} constructor
*/
private ResponseConstructor(int parameterCount, Object responseFunc) {
this.parameterCount = parameterCount;
this.responseFunc = responseFunc;
}
Mono<Response<?>> invoke(final Constructor<? extends Response<?>> constructor,
final HttpResponseDecoder.HttpDecodedResponse decodedResponse,
final Object bodyAsObject) {
final HttpResponse httpResponse = decodedResponse.getSourceResponse();
final HttpRequest httpRequest = httpResponse.getRequest();
final int responseStatusCode = httpResponse.getStatusCode();
final HttpHeaders responseHeaders = httpResponse.getHeaders();

/**
* Invoke the {@link Response} constructor this type represents.
*
* @param decodedResponse the decoded http response
* @param bodyAsObject the http response content
* @return an instance of a {@link Response} implementation
*/
@SuppressWarnings("unchecked")
Mono<Response<?>> invoke(final HttpResponseDecoder.HttpDecodedResponse decodedResponse,
final Object bodyAsObject) {
final HttpResponse httpResponse = decodedResponse.getSourceResponse();
final HttpRequest httpRequest = httpResponse.getRequest();
final int responseStatusCode = httpResponse.getStatusCode();
final HttpHeaders responseHeaders = httpResponse.getHeaders();
switch (this.parameterCount) {
case 3:
try {
return Mono.just((Response<?>) ((ResponseFunc3) this.responseFunc).apply(httpRequest,
final int paramCount = constructor.getParameterCount();
switch (paramCount) {
case 3:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
case 4:
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
case 5:
return decodedResponse.getDecodedHeaders()
.map((Function<Object, Response<?>>) decodedHeaders -> {
try {
return constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
case 4:
try {
return Mono.just((Response<?>) ((ResponseFunc4) this.responseFunc).apply(httpRequest,
responseHeaders,
bodyAsObject,
decodedHeaders);
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
})
.switchIfEmpty(Mono.defer((Supplier<Mono<Response<?>>>) () -> {
try {
return Mono.just(constructor.newInstance(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
case 5:
return decodedResponse.getDecodedHeaders()
.map((Function<Object, Response<?>>) decodedHeaders -> {
try {
return (Response<?>) ((ResponseFunc5) this.responseFunc).apply(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
decodedHeaders);
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
})
.switchIfEmpty(Mono.defer((Supplier<Mono<Response<?>>>) () -> {
try {
return Mono.just((Response<?>) ((ResponseFunc5) this.responseFunc)
.apply(httpRequest,
responseStatusCode,
responseHeaders,
bodyAsObject,
null));
} catch (Throwable t) {
throw Exceptions.propagate(t);
}
}));
default:
return Mono.error(new IllegalStateException(
"Response constructor with expected parameters not found."));
}
bodyAsObject,
null));
} catch (IllegalAccessException | InvocationTargetException | InstantiationException e) {
throw logger.logExceptionAsError(Exceptions.propagate(e));
}
}));
default:
throw logger.logExceptionAsError(
new IllegalStateException("Response constructor with expected parameters not found."));
}
}

@FunctionalInterface
private interface ResponseFunc3 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc3.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders);
}

@FunctionalInterface
private interface ResponseFunc4 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class,
Object.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc4.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders,
Object body);
}

@FunctionalInterface
private interface ResponseFunc5 {
MethodType SIGNATURE = MethodType.methodType(Object.class,
HttpRequest.class,
int.class,
HttpHeaders.class,
Object.class,
Object.class);
MethodType METHOD_TYPE = MethodType.methodType(ResponseFunc5.class);

Object apply(HttpRequest httpRequest,
int responseStatusCode,
HttpHeaders responseHeaders,
Object body,
Object decodedHeaders);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -488,9 +488,9 @@ private Mono<Response<?>> createResponse(HttpDecodedResponse response, Type enti
"Unable to create PagedResponse<T>. Body must be of a type that implements: " + Page.class));
}
}
ResponseConstructorsCache.ResponseConstructor ctr = this.responseConstructorsCache.get(cls);
Constructor<? extends Response<?>> ctr = this.responseConstructorsCache.get(cls);
if (ctr != null) {
return ctr.invoke(response, bodyAsObject);
return this.responseConstructorsCache.invoke(ctr, response, bodyAsObject);
} else {
return Mono.error(new RuntimeException("Cannot find suitable constructor for class " + cls));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,32 +34,32 @@ public class ResponseConstructorsCacheBenchMark {
private ResponseConstructorsCacheBenchMarkTestData testData;
// Cache Types
private ResponseConstructorsCache defaultCache;
private ResponseConstructorsCacheReflection reflectionCache;
private ResponseConstructorsCacheLambdaMetaFactory lambdaMetaCache;
private ResponseConstructorsNoCacheReflection reflectionNoCache;

@Setup
public void setup() {
testData = new ResponseConstructorsCacheBenchMarkTestData();
defaultCache = new ResponseConstructorsCache();
reflectionCache = new ResponseConstructorsCacheReflection();
lambdaMetaCache = new ResponseConstructorsCacheLambdaMetaFactory();
reflectionNoCache = new ResponseConstructorsNoCacheReflection();
}

@Benchmark
@SuppressWarnings("unchecked")
public void lambdaMetaFactoryCache(Blackhole blackhole) {
public void reflectionCache(Blackhole blackhole) {
ResponseConstructorsCacheBenchMarkTestData.Input[] inputs = testData.inputs();

for (int i = 0; i < inputs.length; i++) {
Class<? extends Response<?>> responseClass =
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using LambdaMetaFactory.
ResponseConstructorsCache.ResponseConstructor constructor = defaultCache.get(responseClass);
// Step1: Locate Constructor using Reflection.
Constructor<? extends Response<?>> constructor = defaultCache.get(responseClass);
if (constructor == null) {
throw new IllegalStateException("Response constructor with expected parameters not found.");
}
// Step2: Invoke Constructor using LambdaMetaFactory functional interface.
Mono<Response<?>> response = constructor.invoke(inputs[i].decodedResponse(),
// Step2: Invoke Constructor using Reflection.
Mono<Response<?>> response = defaultCache.invoke(constructor, inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// avoid JVM dead code detection
blackhole.consume(response.block());
Expand All @@ -68,20 +68,21 @@ public void lambdaMetaFactoryCache(Blackhole blackhole) {

@Benchmark
@SuppressWarnings("unchecked")
public void reflectionCache(Blackhole blackhole) {
public void lambdaMetaFactoryCache(Blackhole blackhole) {
ResponseConstructorsCacheBenchMarkTestData.Input[] inputs = testData.inputs();

for (int i = 0; i < inputs.length; i++) {
Class<? extends Response<?>> responseClass =
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using Reflection.
Constructor<? extends Response<?>> constructor = reflectionCache.get(responseClass);
(Class<? extends Response<?>>) TypeUtil.getRawClass(inputs[i].returnType());
// Step1: Locate Constructor using LambdaMetaFactory.
ResponseConstructorsCacheLambdaMetaFactory.ResponseConstructor constructor =
lambdaMetaCache.get(responseClass);
if (constructor == null) {
throw new IllegalStateException("Response constructor with expected parameters not found.");
}
// Step2: Invoke Constructor using Reflection.
Mono<Response<?>> response = reflectionCache.invoke(constructor, inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// Step2: Invoke Constructor using LambdaMetaFactory functional interface.
Mono<Response<?>> response = constructor.invoke(inputs[i].decodedResponse(),
inputs[i].bodyAsObject());
// avoid JVM dead code detection
blackhole.consume(response.block());
}
Expand Down
Loading