Skip to content

Commit 8b20eae

Browse files
author
soumik.dutta
committed
Add LambdaMetafactory support to call simple getters efficiently
1 parent f49d7e9 commit 8b20eae

File tree

3 files changed

+135
-4
lines changed

3 files changed

+135
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package io.leangen.graphql.metadata.execution;
2+
3+
import java.lang.reflect.AnnotatedType;
4+
import java.lang.reflect.Method;
5+
import java.util.function.Supplier;
6+
7+
/**
8+
* Created by soumik.dutta on 9/21/24.
9+
*/
10+
public class FixedLambdaInvoker extends LambdaInvoker {
11+
final private Supplier<Object> targetSupplier;
12+
13+
public FixedLambdaInvoker(final Supplier<Object> targetSupplier, final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception {
14+
super(resolverMethod, enclosingType, useSetAccessible);
15+
this.targetSupplier = targetSupplier;
16+
}
17+
18+
@Override
19+
public Object execute(final Object target, final Object[] arguments) {
20+
return this.lambdaGetter.apply(this.targetSupplier.get());
21+
}
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package io.leangen.graphql.metadata.execution;
2+
3+
import io.leangen.graphql.util.ClassUtils;
4+
5+
import java.lang.invoke.*;
6+
import java.lang.reflect.AnnotatedType;
7+
import java.lang.reflect.Method;
8+
import java.lang.reflect.Parameter;
9+
import java.util.Optional;
10+
import java.util.function.Function;
11+
12+
/**
13+
* Created by soumik.dutta on 9/21/24.
14+
*/
15+
public class LambdaInvoker extends Executable<Method> {
16+
private static final Parameter[] NO_PARAMETERS = {};
17+
private static final AnnotatedType[] NO_ANNOTATED_TYPES = {};
18+
final Function<Object, Object> lambdaGetter;
19+
private final AnnotatedType returnType;
20+
21+
22+
public LambdaInvoker(final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception {
23+
this.delegate = resolverMethod;
24+
this.returnType = resolveReturnType(enclosingType);
25+
final Optional<Function<Object, Object>> getter = createGetter(resolverMethod, useSetAccessible);
26+
if (getter.isEmpty()) {
27+
throw new Exception("Cannot create a lambda getter for " + resolverMethod.getName());
28+
}
29+
30+
this.lambdaGetter = getter.get();
31+
}
32+
33+
public static Optional<Function<Object, Object>> createGetter(final Method method, final boolean useSetAccessible) throws Exception {
34+
if (method != null) {
35+
if (method.getParameterCount() > 0) {
36+
throw new Exception(method.getName() + " requires more than one argument");
37+
}
38+
39+
try {
40+
method.setAccessible(useSetAccessible);
41+
MethodHandles.Lookup lookupMe = MethodHandles.lookup();
42+
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(method.getDeclaringClass(), lookupMe);
43+
MethodHandle virtualMethodHandle = lookup.unreflect(method);
44+
45+
CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class),
46+
MethodType.methodType(Object.class, Object.class), virtualMethodHandle,
47+
MethodType.methodType(method.getReturnType(), method.getDeclaringClass()));
48+
49+
@SuppressWarnings("unchecked") Function<Object, Object> getterFunction = (Function<Object, Object>) site.getTarget()
50+
.invokeExact();
51+
return Optional.of(getterFunction);
52+
} catch (Throwable e) {
53+
//
54+
// if we cant make a dynamic lambda here, then we give up and let the old property fetching code do its thing
55+
// this can happen on runtimes such as GraalVM native where LambdaMetafactory is not supported
56+
// and will throw something like :
57+
//
58+
// com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported.
59+
// at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89)
60+
}
61+
}
62+
return Optional.empty();
63+
}
64+
65+
@Override
66+
public Object execute(final Object target, final Object[] args) {
67+
return lambdaGetter.apply(target);
68+
}
69+
70+
@Override
71+
final public AnnotatedType getReturnType() {
72+
return returnType;
73+
}
74+
75+
private AnnotatedType resolveReturnType(final AnnotatedType enclosingType) {
76+
return ClassUtils.getReturnType(delegate, enclosingType);
77+
}
78+
79+
80+
@Override
81+
final public int getParameterCount() {
82+
return 0;
83+
}
84+
85+
@Override
86+
final public AnnotatedType[] getAnnotatedParameterTypes() {
87+
return NO_ANNOTATED_TYPES;
88+
}
89+
90+
@Override
91+
final public Parameter[] getParameters() {
92+
return NO_PARAMETERS;
93+
}
94+
}
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
11
package io.leangen.graphql.metadata.strategy.query;
22

3-
import io.leangen.graphql.metadata.execution.Executable;
4-
import io.leangen.graphql.metadata.execution.FixedMethodInvoker;
5-
import io.leangen.graphql.metadata.execution.MethodInvoker;
3+
import io.leangen.graphql.metadata.execution.*;
4+
import org.slf4j.Logger;
5+
import org.slf4j.LoggerFactory;
66

77
import java.lang.reflect.AnnotatedType;
88
import java.lang.reflect.Method;
9+
import java.util.concurrent.atomic.AtomicBoolean;
910
import java.util.function.Supplier;
1011

1112
public class DefaultMethodInvokerFactory implements MethodInvokerFactory {
13+
private static final Logger log = LoggerFactory.getLogger(DefaultMethodInvokerFactory.class);
14+
private final AtomicBoolean USE_SET_ACCESSIBLE = new AtomicBoolean(true);
15+
private final AtomicBoolean USE_LAMBDA_FACTORY = new AtomicBoolean(true);
1216

1317
@Override
1418
public Executable<Method> create(Supplier<Object> targetSupplier, Method resolverMethod, AnnotatedType enclosingType, Class<?> exposedType) {
15-
return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier, resolverMethod, enclosingType);
19+
try {
20+
if (USE_LAMBDA_FACTORY.get()) {
21+
return targetSupplier == null ? new LambdaInvoker(resolverMethod, enclosingType,
22+
USE_SET_ACCESSIBLE.get()) : new FixedLambdaInvoker(targetSupplier, resolverMethod, enclosingType,
23+
USE_SET_ACCESSIBLE.get());
24+
}
25+
} catch (Exception e) {
26+
log.warn("Lambda Invokers could not be used for {} because {}", resolverMethod, e.toString());
27+
}
28+
29+
return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier,
30+
resolverMethod, enclosingType);
1631
}
1732
}

0 commit comments

Comments
 (0)