-
Notifications
You must be signed in to change notification settings - Fork 183
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LambdaMetafactory support to call simple getters efficiently
- Loading branch information
soumik.dutta
committed
Jun 21, 2024
1 parent
f49d7e9
commit 2d61219
Showing
3 changed files
with
140 additions
and
4 deletions.
There are no files selected for viewing
22 changes: 22 additions & 0 deletions
22
src/main/java/io/leangen/graphql/metadata/execution/FixedLambdaInvoker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package io.leangen.graphql.metadata.execution; | ||
|
||
import java.lang.reflect.AnnotatedType; | ||
import java.lang.reflect.Method; | ||
import java.util.function.Supplier; | ||
|
||
/** | ||
* Created by soumik.dutta on 9/21/24. | ||
*/ | ||
public class FixedLambdaInvoker extends LambdaInvoker { | ||
final private Supplier<Object> targetSupplier; | ||
|
||
public FixedLambdaInvoker(final Supplier<Object> targetSupplier, final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception { | ||
super(resolverMethod, enclosingType, useSetAccessible); | ||
this.targetSupplier = targetSupplier; | ||
} | ||
|
||
@Override | ||
public Object execute(final Object target, final Object[] arguments) { | ||
return this.lambdaGetter.apply(this.targetSupplier.get()); | ||
} | ||
} |
99 changes: 99 additions & 0 deletions
99
src/main/java/io/leangen/graphql/metadata/execution/LambdaInvoker.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
package io.leangen.graphql.metadata.execution; | ||
|
||
import io.leangen.graphql.util.ClassUtils; | ||
|
||
import java.lang.invoke.*; | ||
import java.lang.reflect.AnnotatedType; | ||
import java.lang.reflect.Method; | ||
import java.lang.reflect.Parameter; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
/** | ||
* Created by soumik.dutta on 9/21/24. | ||
*/ | ||
public class LambdaInvoker extends Executable<Method> { | ||
private static final Parameter[] NO_PARAMETERS = {}; | ||
private static final AnnotatedType[] NO_ANNOTATED_TYPES = {}; | ||
final Function<Object, Object> lambdaGetter; | ||
private final AnnotatedType returnType; | ||
|
||
|
||
public LambdaInvoker(final Method resolverMethod, final AnnotatedType enclosingType, final boolean useSetAccessible) throws Exception { | ||
this.delegate = resolverMethod; | ||
this.returnType = resolveReturnType(enclosingType); | ||
final Optional<Function<Object, Object>> lg = createGetter(resolverMethod, useSetAccessible); | ||
if (lg.isEmpty()) { | ||
throw new Exception("Cannot create a lambda getter for " + resolverMethod.getName()); | ||
} | ||
|
||
this.lambdaGetter = lg.get(); | ||
} | ||
|
||
public static Optional<Function<Object, Object>> createGetter(final Method m, final boolean useSetAccessible) throws Exception { | ||
if (m != null) { | ||
if (m.getParameterCount() > 0) { | ||
throw new Exception(m.getName() + " requires more than one argument"); | ||
} | ||
|
||
try { | ||
m.setAccessible(useSetAccessible); | ||
MethodHandles.Lookup lookupMe = MethodHandles.lookup(); | ||
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(m.getDeclaringClass(), lookupMe); | ||
MethodHandle virtualMethodHandle = lookup.unreflect(m); | ||
|
||
CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class), | ||
MethodType.methodType(Object.class, Object.class), virtualMethodHandle, | ||
MethodType.methodType(m.getReturnType(), m.getDeclaringClass())); | ||
|
||
@SuppressWarnings("unchecked") Function<Object, Object> getterFunction = (Function<Object, Object>) site.getTarget() | ||
.invokeExact(); | ||
return Optional.of(getterFunction); | ||
} catch (Throwable e) { | ||
// | ||
// if we cant make a dynamic lambda here, then we give up and let the old property fetching code do its thing | ||
// this can happen on runtimes such as GraalVM native where LambdaMetafactory is not supported | ||
// and will throw something like : | ||
// | ||
// com.oracle.svm.core.jdk.UnsupportedFeatureError: Defining hidden classes at runtime is not supported. | ||
// at org.graalvm.nativeimage.builder/com.oracle.svm.core.util.VMError.unsupportedFeature(VMError.java:89) | ||
} | ||
} | ||
return Optional.empty(); | ||
} | ||
|
||
@Override | ||
public Object execute(final Object target, final Object[] args) { | ||
return lambdaGetter.apply(target); | ||
} | ||
|
||
@Override | ||
final public AnnotatedType getReturnType() { | ||
return returnType; | ||
} | ||
|
||
|
||
private AnnotatedType resolveReturnType(final AnnotatedType enclosingType) { | ||
return ClassUtils.getReturnType(delegate, enclosingType); | ||
} | ||
|
||
/** | ||
* {@inheritDoc} | ||
* | ||
* @see java.lang.reflect.Executable#getParameterCount | ||
*/ | ||
@Override | ||
final public int getParameterCount() { | ||
return 0; | ||
} | ||
|
||
@Override | ||
final public AnnotatedType[] getAnnotatedParameterTypes() { | ||
return NO_ANNOTATED_TYPES; | ||
} | ||
|
||
@Override | ||
final public Parameter[] getParameters() { | ||
return NO_PARAMETERS; | ||
} | ||
} |
23 changes: 19 additions & 4 deletions
23
src/main/java/io/leangen/graphql/metadata/strategy/query/DefaultMethodInvokerFactory.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,32 @@ | ||
package io.leangen.graphql.metadata.strategy.query; | ||
|
||
import io.leangen.graphql.metadata.execution.Executable; | ||
import io.leangen.graphql.metadata.execution.FixedMethodInvoker; | ||
import io.leangen.graphql.metadata.execution.MethodInvoker; | ||
import io.leangen.graphql.metadata.execution.*; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.lang.reflect.AnnotatedType; | ||
import java.lang.reflect.Method; | ||
import java.util.concurrent.atomic.AtomicBoolean; | ||
import java.util.function.Supplier; | ||
|
||
public class DefaultMethodInvokerFactory implements MethodInvokerFactory { | ||
private static final Logger log = LoggerFactory.getLogger(DefaultMethodInvokerFactory.class); | ||
private final AtomicBoolean USE_SET_ACCESSIBLE = new AtomicBoolean(true); | ||
private final AtomicBoolean USE_LAMBDA_FACTORY = new AtomicBoolean(true); | ||
|
||
@Override | ||
public Executable<Method> create(Supplier<Object> targetSupplier, Method resolverMethod, AnnotatedType enclosingType, Class<?> exposedType) { | ||
return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier, resolverMethod, enclosingType); | ||
try { | ||
if (USE_LAMBDA_FACTORY.get()) { | ||
return targetSupplier == null ? new LambdaInvoker(resolverMethod, enclosingType, | ||
USE_SET_ACCESSIBLE.get()) : new FixedLambdaInvoker(targetSupplier, resolverMethod, enclosingType, | ||
USE_SET_ACCESSIBLE.get()); | ||
} | ||
} catch (Exception e) { | ||
log.warn("Lambda Invokers could not be used for {} because {}", resolverMethod, e.toString()); | ||
} | ||
|
||
return targetSupplier == null ? new MethodInvoker(resolverMethod, enclosingType) : new FixedMethodInvoker(targetSupplier, | ||
resolverMethod, enclosingType); | ||
} | ||
} |