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

Replace groovy script with FreeMarker template engine #2669

Merged
merged 17 commits into from
Jan 27, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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 CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Fixed
- [Core] Improve test step creation performance ([#2666](https://github.com/cucumber/cucumber-jvm/issues/2666), Julien Kronegg)
- [Core] Replace groovy script with FreeMarker template engine ([#2648](https://github.com/cucumber/cucumber-jvm/issues/2648), Marit van Dijk)

## [7.10.1] - 2022-12-16
### Fixed
Expand Down
80 changes: 65 additions & 15 deletions cucumber-java/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,47 +89,97 @@
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Copy FreeMarker template files for code generation-->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>gherkin</artifactId>
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
<version>${gherkin.version}</version>
</dependency>
</dependencies>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>generate-i18n-sources</id>
<id>generate-code</id>
<phase>generate-sources</phase>
<goals>
<goal>execute</goal>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/codegen-classes</outputDirectory>
<resources>
<resource>
<directory>${project.basedir}/src/codegen/resources</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Make sure to compile code generator before running it -->
<!-- Note: both the code generator and regular classes are compiled -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<execution>
<id>generate-code</id>
<phase>generate-sources</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<source>${basedir}/src/main/groovy/generate-annotations.groovy</source>
<classpathScope>compile</classpathScope>
<compileSourceRoots>${project.basedir}/src/codegen/java</compileSourceRoots>
<outputDirectory>${project.build.directory}/codegen-classes</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!-- Run the code generator -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>generate-code</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<classpathScope>test</classpathScope>
<addOutputToClasspath>false</addOutputToClasspath>
<addResourcesToClasspath>false</addResourcesToClasspath>
<additionalClasspathElements>${project.build.directory}/codegen-classes</additionalClasspathElements>
<mainClass>CodeGenerationJava</mainClass>
<arguments>
<argument>${project.build.directory}/generated-sources/i18n/java</argument>
<argument>io/cucumber/java</argument>
</arguments>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<execution>
<id>add-source</id>
<id>generate-code</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>${basedir}/target/generated-sources/i18n/java</source>
<source>${project.build.directory}/generated-sources/i18n/java</source>
</sources>
</configuration>
</execution>
Expand Down
118 changes: 118 additions & 0 deletions cucumber-java/src/codegen/java/CodeGenerationJava.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import io.cucumber.gherkin.GherkinDialect;
import io.cucumber.gherkin.GherkinDialectProvider;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.Normalizer;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;

/* This class generates the cucumber-java Interfaces and package-info
* based on the languages and keywords from the GherkinDialectProvider
* using the FreeMarker template engine and provided templates.
*/
public class CodeGenerationJava {

private static String baseDirectory;
private static String packagePath;

public static void main(String[] args) throws Exception {
if (args.length != 2) {
throw new IllegalArgumentException("Usage: <baseDirectory> <packagePath>");
}
baseDirectory = args[0];
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
packagePath = args[1];

// 1. Configure template engine
Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
cfg.setClassForTemplateLoading(CodeGenerationJava.class, "templates");
cfg.setDefaultEncoding("UTF-8");
cfg.setLocale(Locale.US);
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);

// 2. Add templates
Template templateSource = cfg.getTemplate("annotation.java.ftl");
Template packageInfoSource = cfg.getTemplate("package-info.ftl");

// 3. Process template input and generate files
List<String> unsupported = new ArrayList<>();
unsupported.add("em");
unsupported.add("en_tx"); // The generated files for Emoij and Texan do
// not compile.

// Get languages and keywords from GherkinDialectProvider
GherkinDialectProvider dialectProvider = new GherkinDialectProvider();
dialectProvider.getLanguages().forEach(
language -> {
GherkinDialect dialect = dialectProvider.getDialect(language).get();
String normalizedLanguage = dialect.getLanguage().replaceAll("[\\s-]", "_").toLowerCase();
if (!unsupported.contains(normalizedLanguage)) {
dialect.getStepKeywords().stream()
.filter(it -> !it.contains(String.valueOf('*')))
.filter(it -> !it.matches("^\\d.*")).distinct()
.forEach(keyword -> {
String normalizedKeyword = normalize(keyword.replaceAll("[\\s',!\u00AD]", ""));
LinkedHashMap<String, String> binding = new LinkedHashMap<>();
binding.put("lang", normalizedLanguage);
binding.put("kw", normalizedKeyword);
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
try {
createInterfaces(templateSource, normalizedLanguage, normalizedKeyword, binding);
} catch (IOException | TemplateException e) {
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
throw new RuntimeException(e);
}
});

// Add package-info.java
String name = dialect.getName() + ((dialect.getName().equals(dialect.getNativeName())) ? ""
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
: " - " + dialect.getNativeName());
LinkedHashMap<String, String> binding = new LinkedHashMap<>();
binding.put("normalized_language", normalizedLanguage);
binding.put("language_name", name);
try {
createPackageInfo(packageInfoSource, normalizedLanguage, binding);
} catch (IOException | TemplateException e) {
throw new RuntimeException(e);
}
}
});
}

private static void createInterfaces(
Template templateSource, String normalizedLanguage, String normalizedKeyword,
LinkedHashMap<String, String> binding
) throws IOException, TemplateException {
Path path = Paths.get(baseDirectory, packagePath, normalizedLanguage, normalizedKeyword + ".java");
if (!Files.exists(path)) {
// Haitian has two translations that only differ by case - Sipozeke
// and SipozeKe
// Some file systems are unable to distinguish between them and
// overwrite the other one :-(
Files.createDirectories(path.getParent());
BufferedWriter writer = Files.newBufferedWriter(path, StandardOpenOption.CREATE);
templateSource.process(binding, writer);
}
}

private static void createPackageInfo(
Template packageInfoSource, String normalizedLanguage, LinkedHashMap<String, String> binding
) throws IOException, TemplateException {
Path path = Paths.get(baseDirectory, packagePath, normalizedLanguage, "package-info.java");
Files.createDirectories(path.getParent());
BufferedWriter writer = Files.newBufferedWriter(path, StandardOpenOption.CREATE);
mpkorstanje marked this conversation as resolved.
Show resolved Hide resolved
packageInfoSource.process(binding, writer);
}

static String normalize(CharSequence s) {
return Normalizer.normalize(s, Normalizer.Form.NFC);
}
}
42 changes: 0 additions & 42 deletions cucumber-java/src/main/groovy/generate-annotations.groovy

This file was deleted.

72 changes: 58 additions & 14 deletions cucumber-java8/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down Expand Up @@ -84,34 +89,73 @@
<version>${hamcrest.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.31</version>
</dependency>
</dependencies>

<build>
<plugins>
<!-- Copy FreeMarker template files for code generation-->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.cucumber</groupId>
<artifactId>gherkin</artifactId>
<version>${gherkin.version}</version>
</dependency>
</dependencies>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>generate-i18n-sources</id>
<id>copy-resources</id>
<phase>generate-sources</phase>
<goals>
<goal>execute</goal>
<goal>copy-resources</goal>
</goals>
<phase>generate-sources</phase>
<configuration>
<source>${basedir}/src/main/groovy/generate-interfaces.groovy</source>
<classpathScope>compile</classpathScope>
<outputDirectory>${basedir}/target/classes</outputDirectory>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Make sure to compile code generator before running it -->
<!-- Note: both the code generator and regular classes are compiled -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<executions>
<execution>
<id>compile</id>
<phase>generate-sources</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Run the code generator -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>generate-code</id>
<phase>generate-sources</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>codegen.CodeGenerationJava8</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
Expand Down
Loading