diff --git a/java/.mvn/jvm.config b/java/.mvn/jvm.config
new file mode 100644
index 00000000..32599cef
--- /dev/null
+++ b/java/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/java/pom.xml b/java/pom.xml
index aeaa6ec8..0de9beb1 100644
--- a/java/pom.xml
+++ b/java/pom.xml
@@ -6,11 +6,11 @@
io.cucumber
cucumber-parent
- 4.5.0
+ 5.0.0-SNAPSHOT
ci-environment
- 12.0.1-SNAPSHOT
+ 13.0.0-SNAPSHOT
jar
Cucumber CiEnvironment
Detect CI Environment from environment variables
@@ -49,6 +49,12 @@
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
+
org.junit.jupiter
junit-jupiter
@@ -109,10 +115,6 @@
org.apache.maven.plugins
maven-compiler-plugin
-
-
- true
-
generate-ci-environments
@@ -127,6 +129,7 @@
+
org.codehaus.mojo
@@ -146,7 +149,7 @@
${project.build.directory}/codegen-classes
- Generate
+ io.cucumber.cienvironment.Generate
${project.build.directory}/generated-sources/ci-environments/java
io/cucumber/cienvironment
@@ -173,6 +176,19 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+
+
+ validate
+ validate
+
+ check
+
+
+
+
diff --git a/java/src/codegen/java/Generate.java b/java/src/codegen/java/io/cucumber/cienvironment/Generate.java
similarity index 98%
rename from java/src/codegen/java/Generate.java
rename to java/src/codegen/java/io/cucumber/cienvironment/Generate.java
index 9f0cd540..5efb03a4 100644
--- a/java/src/codegen/java/Generate.java
+++ b/java/src/codegen/java/io/cucumber/cienvironment/Generate.java
@@ -1,3 +1,5 @@
+package io.cucumber.cienvironment;
+
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import freemarker.template.Configuration;
diff --git a/java/src/codegen/resources/templates/ci-environments.java.ftl b/java/src/codegen/resources/io/cucumber/cienvironment/templates/ci-environments.java.ftl
similarity index 89%
rename from java/src/codegen/resources/templates/ci-environments.java.ftl
rename to java/src/codegen/resources/io/cucumber/cienvironment/templates/ci-environments.java.ftl
index 7400072f..af18e827 100644
--- a/java/src/codegen/resources/templates/ci-environments.java.ftl
+++ b/java/src/codegen/resources/io/cucumber/cienvironment/templates/ci-environments.java.ftl
@@ -4,7 +4,7 @@ import java.util.Collection;
import static java.util.Arrays.asList;
-class CiEnvironments {
+final class CiEnvironments {
static final Collection TEMPLATES = asList(
<#list ciEnvironments as ciEnvironment>
new CiEnvironmentImpl(
@@ -20,4 +20,8 @@ class CiEnvironments {
#list>
);
+
+ private CiEnvironments(){
+ /* no-op */
+ }
}
diff --git a/java/src/main/java/io/cucumber/cienvironment/CiEnvironment.java b/java/src/main/java/io/cucumber/cienvironment/CiEnvironment.java
index 3ff176d6..816aa05f 100644
--- a/java/src/main/java/io/cucumber/cienvironment/CiEnvironment.java
+++ b/java/src/main/java/io/cucumber/cienvironment/CiEnvironment.java
@@ -4,14 +4,20 @@
public interface CiEnvironment {
String getName();
+
String getUrl();
+
Optional getBuildNumber();
+
Optional getGit();
interface Git {
String getRemote();
+
String getRevision();
+
Optional getBranch();
+
Optional getTag();
}
}
diff --git a/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java b/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java
index b95bb812..f5501692 100644
--- a/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java
+++ b/java/src/main/java/io/cucumber/cienvironment/CiEnvironmentImpl.java
@@ -1,5 +1,7 @@
package io.cucumber.cienvironment;
+import org.jspecify.annotations.Nullable;
+
import java.util.Objects;
import java.util.Optional;
@@ -7,12 +9,12 @@
import static java.util.Optional.ofNullable;
final class CiEnvironmentImpl implements CiEnvironment {
- public String name;
- public String url;
- public String buildNumber;
- public Git git;
+ private final String name;
+ private final String url;
+ private final @Nullable String buildNumber;
+ private final @Nullable Git git;
- CiEnvironmentImpl(String name, String url, String buildNumber, Git git) {
+ CiEnvironmentImpl(String name, String url, @Nullable String buildNumber, @Nullable Git git) {
this.name = requireNonNull(name);
this.url = requireNonNull(url);
this.buildNumber = buildNumber;
@@ -63,12 +65,12 @@ public String toString() {
}
final static class Git implements CiEnvironment.Git {
- public String remote;
- public String revision;
- public String branch;
- public String tag;
+ private final String remote;
+ private final String revision;
+ private final @Nullable String branch;
+ private final @Nullable String tag;
- Git(String remote, String revision, String branch, String tag) {
+ Git(String remote, String revision, @Nullable String branch, @Nullable String tag) {
this.remote = requireNonNull(remote);
this.revision = requireNonNull(revision);
this.branch = branch;
diff --git a/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java b/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java
index e62225ac..2ca27e6f 100644
--- a/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java
+++ b/java/src/main/java/io/cucumber/cienvironment/DetectCiEnvironment.java
@@ -1,11 +1,11 @@
package io.cucumber.cienvironment;
+import org.jspecify.annotations.Nullable;
+
import java.util.Map;
import java.util.Optional;
import static io.cucumber.cienvironment.VariableExpression.evaluate;
-import static java.util.Optional.empty;
-import static java.util.Optional.of;
public final class DetectCiEnvironment {
private DetectCiEnvironment() {
@@ -28,9 +28,9 @@ public static Optional detectCiEnvironment(Map en
private static Optional detect(CiEnvironment ci, Map env) {
String url = evaluate(ci.getUrl(), env);
- if (url == null) return empty();
+ if (url == null) return Optional.empty();
- return of(new CiEnvironmentImpl(
+ return Optional.of(new CiEnvironmentImpl(
ci.getName(),
url,
ci.getBuildNumber().map(buildNumber -> evaluate(buildNumber, env)).orElse(null),
@@ -38,7 +38,7 @@ private static Optional detect(CiEnvironment ci, Map env) {
+ private static CiEnvironmentImpl.@Nullable Git detectGit(CiEnvironment ci, Map env) {
String revision = evaluateRevision(ci, env);
if (revision == null) return null;
@@ -58,7 +58,7 @@ private static CiEnvironmentImpl.Git detectGit(CiEnvironment ci, Map env) {
+ private static @Nullable String evaluateRevision(CiEnvironment ci, Map env) {
String revision = GithubEventParser.evaluateRevisionGithub(env);
if (revision != null) return revision;
return ci.getGit().map(git -> evaluate(git.getRevision(), env)).orElse(null);
diff --git a/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java b/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java
index d24276e6..c1a973d1 100644
--- a/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java
+++ b/java/src/main/java/io/cucumber/cienvironment/GithubEventParser.java
@@ -1,15 +1,20 @@
package io.cucumber.cienvironment;
+import org.jspecify.annotations.Nullable;
+
import java.io.IOException;
import java.nio.file.Files;
-import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
final class GithubEventParser {
-
+
+ private GithubEventParser(){
+ /* no-op */
+ }
+
/*
* Evaluate the current revision on GitHub.
*
@@ -19,16 +24,16 @@ final class GithubEventParser {
* * https://github.com/orgs/community/discussions/26325
* * https://github.com/cucumber/ci-environment/issues/86
*/
- static String evaluateRevisionGithub(Map env) {
+ static @Nullable String evaluateRevisionGithub(Map env) {
if (!"pull_request".equals(env.get("GITHUB_EVENT_NAME"))) {
return null;
}
- if (env.get("GITHUB_EVENT_PATH") == null) {
+ String path = env.get("GITHUB_EVENT_PATH");
+ if (path == null) {
return null;
}
try {
- Path path = Paths.get(env.get("GITHUB_EVENT_PATH"));
- String event = String.join(" ", Files.readAllLines(path));
+ String event = String.join(" ", Files.readAllLines(Paths.get(path)));
return parsePullRequestHeadSha(event);
} catch (IOException e) {
return null;
@@ -67,7 +72,7 @@ static String evaluateRevisionGithub(Map env) {
// End of object
"}$");
- static String parsePullRequestHeadSha(String eventJson) {
+ static @Nullable String parsePullRequestHeadSha(String eventJson) {
// Parse json using regex. Not ideal but works for the limited input.
Matcher matcher = GITHUB_EVENT_PATTERN.matcher(eventJson.trim());
if (!matcher.matches()) {
diff --git a/java/src/main/java/io/cucumber/cienvironment/RemoveUserInfo.java b/java/src/main/java/io/cucumber/cienvironment/RemoveUserInfo.java
index 229d0fd7..494d9f8f 100644
--- a/java/src/main/java/io/cucumber/cienvironment/RemoveUserInfo.java
+++ b/java/src/main/java/io/cucumber/cienvironment/RemoveUserInfo.java
@@ -4,8 +4,12 @@
import java.net.URISyntaxException;
final class RemoveUserInfo {
+
+ private RemoveUserInfo(){
+ /* no-op */
+ }
+
static String fromUrl(String value) {
- if (value == null) return null;
try {
URI uri = URI.create(value);
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()).toASCIIString();
diff --git a/java/src/main/java/io/cucumber/cienvironment/VariableExpression.java b/java/src/main/java/io/cucumber/cienvironment/VariableExpression.java
index 8ee9385b..f243b316 100644
--- a/java/src/main/java/io/cucumber/cienvironment/VariableExpression.java
+++ b/java/src/main/java/io/cucumber/cienvironment/VariableExpression.java
@@ -1,5 +1,7 @@
package io.cucumber.cienvironment;
+import org.jspecify.annotations.Nullable;
+
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -13,10 +15,10 @@ private VariableExpression() {
}
- static String evaluate(String expression, Map env) {
+ static @Nullable String evaluate(@Nullable String expression, Map env) {
if (expression == null) return null;
Matcher variableMatcher = variablePattern.matcher(expression);
- StringBuffer sb = new StringBuffer();
+ StringBuilder sb = new StringBuilder();
while (variableMatcher.find()) {
String variable = variableMatcher.group(1);
String value = getValue(env, variable);
@@ -43,7 +45,7 @@ static String evaluate(String expression, Map env) {
return sb.toString();
}
- private static String getValue(Map env, String variable) {
+ private static @Nullable String getValue(Map env, String variable) {
if (variable.contains("*")) {
Pattern pattern = Pattern.compile(variable.replace("*", ".*"));
// GoCD env var with dynamic "material" name
diff --git a/java/src/main/java/io/cucumber/cienvironment/package-info.java b/java/src/main/java/io/cucumber/cienvironment/package-info.java
new file mode 100644
index 00000000..a5a6f44b
--- /dev/null
+++ b/java/src/main/java/io/cucumber/cienvironment/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package io.cucumber.cienvironment;
+
+import org.jspecify.annotations.NullMarked;
\ No newline at end of file
diff --git a/java/src/test/java/io/cucumber/CiEnvironmentExample.java b/java/src/test/java/io/cucumber/CiEnvironmentExample.java
index 6c333894..01ec3fc3 100644
--- a/java/src/test/java/io/cucumber/CiEnvironmentExample.java
+++ b/java/src/test/java/io/cucumber/CiEnvironmentExample.java
@@ -2,11 +2,14 @@
import io.cucumber.cienvironment.CiEnvironment;
-import java.util.Optional;
-
import static io.cucumber.cienvironment.DetectCiEnvironment.detectCiEnvironment;
-public class CiEnvironmentExample {
+public final class CiEnvironmentExample {
+
+ private CiEnvironmentExample(){
+ // Demo
+ }
+
public static void main(String[] args) {
CiEnvironment ciEnvironment = detectCiEnvironment(System.getenv()).orElseThrow(() -> new RuntimeException("No CI environment detected"));
System.out.println("ciEnvironment = " + ciEnvironment);
diff --git a/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java b/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java
index c9f85d23..7af896f5 100644
--- a/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java
+++ b/java/src/test/java/io/cucumber/cienvironment/DetectCiEnvironmentTest.java
@@ -1,7 +1,6 @@
package io.cucumber.cienvironment;
-import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import org.jspecify.annotations.Nullable;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.converter.ArgumentConversionException;
@@ -30,7 +29,7 @@
class DetectCiEnvironmentTest {
- private static List acceptance_tests_pass() throws IOException {
+ static List acceptance_tests_pass() throws IOException {
List paths = new ArrayList<>();
try (DirectoryStream testdata = newDirectoryStream(Paths.get("..", "testdata", "src"), "*.txt")){
testdata.forEach(paths::add);
@@ -62,14 +61,18 @@ public CiEnvironment getExpected() {
static class Converter implements ArgumentConverter {
@Override
- public Expectation convert(Object source, ParameterContext context) throws ArgumentConversionException {
+ public Expectation convert(@Nullable Object source, @Nullable ParameterContext context) throws ArgumentConversionException {
+ if (source == null) {
+ throw new ArgumentConversionException("Could not convert null");
+ }
+
Path path = (Path) source;
Map env = new HashMap<>();
try (BufferedReader in = newBufferedReader(path)){
String line;
while ((line = in.readLine()) != null) {
- String[] parts = line.split("=");
+ String[] parts = line.split("=", 2);
if (parts.length == 1) {
env.put(parts[0], "");
diff --git a/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java b/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java
index 32765a5a..9e023715 100644
--- a/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java
+++ b/java/src/test/java/io/cucumber/cienvironment/GitHubPullRequestIntegrationTest.java
@@ -9,6 +9,7 @@
import java.util.Map;
import static io.cucumber.cienvironment.DetectCiEnvironment.detectCiEnvironment;
+import static java.util.Objects.requireNonNull;
import static org.junit.jupiter.api.Assertions.assertEquals;
class GitHubPullRequestIntegrationTest {
@@ -28,7 +29,8 @@ private static String parseRevisionWithRegularExpression(Map env
}
private static String parsePullRequestHeadShaWithJackson(Map env) throws IOException {
- File file = new File(env.get("GITHUB_EVENT_PATH"));
+ String githubEventPath = requireNonNull(env.get("GITHUB_EVENT_PATH"));
+ File file = new File(githubEventPath);
JsonNode event = Jackson.OBJECT_MAPPER.readTree(file);
return event.get("pull_request").get("head").get("sha").textValue();
}
diff --git a/java/src/test/java/io/cucumber/cienvironment/RemoveUserInfoTest.java b/java/src/test/java/io/cucumber/cienvironment/RemoveUserInfoTest.java
index 7c5c0c24..a5ccff85 100644
--- a/java/src/test/java/io/cucumber/cienvironment/RemoveUserInfoTest.java
+++ b/java/src/test/java/io/cucumber/cienvironment/RemoveUserInfoTest.java
@@ -3,13 +3,8 @@
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNull;
class RemoveUserInfoTest {
- @Test
- void returns_null_for_null() {
- assertNull(RemoveUserInfo.fromUrl(null));
- }
@Test
void returns_empty_string_for_empty_string() {
diff --git a/java/src/test/java/io/cucumber/cienvironment/VariableExpressionTest.java b/java/src/test/java/io/cucumber/cienvironment/VariableExpressionTest.java
index 88c127d9..649bc958 100644
--- a/java/src/test/java/io/cucumber/cienvironment/VariableExpressionTest.java
+++ b/java/src/test/java/io/cucumber/cienvironment/VariableExpressionTest.java
@@ -2,7 +2,8 @@
import org.junit.jupiter.api.Test;
-import java.util.HashMap;
+import java.util.Collections;
+import java.util.Map;
import static io.cucumber.cienvironment.VariableExpression.evaluate;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -12,61 +13,55 @@ public class VariableExpressionTest {
@Test
public void it_returns_null_when_a_variable_is_undefined() {
String expression = "hello-${SOME_VAR}";
- String result = evaluate(expression, new HashMap<>());
+ String result = evaluate(expression, Collections.emptyMap());
assertNull(result);
}
@Test
public void it_gets_a_value_without_replacement() {
String expression = "${SOME_VAR}";
- String result = evaluate(expression, new HashMap() {{
- put("SOME_VAR", "some_value");
- }});
+ String result = evaluate(expression, Map.of("SOME_VAR", "some_value"));
assertEquals("some_value", result);
}
+
@Test
public void it_escapes_a_value_without_replacement() {
String expression = "${SOME_VAR}";
- String result = evaluate(expression, new HashMap() {{
- put("SOME_VAR", "${SOME_VAR}");
- }});
+ String result = evaluate(expression, Map.of("SOME_VAR", "${SOME_VAR}"));
assertEquals("${SOME_VAR}", result);
}
@Test
public void it_captures_a_group() {
String expression = "${SOME_REF/refs\\/heads\\/(.*)/\\1}";
- String result = evaluate(expression, new HashMap() {{
- put("SOME_REF", "refs/heads/main");
- }});
+ String result = evaluate(expression, Map.of("SOME_REF", "refs/heads/main"));
assertEquals("main", result);
}
@Test
public void it_works_with_star_wildcard_in_var() {
String expression = "${GO_SCM_*_PR_BRANCH/.*:(.*)/\\1}";
- String result = evaluate(expression, new HashMap() {{
- put("GO_SCM_MY_MATERIAL_PR_BRANCH", "ashwankthkumar:feature-1");
- }});
+ String result = evaluate(expression, Map.of("GO_SCM_MY_MATERIAL_PR_BRANCH", "ashwankthkumar:feature-1"));
assertEquals("feature-1", result);
}
@Test
public void it_evaluates_a_complex_expression() {
String expression = "hello-${VAR1}-${VAR2/(.*) (.*)/\\2-\\1}-world";
- String result = evaluate(expression, new HashMap() {{
- put("VAR1", "amazing");
- put("VAR2", "gorgeous beautiful");
- }});
+ String result = evaluate(expression, Map.of(
+ "VAR1", "amazing",
+ "VAR2", "gorgeous beautiful"
+ ));
assertEquals("hello-amazing-beautiful-gorgeous-world", result);
}
+
@Test
public void it_escapes_a_complex_expression() {
String expression = "hello-${VAR1}-${VAR2/(.*) (.*)/\\2-\\1}-world";
- String result = evaluate(expression, new HashMap() {{
- put("VAR1", "${VAR1}");
- put("VAR2", "${VAR2a} ${VAR2b}");
- }});
+ String result = evaluate(expression, Map.of(
+ "VAR1", "${VAR1}",
+ "VAR2", "${VAR2a} ${VAR2b}"
+ ));
assertEquals("hello-${VAR1}-${VAR2b}-${VAR2a}-world", result);
}
}