Skip to content

Add LambdaMetafactory support for Method Invokers #501

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
@@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
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>> getter = createGetter(resolverMethod, useSetAccessible);
if (getter.isEmpty()) {
throw new Exception("Cannot create a lambda getter for " + resolverMethod.getName());
}

this.lambdaGetter = getter.get();
}

public static Optional<Function<Object, Object>> createGetter(final Method method, final boolean useSetAccessible) throws Exception {
if (method != null) {
if (method.getParameterCount() > 0) {
throw new Exception(method.getName() + " requires more than one argument");
}

try {
method.setAccessible(useSetAccessible);
MethodHandles.Lookup lookupMe = MethodHandles.lookup();
MethodHandles.Lookup lookup = MethodHandles.privateLookupIn(method.getDeclaringClass(), lookupMe);
MethodHandle virtualMethodHandle = lookup.unreflect(method);

CallSite site = LambdaMetafactory.metafactory(lookup, "apply", MethodType.methodType(Function.class),
MethodType.methodType(Object.class, Object.class), virtualMethodHandle,
MethodType.methodType(method.getReturnType(), method.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);
}


@Override
final public int getParameterCount() {
return 0;
}

@Override
final public AnnotatedType[] getAnnotatedParameterTypes() {
return NO_ANNOTATED_TYPES;
}

@Override
final public Parameter[] getParameters() {
return NO_PARAMETERS;
}
}
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);
}
}