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 { ); + + 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); } }