From 6db5503d084550be0a73ca945faef15a9302a276 Mon Sep 17 00:00:00 2001 From: Reed von Redwitz Date: Mon, 11 May 2026 05:27:04 +0200 Subject: [PATCH] feat(spawn-jdk): add AddExports, AddOpens, AddReads, and PackageName option types --- TODO.md | 11 -- .../java/build/spawn/jdk/JDKApplication.java | 12 ++ .../build/spawn/jdk/option/AddExports.java | 179 ++++++++++++++++++ .../java/build/spawn/jdk/option/AddOpens.java | 179 ++++++++++++++++++ .../java/build/spawn/jdk/option/AddReads.java | 161 ++++++++++++++++ .../build/spawn/jdk/option/ModuleName.java | 5 + .../build/spawn/jdk/option/PackageName.java | 91 +++++++++ .../spawn/jdk/option/AddExportsTests.java | 62 ++++++ .../build/spawn/jdk/option/AddOpensTests.java | 62 ++++++ .../build/spawn/jdk/option/AddReadsTests.java | 59 ++++++ 10 files changed, 810 insertions(+), 11 deletions(-) create mode 100644 spawn-jdk/src/main/java/build/spawn/jdk/option/AddExports.java create mode 100644 spawn-jdk/src/main/java/build/spawn/jdk/option/AddOpens.java create mode 100644 spawn-jdk/src/main/java/build/spawn/jdk/option/AddReads.java create mode 100644 spawn-jdk/src/main/java/build/spawn/jdk/option/PackageName.java create mode 100644 spawn-jdk/src/test/java/build/spawn/jdk/option/AddExportsTests.java create mode 100644 spawn-jdk/src/test/java/build/spawn/jdk/option/AddOpensTests.java create mode 100644 spawn-jdk/src/test/java/build/spawn/jdk/option/AddReadsTests.java diff --git a/TODO.md b/TODO.md index ead23de..9618f37 100644 --- a/TODO.md +++ b/TODO.md @@ -2,8 +2,6 @@ # NOW ------------------------------------------------------------------------------------------------------------------------ -* Refactor/Replace Docker okhttp client with Java HTTP Client - * Introduce Debugging support * TODO: Create issues for the remaining JDK features/options (eg: JMX, Garbage Collection, Logging, Flight Recorder, Early Access, Preview etc) @@ -17,15 +15,6 @@ * Introduce Composition Tests -* FIX! Introduce AddReads option to support representing "--add-reads" (and inheritance) -* FIX! Introduce AddOpens option to support representing "--add-opens" (and inheritance) - -eg: to mimic this stuff that's added automatically! - -// JDKOption.of("--add-reads"), JDKOption.of("build.spawn.platform.local.jdk=ALL-UNNAMED"), -// JDKOption.of("--add-opens"), -// JDKOption.of("build.spawn.platform.local.jdk/build.spawn.platform.local.jdk=ALL-UNNAMED"), - ------------------------------------------------------------------------------------------------------------------------ # LATER ------------------------------------------------------------------------------------------------------------------------ diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/JDKApplication.java b/spawn-jdk/src/main/java/build/spawn/jdk/JDKApplication.java index a71467c..031e555 100644 --- a/spawn-jdk/src/main/java/build/spawn/jdk/JDKApplication.java +++ b/spawn-jdk/src/main/java/build/spawn/jdk/JDKApplication.java @@ -30,7 +30,10 @@ import build.spawn.application.Platform; import build.spawn.application.option.ApplicationSubscriber; import build.spawn.application.option.LaunchIdentity; +import build.spawn.jdk.option.AddExports; import build.spawn.jdk.option.AddModules; +import build.spawn.jdk.option.AddOpens; +import build.spawn.jdk.option.AddReads; import build.spawn.jdk.option.ClassPath; import build.spawn.jdk.option.Headless; import build.spawn.jdk.option.Jar; @@ -112,6 +115,15 @@ public void onPreparing(final Platform platform, PatchModule.detect() .forEach(options::add); + + AddExports.detect() + .forEach(options::add); + + AddOpens.detect() + .forEach(options::add); + + AddReads.detect() + .forEach(options::add); } } diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/option/AddExports.java b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddExports.java new file mode 100644 index 0000000..6bfdf34 --- /dev/null +++ b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddExports.java @@ -0,0 +1,179 @@ +package build.spawn.jdk.option; + +/*- + * #%L + * Spawn JDK + * %% + * Copyright (C) 2026 Workday, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import build.base.configuration.CollectedOption; +import build.base.configuration.ConfigurationBuilder; +import build.base.option.JDKVersion; +import build.base.table.Cell; +import build.base.table.Table; +import build.base.table.Tabular; +import build.base.table.option.CellSeparator; +import build.base.table.option.TableName; +import build.spawn.application.Platform; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A {@link JDKOption} to specify a {@code --add-exports=source-module/package=target-module}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +public class AddExports + implements JDKOption, CollectedOption, Tabular { + + /** + * The source {@link ModuleName} that owns the package. + */ + private final ModuleName sourceModule; + + /** + * The {@link PackageName} to export. + */ + private final PackageName packageName; + + /** + * The target {@link ModuleName} to export to. + */ + private final ModuleName targetModule; + + /** + * Constructs an {@link AddExports}. + * + * @param sourceModule the source {@link ModuleName} + * @param packageName the {@link PackageName} + * @param targetModule the target {@link ModuleName} + */ + private AddExports(final ModuleName sourceModule, + final PackageName packageName, + final ModuleName targetModule) { + this.sourceModule = Objects.requireNonNull(sourceModule, "The source ModuleName must not be null"); + this.packageName = Objects.requireNonNull(packageName, "The PackageName must not be null"); + this.targetModule = Objects.requireNonNull(targetModule, "The target ModuleName must not be null"); + } + + @Override + public boolean isSupported(final JDKVersion jdkVersion, + final ConfigurationBuilder options) { + + return jdkVersion.isModular(); + } + + @Override + public Stream resolve(final Platform platform, + final ConfigurationBuilder options) { + + final var arguments = new ArrayList(2); + + arguments.add("--add-exports"); + arguments.add(this.sourceModule.get() + "/" + this.packageName.get() + "=" + this.targetModule.get()); + + return arguments.stream(); + } + + @Override + public Optional> getTableSupplier() { + return Optional.of(() -> { + final Table table = Table.create(); + table.options().add(TableName.of("Add Exports")); + table.options().add(CellSeparator.of("=")); + return table; + }); + } + + @Override + public void tabularize(final Table table) { + table.addRow( + Cell.of(this.sourceModule.get() + "/" + this.packageName.get()), + Cell.of(this.targetModule.get())); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof final AddExports that)) { + return false; + } + return Objects.equals(this.sourceModule, that.sourceModule) + && Objects.equals(this.packageName, that.packageName) + && Objects.equals(this.targetModule, that.targetModule); + } + + @Override + public int hashCode() { + return Objects.hash(this.sourceModule, this.packageName, this.targetModule); + } + + /** + * Creates an {@link AddExports}. + * + * @param sourceModule the source {@link ModuleName} + * @param packageName the {@link PackageName} + * @param targetModule the target {@link ModuleName} + * @return a new {@link AddExports} + */ + public static AddExports of(final ModuleName sourceModule, + final PackageName packageName, + final ModuleName targetModule) { + return new AddExports(sourceModule, packageName, targetModule); + } + + /** + * Creates an {@link AddExports}. + * + * @param sourceModule the source module name + * @param packageName the package name + * @param targetModule the target module name + * @return a new {@link AddExports} + */ + public static AddExports of(final String sourceModule, + final String packageName, + final String targetModule) { + return new AddExports(ModuleName.of(sourceModule), PackageName.of(packageName), ModuleName.of(targetModule)); + } + + /** + * Detects the {@link Stream} of {@link AddExports} directives for this Virtual Machine. + * + * @return the {@link Stream} of {@link AddExports} + */ + public static Stream detect() { + return ManagementFactory.getRuntimeMXBean() + .getInputArguments() + .stream() + .filter(argument -> argument.startsWith("--add-exports=")) + .map(argument -> argument.substring("--add-exports=".length())) + .map(value -> value.split("=", 2)) + .map(parts -> { + final var moduleAndPackage = parts[0].split("/", 2); + return AddExports.of(moduleAndPackage[0], moduleAndPackage[1], parts[1]); + }); + } +} diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/option/AddOpens.java b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddOpens.java new file mode 100644 index 0000000..f31b97c --- /dev/null +++ b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddOpens.java @@ -0,0 +1,179 @@ +package build.spawn.jdk.option; + +/*- + * #%L + * Spawn JDK + * %% + * Copyright (C) 2026 Workday, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import build.base.configuration.CollectedOption; +import build.base.configuration.ConfigurationBuilder; +import build.base.option.JDKVersion; +import build.base.table.Cell; +import build.base.table.Table; +import build.base.table.Tabular; +import build.base.table.option.CellSeparator; +import build.base.table.option.TableName; +import build.spawn.application.Platform; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A {@link JDKOption} to specify a {@code --add-opens=source-module/package=target-module}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +public class AddOpens + implements JDKOption, CollectedOption, Tabular { + + /** + * The source {@link ModuleName} that owns the package. + */ + private final ModuleName sourceModule; + + /** + * The {@link PackageName} to open. + */ + private final PackageName packageName; + + /** + * The target {@link ModuleName} to open to. + */ + private final ModuleName targetModule; + + /** + * Constructs an {@link AddOpens}. + * + * @param sourceModule the source {@link ModuleName} + * @param packageName the {@link PackageName} + * @param targetModule the target {@link ModuleName} + */ + private AddOpens(final ModuleName sourceModule, + final PackageName packageName, + final ModuleName targetModule) { + this.sourceModule = Objects.requireNonNull(sourceModule, "The source ModuleName must not be null"); + this.packageName = Objects.requireNonNull(packageName, "The PackageName must not be null"); + this.targetModule = Objects.requireNonNull(targetModule, "The target ModuleName must not be null"); + } + + @Override + public boolean isSupported(final JDKVersion jdkVersion, + final ConfigurationBuilder options) { + + return jdkVersion.isModular(); + } + + @Override + public Stream resolve(final Platform platform, + final ConfigurationBuilder options) { + + final var arguments = new ArrayList(2); + + arguments.add("--add-opens"); + arguments.add(this.sourceModule.get() + "/" + this.packageName.get() + "=" + this.targetModule.get()); + + return arguments.stream(); + } + + @Override + public Optional> getTableSupplier() { + return Optional.of(() -> { + final Table table = Table.create(); + table.options().add(TableName.of("Add Opens")); + table.options().add(CellSeparator.of("=")); + return table; + }); + } + + @Override + public void tabularize(final Table table) { + table.addRow( + Cell.of(this.sourceModule.get() + "/" + this.packageName.get()), + Cell.of(this.targetModule.get())); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof final AddOpens that)) { + return false; + } + return Objects.equals(this.sourceModule, that.sourceModule) + && Objects.equals(this.packageName, that.packageName) + && Objects.equals(this.targetModule, that.targetModule); + } + + @Override + public int hashCode() { + return Objects.hash(this.sourceModule, this.packageName, this.targetModule); + } + + /** + * Creates an {@link AddOpens}. + * + * @param sourceModule the source {@link ModuleName} + * @param packageName the {@link PackageName} + * @param targetModule the target {@link ModuleName} + * @return a new {@link AddOpens} + */ + public static AddOpens of(final ModuleName sourceModule, + final PackageName packageName, + final ModuleName targetModule) { + return new AddOpens(sourceModule, packageName, targetModule); + } + + /** + * Creates an {@link AddOpens}. + * + * @param sourceModule the source module name + * @param packageName the package name + * @param targetModule the target module name + * @return a new {@link AddOpens} + */ + public static AddOpens of(final String sourceModule, + final String packageName, + final String targetModule) { + return new AddOpens(ModuleName.of(sourceModule), PackageName.of(packageName), ModuleName.of(targetModule)); + } + + /** + * Detects the {@link Stream} of {@link AddOpens} directives for this Virtual Machine. + * + * @return the {@link Stream} of {@link AddOpens} + */ + public static Stream detect() { + return ManagementFactory.getRuntimeMXBean() + .getInputArguments() + .stream() + .filter(argument -> argument.startsWith("--add-opens=")) + .map(argument -> argument.substring("--add-opens=".length())) + .map(value -> value.split("=", 2)) + .map(parts -> { + final var moduleAndPackage = parts[0].split("/", 2); + return AddOpens.of(moduleAndPackage[0], moduleAndPackage[1], parts[1]); + }); + } +} diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/option/AddReads.java b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddReads.java new file mode 100644 index 0000000..0fa8ce3 --- /dev/null +++ b/spawn-jdk/src/main/java/build/spawn/jdk/option/AddReads.java @@ -0,0 +1,161 @@ +package build.spawn.jdk.option; + +/*- + * #%L + * Spawn JDK + * %% + * Copyright (C) 2026 Workday, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import build.base.configuration.CollectedOption; +import build.base.configuration.ConfigurationBuilder; +import build.base.option.JDKVersion; +import build.base.table.Cell; +import build.base.table.Table; +import build.base.table.Tabular; +import build.base.table.option.CellSeparator; +import build.base.table.option.TableName; +import build.spawn.application.Platform; + +import java.lang.management.ManagementFactory; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +/** + * A {@link JDKOption} to specify a {@code --add-reads=source-module=target-module}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +public class AddReads + implements JDKOption, CollectedOption, Tabular { + + /** + * The source {@link ModuleName} that is granted readability. + */ + private final ModuleName sourceModule; + + /** + * The target {@link ModuleName} to read from. + */ + private final ModuleName targetModule; + + /** + * Constructs an {@link AddReads}. + * + * @param sourceModule the source {@link ModuleName} + * @param targetModule the target {@link ModuleName} + */ + private AddReads(final ModuleName sourceModule, + final ModuleName targetModule) { + this.sourceModule = Objects.requireNonNull(sourceModule, "The source ModuleName must not be null"); + this.targetModule = Objects.requireNonNull(targetModule, "The target ModuleName must not be null"); + } + + @Override + public boolean isSupported(final JDKVersion jdkVersion, + final ConfigurationBuilder options) { + + return jdkVersion.isModular(); + } + + @Override + public Stream resolve(final Platform platform, + final ConfigurationBuilder options) { + + final var arguments = new ArrayList(2); + + arguments.add("--add-reads"); + arguments.add(this.sourceModule.get() + "=" + this.targetModule.get()); + + return arguments.stream(); + } + + @Override + public Optional> getTableSupplier() { + return Optional.of(() -> { + final Table table = Table.create(); + table.options().add(TableName.of("Add Reads")); + table.options().add(CellSeparator.of("=")); + return table; + }); + } + + @Override + public void tabularize(final Table table) { + table.addRow(Cell.of(this.sourceModule.get()), Cell.of(this.targetModule.get())); + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof final AddReads that)) { + return false; + } + return Objects.equals(this.sourceModule, that.sourceModule) + && Objects.equals(this.targetModule, that.targetModule); + } + + @Override + public int hashCode() { + return Objects.hash(this.sourceModule, this.targetModule); + } + + /** + * Creates an {@link AddReads}. + * + * @param sourceModule the source {@link ModuleName} + * @param targetModule the target {@link ModuleName} + * @return a new {@link AddReads} + */ + public static AddReads of(final ModuleName sourceModule, + final ModuleName targetModule) { + return new AddReads(sourceModule, targetModule); + } + + /** + * Creates an {@link AddReads}. + * + * @param sourceModule the source module name + * @param targetModule the target module name + * @return a new {@link AddReads} + */ + public static AddReads of(final String sourceModule, + final String targetModule) { + return new AddReads(ModuleName.of(sourceModule), ModuleName.of(targetModule)); + } + + /** + * Detects the {@link Stream} of {@link AddReads} directives for this Virtual Machine. + * + * @return the {@link Stream} of {@link AddReads} + */ + public static Stream detect() { + return ManagementFactory.getRuntimeMXBean() + .getInputArguments() + .stream() + .filter(argument -> argument.startsWith("--add-reads=")) + .map(argument -> argument.substring("--add-reads=".length())) + .map(value -> value.split("=", 2)) + .map(parts -> AddReads.of(parts[0], parts[1])); + } +} diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/option/ModuleName.java b/spawn-jdk/src/main/java/build/spawn/jdk/option/ModuleName.java index 9116c9e..f32d39b 100644 --- a/spawn-jdk/src/main/java/build/spawn/jdk/option/ModuleName.java +++ b/spawn-jdk/src/main/java/build/spawn/jdk/option/ModuleName.java @@ -45,6 +45,11 @@ public class ModuleName { */ public static final ModuleName ALL_MODULE_PATH = new ModuleName("ALL-MODULE-PATH"); + /** + * The {@code ALL-UNNAMED} {@link ModuleName}. + */ + public static final ModuleName ALL_UNNAMED = new ModuleName("ALL-UNNAMED"); + /** * The module name. */ diff --git a/spawn-jdk/src/main/java/build/spawn/jdk/option/PackageName.java b/spawn-jdk/src/main/java/build/spawn/jdk/option/PackageName.java new file mode 100644 index 0000000..adfb005 --- /dev/null +++ b/spawn-jdk/src/main/java/build/spawn/jdk/option/PackageName.java @@ -0,0 +1,91 @@ +package build.spawn.jdk.option; + +/*- + * #%L + * Spawn JDK + * %% + * Copyright (C) 2026 Workday, Inc. + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.util.Objects; + +/** + * A value type representing the name of a Java package. + * + * @author reed.vonredwitz + * @since May-2026 + */ +public class PackageName { + + /** + * The package name. + */ + private final String packageName; + + /** + * Constructs a {@link PackageName}. + * + * @param packageName the package name + */ + private PackageName(final String packageName) { + this.packageName = Objects.requireNonNull(packageName, "The Package Name must not be null") + .trim(); + + if (this.packageName.isEmpty()) { + throw new IllegalArgumentException("The Package Name must not be empty"); + } + } + + /** + * Obtains the {@link String} representation of the {@link PackageName}. + * + * @return the {@link String} representation + */ + public String get() { + return this.packageName; + } + + @Override + public boolean equals(final Object object) { + if (this == object) { + return true; + } + if (!(object instanceof final PackageName that)) { + return false; + } + return Objects.equals(this.packageName, that.packageName); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.packageName); + } + + @Override + public String toString() { + return this.packageName; + } + + /** + * Creates a {@link PackageName} given a name. + * + * @param packageName the package name + * @return a new {@link PackageName} + */ + public static PackageName of(final String packageName) { + return new PackageName(packageName); + } +} diff --git a/spawn-jdk/src/test/java/build/spawn/jdk/option/AddExportsTests.java b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddExportsTests.java new file mode 100644 index 0000000..7f73e3f --- /dev/null +++ b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddExportsTests.java @@ -0,0 +1,62 @@ +package build.spawn.jdk.option; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AddExports}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +class AddExportsTests { + + /** + * Ensure {@link AddExports} resolves to the expected command-line tokens. + */ + @Test + void shouldResolveToExpectedTokens() { + final var addExports = AddExports.of("com.example.app", "com.example.app.internal", "com.example.lib"); + + assertThat(addExports.resolve(null, null)) + .containsExactly("--add-exports", "com.example.app/com.example.app.internal=com.example.lib"); + } + + /** + * Ensure {@link AddExports} resolves correctly when the target is {@code ALL-UNNAMED}. + */ + @Test + void shouldResolveWithAllUnnamedTarget() { + final var addExports = AddExports.of( + ModuleName.of("com.example.app"), + PackageName.of("com.example.app.internal"), + ModuleName.ALL_UNNAMED); + + assertThat(addExports.resolve(null, null)) + .containsExactly("--add-exports", "com.example.app/com.example.app.internal=ALL-UNNAMED"); + } + + /** + * Ensure two {@link AddExports} instances with the same values are equal. + */ + @Test + void shouldEqualWhenSameValues() { + final var a = AddExports.of("com.example.app", "com.example.app.internal", "com.example.lib"); + final var b = AddExports.of("com.example.app", "com.example.app.internal", "com.example.lib"); + + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } + + /** + * Ensure two {@link AddExports} instances with different values are not equal. + */ + @Test + void shouldNotEqualWhenDifferentValues() { + final var a = AddExports.of("com.example.app", "com.example.app.internal", "com.example.lib"); + final var b = AddExports.of("com.example.app", "com.example.app.other", "com.example.lib"); + + assertThat(a).isNotEqualTo(b); + } +} diff --git a/spawn-jdk/src/test/java/build/spawn/jdk/option/AddOpensTests.java b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddOpensTests.java new file mode 100644 index 0000000..7430dc1 --- /dev/null +++ b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddOpensTests.java @@ -0,0 +1,62 @@ +package build.spawn.jdk.option; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AddOpens}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +class AddOpensTests { + + /** + * Ensure {@link AddOpens} resolves to the expected command-line tokens. + */ + @Test + void shouldResolveToExpectedTokens() { + final var addOpens = AddOpens.of("com.example.app", "com.example.app.internal", "com.example.lib"); + + assertThat(addOpens.resolve(null, null)) + .containsExactly("--add-opens", "com.example.app/com.example.app.internal=com.example.lib"); + } + + /** + * Ensure {@link AddOpens} resolves correctly when the target is {@code ALL-UNNAMED}. + */ + @Test + void shouldResolveWithAllUnnamedTarget() { + final var addOpens = AddOpens.of( + ModuleName.of("com.example.app"), + PackageName.of("com.example.app.internal"), + ModuleName.ALL_UNNAMED); + + assertThat(addOpens.resolve(null, null)) + .containsExactly("--add-opens", "com.example.app/com.example.app.internal=ALL-UNNAMED"); + } + + /** + * Ensure two {@link AddOpens} instances with the same values are equal. + */ + @Test + void shouldEqualWhenSameValues() { + final var a = AddOpens.of("com.example.app", "com.example.app.internal", "com.example.lib"); + final var b = AddOpens.of("com.example.app", "com.example.app.internal", "com.example.lib"); + + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } + + /** + * Ensure two {@link AddOpens} instances with different values are not equal. + */ + @Test + void shouldNotEqualWhenDifferentValues() { + final var a = AddOpens.of("com.example.app", "com.example.app.internal", "com.example.lib"); + final var b = AddOpens.of("com.example.app", "com.example.app.other", "com.example.lib"); + + assertThat(a).isNotEqualTo(b); + } +} diff --git a/spawn-jdk/src/test/java/build/spawn/jdk/option/AddReadsTests.java b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddReadsTests.java new file mode 100644 index 0000000..cbdbc9a --- /dev/null +++ b/spawn-jdk/src/test/java/build/spawn/jdk/option/AddReadsTests.java @@ -0,0 +1,59 @@ +package build.spawn.jdk.option; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link AddReads}. + * + * @author reed.vonredwitz + * @since May-2026 + */ +class AddReadsTests { + + /** + * Ensure {@link AddReads} resolves to the expected command-line tokens. + */ + @Test + void shouldResolveToExpectedTokens() { + final var addReads = AddReads.of("com.example.app", "com.example.lib"); + + assertThat(addReads.resolve(null, null)) + .containsExactly("--add-reads", "com.example.app=com.example.lib"); + } + + /** + * Ensure {@link AddReads} resolves correctly when the target is {@code ALL-UNNAMED}. + */ + @Test + void shouldResolveWithAllUnnamedTarget() { + final var addReads = AddReads.of(ModuleName.of("com.example.app"), ModuleName.ALL_UNNAMED); + + assertThat(addReads.resolve(null, null)) + .containsExactly("--add-reads", "com.example.app=ALL-UNNAMED"); + } + + /** + * Ensure two {@link AddReads} instances with the same values are equal. + */ + @Test + void shouldEqualWhenSameValues() { + final var a = AddReads.of("com.example.app", "com.example.lib"); + final var b = AddReads.of("com.example.app", "com.example.lib"); + + assertThat(a).isEqualTo(b); + assertThat(a.hashCode()).isEqualTo(b.hashCode()); + } + + /** + * Ensure two {@link AddReads} instances with different values are not equal. + */ + @Test + void shouldNotEqualWhenDifferentValues() { + final var a = AddReads.of("com.example.app", "com.example.lib"); + final var b = AddReads.of("com.example.app", "com.example.other"); + + assertThat(a).isNotEqualTo(b); + } +}