Skip to content
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

[Java8, Kotlin Java8] Support java 8 method references #1140

Merged
merged 2 commits into from
Jun 14, 2017
Merged
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
1 change: 1 addition & 0 deletions History.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [2.0.0-SNAPSHOT](https://github.com/cucumber/cucumber-jvm/compare/v1.2.5...master) (In Git)

* [Java8, Kotlin Java8] Support java 8 method references ([#1140](https://github.com/cucumber/cucumber-jvm/pull/1140) M.P. Korstanje)
* [Core] Show explicit error message when field name missed in table header ([#1014](https://github.com/cucumber/cucumber-jvm/pull/1014) Mykola Gurov)
* [Examples] Properly quit selenium in webbit examples ([#1146](https://github.com/cucumber/cucumber-jvm/pull/1146) Alberto Scotto)
* [JUnit] Use AssumptionFailed to mark scenarios/steps as skipped ([#1142](https://github.com/cucumber/cucumber-jvm/pull/1142) Björn Rasmusson)
Expand Down
5 changes: 0 additions & 5 deletions java8/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,6 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.cobertura</groupId>
<artifactId>cobertura</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package cucumber.runtime.java8;

import cucumber.runtime.CucumberException;
import static java.lang.Class.forName;
import static java.lang.System.arraycopy;
import static jdk.internal.org.objectweb.asm.Type.getObjectType;

import cucumber.api.java8.StepdefBody;
import cucumber.runtime.CucumberException;
import cucumber.runtime.java.TypeIntrospector;
import sun.reflect.ConstantPool;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;

public class ConstantPoolTypeIntrospector implements TypeIntrospector {
private static final Method Class_getConstantPool;
private static final int REFERENCE_CLASS = 0;
private static final int REFERENCE_METHOD = 1;
private static final int REFERENCE_ARGUMENT_TYPES = 2;

static {
try {
Expand All @@ -26,37 +31,72 @@ public class ConstantPoolTypeIntrospector implements TypeIntrospector {

@Override
public Type[] getGenericTypes(Class<? extends StepdefBody> clazz, Class<? extends StepdefBody> interfac3) throws Exception {
ConstantPool constantPool = (ConstantPool) Class_getConstantPool.invoke(clazz);
String typeString = getLambdaTypeString(constantPool);
int typeParameterCount = interfac3.getTypeParameters().length;
jdk.internal.org.objectweb.asm.Type[] argumentTypes = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(typeString);
final ConstantPool constantPool = (ConstantPool) Class_getConstantPool.invoke(clazz);
final String[] member = getMemberReference(constantPool);
final int parameterCount = interfac3.getTypeParameters().length;

// Kotlin lambda expression without arguments or closure variables
if (member[REFERENCE_METHOD].equals("INSTANCE")) {
return handleKotlinInstance();
}

final jdk.internal.org.objectweb.asm.Type[] argumentTypes = jdk.internal.org.objectweb.asm.Type.getArgumentTypes(member[REFERENCE_ARGUMENT_TYPES]);

// If we are one parameter short, this is a
// - Reference to an instance method of an arbitrary object of a particular type
if (parameterCount - 1 == argumentTypes.length) {
return handleMethodReferenceToObjectOfType(member[REFERENCE_CLASS], handleLambda(argumentTypes, parameterCount - 1));
}
// If we are not short on parameters this either
// - Reference to a static method
// - Reference to an instance method of a particular object
// - Reference to a constructor
// - A lambda expression
// We can all treat these as lambda's for figuring out the types.
return handleLambda(argumentTypes, parameterCount);
}

private static Type[] handleMethodReferenceToObjectOfType(String containingType, Type[] methodArgumentTypes) throws ClassNotFoundException {
Type[] containingTypeAndMethodArgumentTypes = new Type[methodArgumentTypes.length + 1];
containingTypeAndMethodArgumentTypes[0] = forName(getObjectType(containingType).getClassName());
arraycopy(methodArgumentTypes, 0, containingTypeAndMethodArgumentTypes, 1, methodArgumentTypes.length);
return containingTypeAndMethodArgumentTypes;
}

private static Type[] handleLambda(jdk.internal.org.objectweb.asm.Type[] argumentTypes, int typeParameterCount) throws ClassNotFoundException {
if (argumentTypes.length < typeParameterCount) {
throw new CucumberException(String.format("Expected at least %s arguments but found only %s", typeParameterCount, argumentTypes.length));
}

// Only look at the N last arguments to the lambda static method, since the first ones might be variables
// who only pass in the states of closed variables
List<jdk.internal.org.objectweb.asm.Type> interestingArgumentTypes = Arrays.asList(argumentTypes)
.subList(argumentTypes.length - typeParameterCount, argumentTypes.length);
jdk.internal.org.objectweb.asm.Type[] interestingArgumentTypes = new jdk.internal.org.objectweb.asm.Type[typeParameterCount];
arraycopy(argumentTypes, argumentTypes.length - typeParameterCount, interestingArgumentTypes, 0, typeParameterCount);

Type[] typeArguments = new Type[typeParameterCount];
for (int i = 0; i < typeParameterCount; i++) {
typeArguments[i] = Class.forName(interestingArgumentTypes.get(i).getClassName());
typeArguments[i] = forName(interestingArgumentTypes[i].getClassName());
}
return typeArguments;
}

private String getLambdaTypeString(ConstantPool constantPool) {
private static Type[] handleKotlinInstance() {
return new Type[0];
}

private static String[] getMemberReference(ConstantPool constantPool) {
int size = constantPool.getSize();
String[] memberRef = null;

// find last element in constantPool with valid memberRef
// - previously always at size-2 index but changed with JDK 1.8.0_60
for (int i = size - 1; i > -1; i--) {
try {
memberRef = constantPool.getMemberRefInfoAt(i);
return memberRef[2];
return constantPool.getMemberRefInfoAt(i);
} catch (IllegalArgumentException e) {
// eat error; null entry at ConstantPool index?
}
}
throw new CucumberException("Couldn't find memberRef.");
throw new CucumberException("Couldn't find memberRef.");
}

}
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package cucumber.runtime.java8.test;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertTrue;

import cucumber.api.DataTable;
import cucumber.api.Scenario;
import cucumber.api.java8.En;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotSame;

public class LambdaStepdefs implements En {
private static LambdaStepdefs lastInstance;

private final int outside = 41;

public LambdaStepdefs() {
Before((Scenario scenario) -> {
assertNotSame(this, lastInstance);
Expand Down Expand Up @@ -41,33 +44,72 @@ public LambdaStepdefs() {
assertEquals("hello", localState);
});

int localInt = 1;
Given("^A statement with a simple match$", () -> {
assertEquals(2, localInt+1);
assertTrue(true);
});

int localInt = 1;
Given("^A statement with a scoped argument$", () -> {
assertEquals(2, localInt + 1);
assertEquals(42, outside + 1);
});

Given("^I will give you (\\d+) and ([\\d\\.]+) and (\\w+) and (\\d+)$", (Integer a, Float b, String c,
Integer d)
-> {
-> {
assertEquals((Integer) 1, a);
assertEquals((Float) 2.2f, b);
assertEquals("three", c);
assertEquals((Integer) 4, d);
});

Given("^A lambda that declares an exception$", this::methodThatDeclaresException);
Given("^A method reference that declares an exception$", this::methodThatDeclaresException);
Given("^A method reference with an argument (\\d+)$", this::methodWithAnArgument);
Given("^A constructor reference with an argument (.*)$", Contact::new);
Given("^A static method reference with an argument (\\d+)$", LambdaStepdefs::staticMethodWithAnArgument);

Given("^A method reference to an arbitrary object of a particular type (\\d+)$", Contact::call);
Given("^A method reference to an arbitrary object of a particular type (.*) with argument (.*)$", Contact::update);

}

private void methodThatDeclaresException() throws Throwable {
}
private void methodWithAnArgument(Integer cuckes) throws Throwable {
assertEquals(42, cuckes.intValue());
}

public static void staticMethodWithAnArgument(Integer cuckes) throws Throwable {
assertEquals(42, cuckes.intValue());
}


private void hookWithArgs(Scenario scenario) throws Throwable {
}


public static class Person {
String first;
String last;
}

public static class Contact {

private final String number;

public Contact(String number){
this.number = number;
assertEquals("42", number);
}

public void call(){
assertEquals("42", number);
}

public void update(String number){
assertEquals("42", this.number);
assertEquals("314", number);
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Feature: Java8

Scenario: Parameterless lambdas
Given A statement with a simple match
Given A statement with a scoped argument

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

Scenario: using lambdas with exceptions
Given A lambda that declares an exception
Scenario: using method references
Given A method reference that declares an exception
Given A method reference with an argument 42
Given A static method reference with an argument 42
Given A constructor reference with an argument 42
Given A method reference to an arbitrary object of a particular type 42
Given A method reference to an arbitrary object of a particular type 42 with argument 314
6 changes: 6 additions & 0 deletions kotlin-java8/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Java 8 Bindings for Kotlin
==========================

This module only runs tests.

You can use `cucumber-java` or `cucumber-java8` directly in Kotlin.
93 changes: 93 additions & 0 deletions kotlin-java8/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-jvm</artifactId>
<relativePath>../pom.xml</relativePath>
<version>2.0.0-SNAPSHOT</version>
</parent>

<artifactId>cucumber-kotlin-java8</artifactId>
<packaging>jar</packaging>
<name>Cucumber-JVM: Kotlin Java8</name>

<properties>
<kotlin.version>1.1.2-2</kotlin.version>
</properties>

<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-java8</artifactId>
</dependency>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>cucumber-junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sourceforge.cobertura</groupId>
<artifactId>cobertura</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
<testSourceDirectory>${project.basedir}/src/test/kotlin</testSourceDirectory>
<plugins>
<plugin>
<artifactId>kotlin-maven-plugin</artifactId>
<groupId>org.jetbrains.kotlin</groupId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>

</project>
Loading