Skip to content

Commit 004e192

Browse files
committed
[Java8] Improve support for method references
The implementation of t he `ConstantPoolTypeIntrospector` supported: - Reference to a static method - Reference to an instance method of a particular object - Reference to a constructor But did not support: - Reference to an instance method of an arbitrary object of a particular type Referencing instances methods would result in an index out of bounds exception as the step definition interface was expecting exactly 1 more argument then was bound to the body. We now use this discrepancy to detect if this is an instance method reference and include the the instance class as the first type. Related issues: - #1123 - #1126 This fixes #1126
1 parent c8c9eab commit 004e192

File tree

4 files changed

+104
-30
lines changed

4 files changed

+104
-30
lines changed

java8/pom.xml

-5
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@
2828
<artifactId>junit</artifactId>
2929
<scope>test</scope>
3030
</dependency>
31-
<dependency>
32-
<groupId>org.mockito</groupId>
33-
<artifactId>mockito-all</artifactId>
34-
<scope>test</scope>
35-
</dependency>
3631
<dependency>
3732
<groupId>net.sourceforge.cobertura</groupId>
3833
<artifactId>cobertura</artifactId>
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
package cucumber.runtime.java8;
22

3-
import cucumber.runtime.CucumberException;
3+
import static java.lang.Class.forName;
4+
import static java.lang.System.arraycopy;
5+
import static jdk.internal.org.objectweb.asm.Type.getObjectType;
6+
47
import cucumber.api.java8.StepdefBody;
8+
import cucumber.runtime.CucumberException;
59
import cucumber.runtime.java.TypeIntrospector;
610
import sun.reflect.ConstantPool;
711

812
import java.lang.reflect.Method;
913
import java.lang.reflect.Type;
10-
import java.util.Arrays;
11-
import java.util.List;
1214

1315
public class ConstantPoolTypeIntrospector implements TypeIntrospector {
1416
private static final Method Class_getConstantPool;
17+
private static final int REFERENCE_CLASS = 0;
18+
private static final int REFERENCE_METHOD = 1;
19+
private static final int REFERENCE_ARGUMENT_TYPES = 2;
1520

1621
static {
1722
try {
@@ -26,37 +31,63 @@ public class ConstantPoolTypeIntrospector implements TypeIntrospector {
2631

2732
@Override
2833
public Type[] getGenericTypes(Class<? extends StepdefBody> clazz, Class<? extends StepdefBody> interfac3) throws Exception {
29-
ConstantPool constantPool = (ConstantPool) Class_getConstantPool.invoke(clazz);
30-
String typeString = getLambdaTypeString(constantPool);
31-
int typeParameterCount = interfac3.getTypeParameters().length;
32-
jdk.internal.org.objectweb.asm.Type[] argumentTypes = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(typeString);
34+
final ConstantPool constantPool = (ConstantPool) Class_getConstantPool.invoke(clazz);
35+
final String[] member = getMemberReference(constantPool);
36+
final int parameterCount = interfac3.getTypeParameters().length;
37+
38+
final jdk.internal.org.objectweb.asm.Type[] argumentTypes = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(member[REFERENCE_ARGUMENT_TYPES]);
39+
40+
// If we are one parameter short, this is a
41+
// - Reference to an instance method of an arbitrary object of a particular type
42+
if (parameterCount - 1 == argumentTypes.length) {
43+
return handleMethodReferenceToObjectOfType(member[REFERENCE_CLASS], handleLambda(argumentTypes, parameterCount - 1));
44+
}
45+
// If we are not short on parameters this either
46+
// - Reference to a static method
47+
// - Reference to an instance method of a particular object
48+
// - Reference to a constructor
49+
// - A lambda expression
50+
// We can all treat these as lambda's for figuring out the types.
51+
return handleLambda(argumentTypes, parameterCount);
52+
}
53+
54+
private static Type[] handleMethodReferenceToObjectOfType(String containingType, Type[] methodArgumentTypes) throws ClassNotFoundException {
55+
Type[] containingTypeAndMethodArgumentTypes = new Type[methodArgumentTypes.length + 1];
56+
containingTypeAndMethodArgumentTypes[0] = forName(getObjectType(containingType).getClassName());
57+
arraycopy(methodArgumentTypes, 0, containingTypeAndMethodArgumentTypes, 1, methodArgumentTypes.length);
58+
return containingTypeAndMethodArgumentTypes;
59+
}
60+
61+
private static Type[] handleLambda(jdk.internal.org.objectweb.asm.Type[] argumentTypes, int typeParameterCount) throws ClassNotFoundException {
62+
if (argumentTypes.length < typeParameterCount) {
63+
throw new CucumberException(String.format("Expected at least %s arguments but found only %s", typeParameterCount, argumentTypes.length));
64+
}
65+
3366
// Only look at the N last arguments to the lambda static method, since the first ones might be variables
3467
// who only pass in the states of closed variables
35-
List<jdk.internal.org.objectweb.asm.Type> interestingArgumentTypes = Arrays.asList(argumentTypes)
36-
.subList(argumentTypes.length - typeParameterCount, argumentTypes.length);
68+
jdk.internal.org.objectweb.asm.Type[] interestingArgumentTypes = new jdk.internal.org.objectweb.asm.Type[typeParameterCount];
69+
arraycopy(argumentTypes, argumentTypes.length - typeParameterCount, interestingArgumentTypes, 0, typeParameterCount);
3770

3871
Type[] typeArguments = new Type[typeParameterCount];
3972
for (int i = 0; i < typeParameterCount; i++) {
40-
typeArguments[i] = Class.forName(interestingArgumentTypes.get(i).getClassName());
73+
typeArguments[i] = forName(interestingArgumentTypes[i].getClassName());
4174
}
4275
return typeArguments;
4376
}
4477

45-
private String getLambdaTypeString(ConstantPool constantPool) {
78+
private static String[] getMemberReference(ConstantPool constantPool) {
4679
int size = constantPool.getSize();
47-
String[] memberRef = null;
4880

4981
// find last element in constantPool with valid memberRef
5082
// - previously always at size-2 index but changed with JDK 1.8.0_60
5183
for (int i = size - 1; i > -1; i--) {
5284
try {
53-
memberRef = constantPool.getMemberRefInfoAt(i);
54-
return memberRef[2];
85+
return constantPool.getMemberRefInfoAt(i);
5586
} catch (IllegalArgumentException e) {
5687
// eat error; null entry at ConstantPool index?
5788
}
5889
}
59-
throw new CucumberException("Couldn't find memberRef.");
90+
throw new CucumberException("Couldn't find memberRef.");
6091
}
6192

6293
}

java8/src/test/java/cucumber/runtime/java8/test/LambdaStepdefs.java

+50-8
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package cucumber.runtime.java8.test;
22

3+
import static org.junit.Assert.assertEquals;
4+
import static org.junit.Assert.assertNotSame;
5+
import static org.junit.Assert.assertTrue;
6+
37
import cucumber.api.DataTable;
48
import cucumber.api.Scenario;
59
import cucumber.api.java8.En;
610

711
import java.util.List;
812

9-
import static org.junit.Assert.assertEquals;
10-
import static org.junit.Assert.assertNotSame;
11-
1213
public class LambdaStepdefs implements En {
1314
private static LambdaStepdefs lastInstance;
1415

16+
private final int outside = 41;
17+
1518
public LambdaStepdefs() {
1619
Before((Scenario scenario) -> {
1720
assertNotSame(this, lastInstance);
@@ -41,33 +44,72 @@ public LambdaStepdefs() {
4144
assertEquals("hello", localState);
4245
});
4346

44-
int localInt = 1;
4547
Given("^A statement with a simple match$", () -> {
46-
assertEquals(2, localInt+1);
48+
assertTrue(true);
49+
});
50+
51+
int localInt = 1;
52+
Given("^A statement with a scoped argument$", () -> {
53+
assertEquals(2, localInt + 1);
54+
assertEquals(42, outside + 1);
4755
});
4856

4957
Given("^I will give you (\\d+) and ([\\d\\.]+) and (\\w+) and (\\d+)$", (Integer a, Float b, String c,
5058
Integer d)
51-
-> {
59+
-> {
5260
assertEquals((Integer) 1, a);
5361
assertEquals((Float) 2.2f, b);
5462
assertEquals("three", c);
5563
assertEquals((Integer) 4, d);
5664
});
5765

58-
Given("^A lambda that declares an exception$", this::methodThatDeclaresException);
66+
Given("^A method reference that declares an exception$", this::methodThatDeclaresException);
67+
Given("^A method reference with an argument (\\d+)$", this::methodWithAnArgument);
68+
Given("^A constructor reference with an argument (.*)$", Contact::new);
69+
Given("^A static method reference with an argument (\\d+)$", LambdaStepdefs::staticMethodWithAnArgument);
70+
71+
Given("^A method reference to an arbitrary object of a particular type (\\d+)$", Contact::call);
72+
Given("^A method reference to an arbitrary object of a particular type (.*) with argument (.*)$", Contact::update);
73+
5974
}
6075

6176
private void methodThatDeclaresException() throws Throwable {
6277
}
78+
private void methodWithAnArgument(Integer cuckes) throws Throwable {
79+
assertEquals(42, cuckes.intValue());
80+
}
81+
82+
public static void staticMethodWithAnArgument(Integer cuckes) throws Throwable {
83+
assertEquals(42, cuckes.intValue());
84+
}
6385

6486

6587
private void hookWithArgs(Scenario scenario) throws Throwable {
6688
}
6789

68-
6990
public static class Person {
7091
String first;
7192
String last;
7293
}
94+
95+
public static class Contact {
96+
97+
private final String number;
98+
99+
public Contact(String number){
100+
this.number = number;
101+
assertEquals("42", number);
102+
}
103+
104+
public void call(){
105+
assertEquals("42", number);
106+
}
107+
108+
public void update(String number){
109+
assertEquals("42", this.number);
110+
assertEquals("314", number);
111+
}
112+
}
113+
114+
73115
}

java8/src/test/resources/cucumber/runtime/java8/test/java8.feature

+8-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Feature: Java8
1010

1111
Scenario: Parameterless lambdas
1212
Given A statement with a simple match
13+
Given A statement with a scoped argument
1314

1415
Scenario: Multi-param lambdas
1516
Given I will give you 1 and 2.2 and three and 4
@@ -20,5 +21,10 @@ Feature: Java8
2021
| Aslak | Hellesøy |
2122
| Donald | Duck |
2223

23-
Scenario: using lambdas with exceptions
24-
Given A lambda that declares an exception
24+
Scenario: using method references
25+
Given A method reference that declares an exception
26+
Given A method reference with an argument 42
27+
Given A static method reference with an argument 42
28+
Given A constructor reference with an argument 42
29+
Given A method reference to an arbitrary object of a particular type 42
30+
Given A method reference to an arbitrary object of a particular type 42 with argument 314

0 commit comments

Comments
 (0)