Skip to content

Commit b134fd5

Browse files
committed
Fix lambda instantiated method signatures
Update `LambdaUtils` to accept an explicit functional input type when creating `Function`, `Consumer`, and generic lambdas so the generated instantiated method signature matches the target method parameters. Add tests covering multi-argument lambda creation and constant-pool inspection of generated lambda classes.
1 parent 2fe70ac commit b134fd5

3 files changed

Lines changed: 71 additions & 21 deletions

File tree

microsphere-java-core/src/main/java/io/microsphere/lang/invoke/LambdaUtils.java

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
import static io.microsphere.invoke.MethodHandleUtils.findVirtual;
3333
import static io.microsphere.reflect.MethodUtils.findFunctionalInterfaceMethod;
34+
import static io.microsphere.util.ArrayUtils.combine;
3435
import static java.lang.invoke.LambdaMetafactory.metafactory;
3536
import static java.lang.invoke.MethodHandles.lookup;
3637
import static java.lang.invoke.MethodType.methodType;
@@ -49,30 +50,28 @@ public abstract class LambdaUtils implements Utils {
4950
/**
5051
* Creates a {@link Function} lambda instance for the specified target class, and method.
5152
*
52-
* @param targetClass the target class containing the method to be invoked
53-
* @param methodName the name of the method to be invoked
54-
* @param parameterTypes the parameter types of the method to be invoked
55-
* @param <T> the type of the function input
56-
* @param <R> the return type of the function
53+
* @param targetClass the target class containing the method to be invoked
54+
* @param methodName the name of the method to be invoked
55+
* @param <T> the type of the function input
56+
* @param <R> the return type of the function
5757
* @return a {@link Function} lambda instance
5858
* @throws Throwable if an error occurs during lambda creation
5959
*/
60-
public static <T, R> Function<T, R> function(Class<T> targetClass, String methodName, Class<?>... parameterTypes) throws Throwable {
61-
return lambda(Function.class, targetClass, methodName, parameterTypes);
60+
public static <T, R> Function<T, R> function(Class<T> functionInputType, Class<T> targetClass, String methodName) throws Throwable {
61+
return lambda(Function.class, functionInputType, targetClass, methodName);
6262
}
6363

6464
/**
6565
* Creates a {@link Consumer} lambda instance for the specified target class, and method.
6666
*
67-
* @param targetClass the target class containing the method to be invoked
68-
* @param methodName the name of the method to be invoked
69-
* @param parameterTypes the parameter types of the method to be invoked
70-
* @param <T> the type of the consumer input
67+
* @param targetClass the target class containing the method to be invoked
68+
* @param methodName the name of the method to be invoked
69+
* @param <T> the type of the consumer input
7170
* @return a {@link Consumer} lambda instance
7271
* @throws Throwable if an error occurs during lambda creation
7372
*/
74-
public static <T> Consumer<T> consumer(Class<T> targetClass, String methodName, Class<?>... parameterTypes) throws Throwable {
75-
return lambda(Consumer.class, targetClass, methodName, parameterTypes);
73+
public static <T> Consumer<T> consumer(Class<T> functionInputType, Class<T> targetClass, String methodName) throws Throwable {
74+
return lambda(Consumer.class, functionInputType, targetClass, methodName);
7675
}
7776

7877
/**
@@ -81,12 +80,13 @@ public static <T> Consumer<T> consumer(Class<T> targetClass, String methodName,
8180
* @param functionalInterface the functional interface class
8281
* @param targetClass the target class containing the method to be invoked
8382
* @param methodName the name of the method to be invoked
84-
* @param parameterTypes the parameter types of the method to be invoked
83+
* @param parameterTypes the types of the method parameters
8584
* @param <F> the type of the functional interface
8685
* @return a lambda instance implementing the specified functional interface
8786
* @throws Throwable if an error occurs during lambda creation
8887
*/
89-
public static <F> F lambda(Class<F> functionalInterface, Class<?> targetClass, String methodName, Class<?>... parameterTypes) throws Throwable {
88+
public static <F> F lambda(Class<F> functionalInterface, Class<?> functionInputType,
89+
Class<?> targetClass, String methodName, Class<?>... parameterTypes) throws Throwable {
9090
// 1. Set up the target method lookup context
9191
Lookup lookup = lookup();
9292

@@ -102,7 +102,8 @@ public static <F> F lambda(Class<F> functionalInterface, Class<?> targetClass, S
102102
MethodType samMethodType = methodType(functionalInterfaceReturnType, functionalInterfaceMethodParameterTypes);
103103

104104
// The type signature of the functional interface's abstract method (instantiated type)
105-
MethodType instantiatedMethodType = methodType(functionalInterfaceReturnType, targetClass);
105+
Class<?>[] instantiatedMethodParameterTypes = combine(functionInputType, parameterTypes);
106+
MethodType instantiatedMethodType = methodType(functionalInterfaceReturnType, instantiatedMethodParameterTypes);
106107

107108
// 4. Invoke LambdaMetafactory to spin the runtime lambda class
108109
CallSite callSite = metafactory(

microsphere-java-core/src/test/java/io/microsphere/internal/reflect/ConstantPoolUtilsTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,15 @@
1818
package io.microsphere.internal.reflect;
1919

2020

21+
import io.microsphere.event.EchoEvent;
22+
import io.microsphere.event.EventListener;
2123
import org.junit.jupiter.api.Test;
2224

2325
import java.lang.reflect.Field;
26+
import java.lang.reflect.Member;
27+
import java.lang.reflect.Method;
2428
import java.util.Set;
29+
import java.util.function.Function;
2530

2631
import static io.microsphere.internal.reflect.ConstantPoolUtils.CONSTANT_POOL_CLASS;
2732
import static io.microsphere.internal.reflect.ConstantPoolUtils.getClassAt;
@@ -42,9 +47,11 @@
4247
import static io.microsphere.internal.reflect.ConstantPoolUtils.getSize;
4348
import static io.microsphere.internal.reflect.ConstantPoolUtils.getStringAt;
4449
import static io.microsphere.internal.reflect.ConstantPoolUtils.getUTF8At;
50+
import static io.microsphere.lang.invoke.LambdaUtils.function;
4551
import static io.microsphere.reflect.FieldUtils.findAllDeclaredFields;
4652
import static io.microsphere.reflect.FieldUtils.getStaticFieldValue;
4753
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
54+
import static org.junit.jupiter.api.Assertions.assertEquals;
4855
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
4956
import static org.junit.jupiter.api.Assertions.assertNotNull;
5057
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -96,4 +103,44 @@ void testGetter() {
96103
assertDoesNotThrow(() -> getUTF8At(targetClass, index));
97104
}
98105
}
106+
107+
@Test
108+
void testDeduceGenericTypeFromLambda() {
109+
EventListener<EchoEvent> e = event -> {
110+
};
111+
112+
Class<? extends EventListener> targetClass = e.getClass();
113+
114+
int count = 0;
115+
116+
int size = getSize(targetClass);
117+
for (int i = 0; i < size; i++) {
118+
Member member = getMethodAt(targetClass, i);
119+
if (member instanceof Method) {
120+
count++;
121+
}
122+
}
123+
124+
assertEquals(1, count);
125+
}
126+
127+
@Test
128+
void testDeduceGenericTypeFromLambda2() throws Throwable {
129+
Function<String, String> toUpperCase = function(String.class, String.class, "toUpperCase");
130+
131+
Class<?> targetClass = toUpperCase.getClass();
132+
133+
int count = 0;
134+
135+
int size = getSize(targetClass);
136+
for (int i = size - 1; i >= 0; i--) {
137+
Member member = getMethodAt(targetClass, i);
138+
if (member instanceof Method) {
139+
count++;
140+
}
141+
}
142+
143+
assertEquals(1, count);
144+
}
145+
99146
}

microsphere-java-core/src/test/java/io/microsphere/lang/invoke/LambdaUtilsTest.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020

2121
import org.junit.jupiter.api.Test;
2222

23+
import java.util.function.BiFunction;
2324
import java.util.function.Consumer;
2425
import java.util.function.Function;
2526

2627
import static io.microsphere.lang.invoke.LambdaUtils.consumer;
2728
import static io.microsphere.lang.invoke.LambdaUtils.function;
2829
import static io.microsphere.lang.invoke.LambdaUtils.lambda;
30+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
2931
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
3032
import static org.junit.jupiter.api.Assertions.assertEquals;
3133

@@ -40,22 +42,22 @@ public class LambdaUtilsTest {
4042

4143
@Test
4244
void testFunction() throws Throwable {
43-
Function<String, String> toUpperCase = function(String.class, "toUpperCase");
45+
Function<String, String> toUpperCase = function(String.class, String.class, "toUpperCase");
4446
assertEquals("HELLO WORLD", toUpperCase.apply("hello world"));
4547

46-
Function<String, Integer> lengthFunction = function(String.class, "length");
48+
Function<String, Integer> lengthFunction = function(String.class, String.class, "length");
4749
assertEquals(11, lengthFunction.apply("hello world"));
4850
}
4951

5052
@Test
5153
void testConsumer() throws Throwable {
52-
Consumer<String> consumer = consumer(String.class, "toString");
54+
Consumer<String> consumer = consumer(String.class, String.class, "toString");
5355
assertDoesNotThrow(() -> consumer.accept("hello world"));
5456
}
5557

5658
@Test
5759
void testLambda() throws Throwable {
58-
Function<String, String> toUpperCase = lambda(Function.class, String.class, "toUpperCase");
59-
assertEquals("HELLO WORLD", toUpperCase.apply("hello world"));
60+
BiFunction<String, String, byte[]> getBytes = lambda(BiFunction.class, String.class, String.class, "getBytes", String.class);
61+
assertArrayEquals("hello world".getBytes("UTF-8"), getBytes.apply("hello world", "UTF-8"));
6062
}
6163
}

0 commit comments

Comments
 (0)