diff --git a/README.md b/README.md
index 8e671b08a..0f716c2fa 100644
--- a/README.md
+++ b/README.md
@@ -75,17 +75,18 @@ To build EqualsVerifier, you need [Maven](https://maven.apache.org/). Just call
There are several Maven profiles that can be enabled or disabled:
-| profile | activation | purpose |
-| ---------------------- | ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| `modules-jdk8` | JDK 8 up to but not including 11 | Build only the modules that are compatible with Java 8 and up. |
-| `modules-jdk11` | JDK 11 up to but not including 16 | Build only the modules that are compatible with Java 11 and up. |
-| `modules-jdk16` | JDK 16 | Build only the modules that are compatible with Java 16 and up. |
-| `modules-jdk17` | JDK 17 and up | Build all modules and build releaseable artifacts. |
-| `static-analysis` | JDK 17 and up, _and_ `disableStaticAnalysis` property must be off | Run static analysis checks. This only happens on a recent JDK. Can be disabled by running `mvn verify -DdisableStaticAnalysis` |
-| `release-verification` | JDK 17 and up, _and_ `disableReleaseVerification` property must be off | Run release verification checks. This only happens on a recent JDK. Can be disabled by running `mvn verify -DdisableReleaseVerification` |
-| `argline-preview` | `preview` property must be on | Enable Java preview features. Can be activated by running `mvn verify -Dpreview` |
-| `argline-experimental` | `experimental` property must be on | Enables ByteBuddy experimental features; useful for testing EqualsVerifier on Early Access JDK builds. Can be activated by running `mvn verify -Dexperimental` |
-| `pitest` | `pitest` property must be on | Used by PITest integration on GitHub. Can be activated by running `mvn verify -Dpitest` |
+| profile | activation | purpose |
+| ------------------------------ | ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `modules-jdk11` | JDK 11 up to but not including 16 | Build only the modules that are compatible with Java 11 and up. |
+| `modules-jdk16` | JDK 16 | Build only the modules that are compatible with Java 16 and up. |
+| `modules-jdk17` | JDK 17 up to but not including 21 | Build only the modules that are compatible with Java 17 and up. |
+| `modules-jdk21` | JDK 21 and up | Build all modules and build releaseable artifacts. |
+| `modules-jdk21-without-pitest` | JDK 21 and up, but `pitest` property must be off | Run release verification checks (which are incompatible with PITest) |
+| `static-analysis` | `disableStaticAnalysis` property must be off | Run static analysis checks. Can be disabled by running `mvn verify -DdisableStaticAnalysis` |
+| `static-analysis-checkstyle` | `disableStaticAnalysis` property must be off | Used by the Checkstyle project for their regression tests - see ([Issue 586](https://github.com/jqno/equalsverifier/issues/586)) |
+| `argline-preview` | `preview` property must be on | Enable Java preview features. Can be activated by running `mvn verify -Dpreview` |
+| `argline-experimental` | `experimental` property must be on | Enables ByteBuddy experimental features; useful for testing EqualsVerifier on Early Access JDK builds. Can be activated by running `mvn verify -Dexperimental` |
+| `pitest` | `pitest` property must be on | Used by PITest integration on GitHub. Can be activated by running `mvn verify -Dpitest` |
## Formatting
@@ -109,6 +110,7 @@ Here's a description of the modules:
| equalsverifier-release-main | release assembly for jar with dependencies |
| equalsverifier-release-nodep | release assembly for fat jar (with dependencies shaded in) |
| equalsverifier-release-verify | validation tests for the releases |
+| equalsverifier-test-jpms | tests for the Java Platform Module System |
| equalsverifier-test-kotlin | tests for Kotlin classes |
## Signed JAR
@@ -129,8 +131,8 @@ The signed JAR itself can be found in [this repo](https://github.com/jqno/equals
To generate the website
-- Using Docker: start the server by running `docker-compose up` or `docker compose run jekyll serve`.
-- Using Jekyll: install the Ruby 3.x toolchain and run `bundle exec jekyll serve --watch`
+- Using Docker: start the server by running `docker-compose up` or `docker compose run jekyll serve`.
+- Using Jekyll: install the Ruby 3.x toolchain and run `bundle exec jekyll serve --watch`
Note that thepage uses the [TilburgsAns](https://www.tilburgsans.nl/) font but references it from the main site at [jqno.nl](https://jqno.nl). In development, it will fall back to a `sans-serif` font. See the font license [here](assets/tilburgsans/Ans%20Font%20License-AFL.pdf).
diff --git a/build/checkstyle-config.xml b/build/checkstyle-config.xml
index beba271a4..594d28e1d 100644
--- a/build/checkstyle-config.xml
+++ b/build/checkstyle-config.xml
@@ -7,6 +7,10 @@
+
+
+
+
diff --git a/docs/_manual/14-jpms.md b/docs/_manual/14-jpms.md
index 6ee8be50c..08d8658fe 100644
--- a/docs/_manual/14-jpms.md
+++ b/docs/_manual/14-jpms.md
@@ -37,9 +37,9 @@ module my.module {
}
{% endhighlight %}
-Note that the line `requires net.bytebuddy` is not necessary if you use the uberjar dependency `equalsverifier-nodep`.
+Note that the line `requires net.bytebuddy` is not necessary (and in fact will give a compilation error) if you use the uberjar dependency `equalsverifier-nodep`.
-Note that if you do this, and you have model classes or dependencies for model classes in other packages, you will have to open these packages as well, or provide prefab values for these dependencies:
+Also note that if you do this, and you have model classes or dependencies for model classes in other packages, you will have to open these packages as well, or provide prefab values for these dependencies:
{% highlight java %}
import my.module.package.somewhere.inaccessible.Bar;
diff --git a/docs/_pages/resources.md b/docs/_pages/resources.md
index 305fa0f54..ea2a43b96 100644
--- a/docs/_pages/resources.md
+++ b/docs/_pages/resources.md
@@ -2,6 +2,7 @@
title: Additional resources
permalink: /resources/
---
+* [Javadoc](https://javadoc.io/doc/nl.jqno.equalsverifier/equalsverifier/latest/index.html)
* [FAQ](/equalsverifier/faq)
* [Changelog](https://github.com/jqno/equalsverifier/blob/main/CHANGELOG.md)
* Migration guides: [[2.x to 3.x](/equalsverifier/migration2to3)] [[1.x to 2.x](/equalsverifier/migration1to2)]
diff --git a/equalsverifier-core/src/main/java/module-info.java b/equalsverifier-core/src/main/java/module-info.java
new file mode 100644
index 000000000..f4a355b2e
--- /dev/null
+++ b/equalsverifier-core/src/main/java/module-info.java
@@ -0,0 +1,19 @@
+// When making changes to this file, make sure they are
+// reflected in equalsverifier-release-nodep/../module-info.java!
+module nl.jqno.equalsverifier {
+ exports nl.jqno.equalsverifier;
+ exports nl.jqno.equalsverifier.api;
+
+ // Direct dependencies
+ requires net.bytebuddy;
+ requires org.objenesis;
+
+ // Built-in prefab values
+ requires static com.google.common;
+ requires static java.desktop;
+ requires static java.naming;
+ requires static java.rmi;
+ requires static java.sql;
+ requires static javafx.base;
+ requires static org.joda.time;
+}
diff --git a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java
index c3975f9b6..537d7d2c3 100644
--- a/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java
+++ b/equalsverifier-core/src/main/java/nl/jqno/equalsverifier/internal/reflection/Instantiator.java
@@ -1,8 +1,6 @@
package nl.jqno.equalsverifier.internal.reflection;
import static nl.jqno.equalsverifier.internal.reflection.Util.classForName;
-import static nl.jqno.equalsverifier.internal.reflection.Util.classes;
-import static nl.jqno.equalsverifier.internal.reflection.Util.objects;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Modifier;
@@ -121,19 +119,13 @@ private static String getPackageName(Class> type) {
}
private static ClassLoadingStrategy getClassLoadingStrategy(Class context) {
- if (System.getProperty("java.version").startsWith("1.")) {
- return ClassLoadingStrategy.Default.INJECTION.with(context.getProtectionDomain());
- }
- else {
- ConditionalInstantiator ci = new ConditionalInstantiator("java.lang.invoke.MethodHandles$Lookup");
- Object lookup = ci
- .callFactory(
- "java.lang.invoke.MethodHandles",
- "privateLookupIn",
- classes(Class.class, MethodHandles.Lookup.class),
- objects(context, MethodHandles.lookup()));
+ try {
+ var lookup = MethodHandles.privateLookupIn(context, MethodHandles.lookup());
return ClassLoadingStrategy.UsingLookup.of(lookup);
}
+ catch (IllegalAccessException e) {
+ return ClassLoadingStrategy.Default.INJECTION.with(context.getProtectionDomain());
+ }
}
private static boolean isSystemClass(String className) {
diff --git a/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/ModuleTest.java b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/ModuleTest.java
new file mode 100644
index 000000000..37294f52e
--- /dev/null
+++ b/equalsverifier-core/src/test/java/nl/jqno/equalsverifier/integration/operational/ModuleTest.java
@@ -0,0 +1,13 @@
+package nl.jqno.equalsverifier.integration.operational;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class ModuleTest {
+ @Test
+ void sanity() {
+ // We want the tests in this module to be run on the class path, not the module path.
+ assertThat(getClass().getModule().isNamed()).isFalse();
+ }
+}
diff --git a/equalsverifier-release-main/pom.xml b/equalsverifier-release-main/pom.xml
index a54645e63..c33be52cb 100644
--- a/equalsverifier-release-main/pom.xml
+++ b/equalsverifier-release-main/pom.xml
@@ -61,7 +61,6 @@
true
- nl.jqno.equalsverifier
true
EqualsVerifier
${project.url}
diff --git a/equalsverifier-release-nodep/pom.xml b/equalsverifier-release-nodep/pom.xml
index 991e0c3b9..1804e663f 100644
--- a/equalsverifier-release-nodep/pom.xml
+++ b/equalsverifier-release-nodep/pom.xml
@@ -7,7 +7,7 @@
equalsverifier-parent
3.18.2-SNAPSHOT
- pom
+ jar
equalsverifier-nodep
EqualsVerifier | release fat jar
@@ -22,6 +22,7 @@
+ 9
false
false
@@ -61,7 +62,6 @@
true
- nl.jqno.equalsverifier
true
EqualsVerifier (no dependencies)
${project.url}
diff --git a/equalsverifier-release-nodep/src/main/assembly/assembly.xml b/equalsverifier-release-nodep/src/main/assembly/assembly.xml
index 3f325ffb1..3436f58ea 100644
--- a/equalsverifier-release-nodep/src/main/assembly/assembly.xml
+++ b/equalsverifier-release-nodep/src/main/assembly/assembly.xml
@@ -12,6 +12,14 @@
assemblies/assembly.xml
+
+
+
+
+ .
+ module-info.class
+
+
true
diff --git a/equalsverifier-release-nodep/src/main/java/module-info.java b/equalsverifier-release-nodep/src/main/java/module-info.java
new file mode 100644
index 000000000..36509c575
--- /dev/null
+++ b/equalsverifier-release-nodep/src/main/java/module-info.java
@@ -0,0 +1,13 @@
+module nl.jqno.equalsverifier {
+ exports nl.jqno.equalsverifier;
+ exports nl.jqno.equalsverifier.api;
+
+ // Built-in prefab values
+ requires static com.google.common;
+ requires static java.desktop;
+ requires static java.naming;
+ requires static java.rmi;
+ requires static java.sql;
+ requires static javafx.base;
+ requires static org.joda.time;
+}
diff --git a/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/Placeholder.java b/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/Placeholder.java
new file mode 100644
index 000000000..9ebd365fd
--- /dev/null
+++ b/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/Placeholder.java
@@ -0,0 +1,4 @@
+package nl.jqno.equalsverifier;
+
+// A file needs to exist in this package in order to be able to compile the module-info.java
+public class Placeholder {}
diff --git a/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/api/Placeholder.java b/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/api/Placeholder.java
new file mode 100644
index 000000000..d8b7b18a5
--- /dev/null
+++ b/equalsverifier-release-nodep/src/main/java/nl/jqno/equalsverifier/api/Placeholder.java
@@ -0,0 +1,4 @@
+package nl.jqno.equalsverifier.api;
+
+// A file needs to exist in this package in order to be able to compile the module-info.java
+public class Placeholder {}
diff --git a/equalsverifier-release-verify/pom.xml b/equalsverifier-release-verify/pom.xml
index 1f9ac34a5..7b407463e 100644
--- a/equalsverifier-release-verify/pom.xml
+++ b/equalsverifier-release-verify/pom.xml
@@ -13,10 +13,11 @@
EqualsVerifier | release verify
- 17
+ 21
${project.basedir}/../equalsverifier-release-main/target
${project.basedir}/../equalsverifier-release-nodep/target
+ ${project.basedir}/../equalsverifier-core/target
${project.basedir}/src/test/resources
@@ -76,6 +77,10 @@
${artifact.src.nodep}/equalsverifier-nodep-${project.version}-sources.jar
${artifact.dst}/equalsverifier-nodep-sources.jar
+
+ ${artifact.src.core}/equalsverifier-core-${project.version}.jar
+ ${artifact.dst}/equalsverifier-core.jar
+
@@ -108,6 +113,11 @@
${version.assertj}
test
+
+ net.bytebuddy
+ byte-buddy
+ ${version.bytebuddy}
+
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainClassesJarTest.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainClassesJarTest.java
index 3b0697dc8..10ba3109c 100644
--- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainClassesJarTest.java
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainClassesJarTest.java
@@ -1,5 +1,7 @@
package nl.jqno.equalsverifier.verify_release.jar;
+import static org.assertj.core.api.Assertions.assertThat;
+
import nl.jqno.equalsverifier.verify_release.jar.helper.JarAsserter;
import nl.jqno.equalsverifier.verify_release.jar.helper.JarReader;
import org.junit.jupiter.api.AfterAll;
@@ -47,4 +49,18 @@ void contentOfManifest() {
void versionsOfClassFiles() {
jar.assertVersionsOfClassFiles();
}
+
+ @Test
+ void presenceOfModuleInfoWithDependencies() {
+ jar.assertModuleInfoWithDependencies();
+ }
+
+ @Test
+ void moduleInfoIsIdenticalToCore() throws Exception {
+ try (var coreReader = new JarReader("equalsverifier-core.jar")) {
+ var coreModuleinfo = coreReader.getContentOf("module-info.class");
+ var mainModuleinfo = reader.getContentOf("module-info.class");
+ assertThat(mainModuleinfo).containsExactly(coreModuleinfo);
+ }
+ }
}
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainJavadocJarTest.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainJavadocJarTest.java
index 32b09528a..cca623f22 100644
--- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainJavadocJarTest.java
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/MainJavadocJarTest.java
@@ -26,7 +26,12 @@ static void clean() throws Exception {
}
@Test
- void presenceOfCoreSources() {
- jar.assertPresenceOf("/index.html", EV + "/EqualsVerifier.html");
+ void presenceOfCoreFiles() {
+ jar.assertPresenceOf("/index.html", "/nl.jqno.equalsverifier" + EV + "/EqualsVerifier.html");
+ }
+
+ @Test
+ void absenceOfNonExportedPackages() {
+ jar.assertAbsenceOf("/nl.jqno.equalsverifier" + EV + "/internal");
}
}
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepClassesJarTest.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepClassesJarTest.java
index de8d47de2..b5243d428 100644
--- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepClassesJarTest.java
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepClassesJarTest.java
@@ -52,4 +52,9 @@ void versionsOfClassFiles() {
void versionsOfEmbeddedDependencies() {
jar.assertVersionsOfEmbeddedClassFiles();
}
+
+ @Test
+ void presenceOfModuleInfoWithoutDependencies() {
+ jar.assertModuleInfoWithoutDependencies();
+ }
}
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepJavadocJarTest.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepJavadocJarTest.java
index f24c27709..ac8e9b620 100644
--- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepJavadocJarTest.java
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/NodepJavadocJarTest.java
@@ -26,7 +26,12 @@ static void clean() throws Exception {
}
@Test
- void presenceOfCoreSources() {
- jar.assertPresenceOf("/index.html", EV + "/EqualsVerifier.html");
+ void presenceOfCoreFiles() {
+ jar.assertPresenceOf("/index.html", "/nl.jqno.equalsverifier" + EV + "/EqualsVerifier.html");
+ }
+
+ @Test
+ void absenceOfNonExportedPackages() {
+ jar.assertAbsenceOf("/nl.jqno.equalsverifier" + EV + "/internal");
}
}
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java
index 78d1a6263..d2a7a582d 100644
--- a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/JarAsserter.java
@@ -2,6 +2,8 @@
import static org.assertj.core.api.Assertions.assertThat;
+import java.util.Set;
+
public class JarAsserter {
public static final String EV = "/nl/jqno/equalsverifier";
@@ -14,6 +16,7 @@ public JarAsserter(JarReader reader) {
public void assertPresenceOfCoreClasses() {
assertPresenceOf(
+ "/module-info.class",
EV + "/EqualsVerifier.class",
EV + "/internal/reflection/ClassProbe.class",
EV + "/internal/checkers/HierarchyChecker.class");
@@ -46,12 +49,17 @@ public void assertAbsenceOf(String... fileNames) {
assertThat(fileNames).allMatch(fn -> !entries.contains(fn), "absent from " + reader.getFilename());
}
+ public void assertAbsenceOfDirectory(String... dirNames) {
+ var dirs = Set.of(dirNames);
+ var entries = reader.getEntries();
+ assertThat(entries).noneMatch(dirs::contains);
+ }
+
public void assertContentOfManifest(String implementationTitle) {
var filename = "/META-INF/MANIFEST.MF";
var manifest = new String(reader.getContentOf(filename));
assertThat(manifest)
.satisfies(
- m -> assertContains("Automatic-Module-Name: nl.jqno.equalsverifier", m, filename),
m -> assertContains("Implementation-Title: " + implementationTitle, m, filename),
m -> assertContains("Implementation-Version: ", m, filename),
m -> assertContains("Multi-Release: true", m, filename),
@@ -92,4 +100,26 @@ private void assertVersionOfClassFile(int expectedVersion, String innerFilename)
+ actualVersion;
assertThat(actualVersion).as(description).isEqualTo((byte) expectedVersion);
}
+
+ public void assertModuleInfoWithDependencies() {
+ var moduleinfo = reader.getContentOf("module-info.class");
+ var module = ModuleInfoAsserter.parse(moduleinfo);
+ assertThat(module)
+ .satisfies(
+ m -> m.assertName("nl.jqno.equalsverifier"),
+ m -> m.assertExports("nl/jqno/equalsverifier", "nl/jqno/equalsverifier/api"),
+ m -> m.assertRequires("net.bytebuddy", ""),
+ m -> m.assertRequires("org.objenesis", ""));
+ }
+
+ public void assertModuleInfoWithoutDependencies() {
+ var moduleinfo = reader.getContentOf("module-info.class");
+ var module = ModuleInfoAsserter.parse(moduleinfo);
+ assertThat(module)
+ .satisfies(
+ m -> m.assertName("nl.jqno.equalsverifier"),
+ m -> m.assertExports("nl/jqno/equalsverifier", "nl/jqno/equalsverifier/api"),
+ m -> m.assertDoesNotRequire("net.bytebuddy"),
+ m -> m.assertDoesNotRequire("org.objenesis"));
+ }
}
diff --git a/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/ModuleInfoAsserter.java b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/ModuleInfoAsserter.java
new file mode 100644
index 000000000..90862db7b
--- /dev/null
+++ b/equalsverifier-release-verify/src/test/java/nl/jqno/equalsverifier/verify_release/jar/helper/ModuleInfoAsserter.java
@@ -0,0 +1,84 @@
+package nl.jqno.equalsverifier.verify_release.jar.helper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.*;
+
+import net.bytebuddy.jar.asm.*;
+
+public class ModuleInfoAsserter {
+
+ private String moduleName;
+ private final Set exports = new HashSet<>();
+ private final Map requires = new HashMap<>();
+
+ public static ModuleInfoAsserter parse(byte[] moduleinfo) {
+ var result = new ModuleInfoAsserter();
+ new ClassReader(moduleinfo).accept(new ModuleInfoParser(result), 0);
+ return result;
+ }
+
+ public void assertName(String name) {
+ assertThat(moduleName).isEqualTo(name);
+ }
+
+ public void assertExports(String... names) {
+ assertThat(exports).containsOnly(names);
+ }
+
+ public void assertRequires(String require, String modifier) {
+ assertThat(requires).containsEntry(require, modifier);
+ }
+
+ public void assertDoesNotRequire(String require) {
+ assertThat(requires).doesNotContainKey(require);
+ }
+
+ private static final class ModuleInfoParser extends ClassVisitor {
+
+ private final ModuleInfoAsserter asserter;
+
+ private ModuleInfoParser(ModuleInfoAsserter asserter) {
+ super(Opcodes.ASM9);
+ this.asserter = asserter;
+ }
+
+ @Override
+ public ModuleVisitor visitModule(String name, int access, String version) {
+ asserter.moduleName = name;
+ return new ModuleVisitorExtension(asserter);
+ }
+ }
+
+ private static final class ModuleVisitorExtension extends ModuleVisitor {
+ private final ModuleInfoAsserter asserter;
+
+ private ModuleVisitorExtension(ModuleInfoAsserter asserter) {
+ super(Opcodes.ASM9);
+ this.asserter = asserter;
+ }
+
+ @Override
+ public void visitExport(String packaze, int access, String... modules) {
+ asserter.exports.add(packaze);
+ }
+
+ @Override
+ public void visitRequire(String module, int access, String version) {
+ var modifiers = "";
+ if ((access & Opcodes.ACC_TRANSITIVE) != 0) {
+ modifiers += "transitive ";
+ }
+ if ((access & Opcodes.ACC_STATIC_PHASE) != 0) {
+ modifiers += "static ";
+ }
+ if ((access & Opcodes.ACC_SYNTHETIC) != 0) {
+ modifiers += "synthetic ";
+ }
+ if ((access & Opcodes.ACC_MANDATED) != 0) {
+ modifiers += "mandated ";
+ }
+ asserter.requires.put(module, modifiers);
+ }
+ }
+}
diff --git a/equalsverifier-test-jpms/pom.xml b/equalsverifier-test-jpms/pom.xml
new file mode 100644
index 000000000..631207290
--- /dev/null
+++ b/equalsverifier-test-jpms/pom.xml
@@ -0,0 +1,51 @@
+
+
+
+ 4.0.0
+
+ nl.jqno.equalsverifier
+ equalsverifier-parent
+ 3.18.2-SNAPSHOT
+
+ jar
+
+ equalsverifier-test-jpms
+ EqualsVerifier | test JPMS
+
+
+ 21
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ true
+
+
+
+
+
+
+
+ nl.jqno.equalsverifier
+ equalsverifier-core
+ ${project.version}
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter
+ ${version.junit-jupiter}
+ test
+
+
+ org.assertj
+ assertj-core
+ ${version.assertj}
+ test
+
+
+
diff --git a/equalsverifier-test-jpms/src/test/java/it/EverythingWorksInTheModularWorldTest.java b/equalsverifier-test-jpms/src/test/java/it/EverythingWorksInTheModularWorldTest.java
new file mode 100644
index 000000000..a0c81e492
--- /dev/null
+++ b/equalsverifier-test-jpms/src/test/java/it/EverythingWorksInTheModularWorldTest.java
@@ -0,0 +1,138 @@
+package it;
+
+import java.util.Objects;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
+
+public class EverythingWorksInTheModularWorldTest {
+
+ @Test
+ void classCanBeVerified() {
+ EqualsVerifier.forClass(ClassPoint.class).verify();
+ }
+
+ @Test
+ void classContainingClassCanBeVerifier() {
+ EqualsVerifier.forClass(ClassPointContainer.class).verify();
+ }
+
+ @Test
+ @Disabled("It's impossble to load `equalsverifier-16` in the module world "
+ + "because it creates a split path. We can enable this test again "
+ + "when EqualsVerifier's baseline becomes Java 17")
+ void recordCanBeVerified() {
+ EqualsVerifier.forClass(RecordPoint.class).verify();
+ }
+
+ @Test
+ @Disabled("It's impossble to load `equalsverifier-16` in the module world "
+ + "because it creates a split path. We can enable this test again "
+ + "when EqualsVerifier's baseline becomes Java 17")
+ void recordContainingRecordCanBeVerified() {
+ EqualsVerifier.forClass(RecordPointContainer.class).verify();
+ }
+
+ @Test
+ void classContainingFieldsFromOtherJdkModulesCanBeVerifier() {
+ EqualsVerifier.forClass(FieldsFromJdkModulesHaver.class).verify();
+ }
+
+ @Test
+ void nonFinalClassCanBeVerified() {
+ EqualsVerifier.forClass(NonFinal.class).verify();
+ }
+
+ static final class ClassPoint {
+ private final int x;
+ private final int y;
+
+ private ClassPoint(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ClassPoint other && x == other.x && y == other.y;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x, y);
+ }
+ }
+
+ static final class ClassPointContainer {
+ private final ClassPoint cp;
+
+ private ClassPointContainer(ClassPoint cp) {
+ this.cp = cp;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ClassPointContainer other && Objects.equals(cp, other.cp);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(cp);
+ }
+ }
+
+ record RecordPoint(int x, int y) {}
+
+ record RecordPointContainer(RecordPoint rp) {}
+
+ static final class FieldsFromJdkModulesHaver {
+ private final java.awt.Color desktopAwtColor;
+ private final java.rmi.server.UID rmiUid;
+ private final java.sql.Date sqlDate;
+ private final javax.naming.Reference namingReference;
+
+ private FieldsFromJdkModulesHaver(
+ java.awt.Color c,
+ java.rmi.server.UID u,
+ java.sql.Date d,
+ javax.naming.Reference r) {
+ this.desktopAwtColor = c;
+ this.rmiUid = u;
+ this.sqlDate = d;
+ this.namingReference = r;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof FieldsFromJdkModulesHaver other
+ && Objects.equals(desktopAwtColor, other.desktopAwtColor)
+ && Objects.equals(rmiUid, other.rmiUid)
+ && Objects.equals(sqlDate, other.sqlDate)
+ && Objects.equals(namingReference, other.namingReference);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(desktopAwtColor, rmiUid, sqlDate, namingReference);
+ }
+ }
+
+ static class NonFinal {
+ private final int i;
+
+ public NonFinal(int i) {
+ this.i = i;
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof NonFinal other && Objects.equals(i, other.i);
+ }
+
+ @Override
+ public final int hashCode() {
+ return Objects.hash(i);
+ }
+ }
+}
diff --git a/equalsverifier-test-jpms/src/test/java/it/ModuleErrorsTest.java b/equalsverifier-test-jpms/src/test/java/it/ModuleErrorsTest.java
new file mode 100644
index 000000000..c13fe154d
--- /dev/null
+++ b/equalsverifier-test-jpms/src/test/java/it/ModuleErrorsTest.java
@@ -0,0 +1,60 @@
+package it;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.text.AttributedString;
+import java.util.Objects;
+
+import nl.jqno.equalsverifier.EqualsVerifier;
+import nl.jqno.equalsverifier.Warning;
+import org.junit.jupiter.api.Test;
+
+/*
+ * Let's hope nobody needs prefab values for `java.text.AttributedString`, because we need a class here from je Java
+ * APIs that doesn't already have prefab values.
+ */
+public class ModuleErrorsTest {
+ @Test
+ void giveProperErrorMessage_whenClassUnderTestIsInaccessible() {
+ assertThatThrownBy(
+ () -> EqualsVerifier
+ .forClass(AttributedString.class)
+ .suppress(Warning.INHERITED_DIRECTLY_FROM_OBJECT)
+ .verify())
+ .isInstanceOf(AssertionError.class)
+ .hasMessageContaining("The class")
+ .hasMessageContaining("Consider opening");
+ }
+
+ @Test
+ void giveProperErrorMessage_whenFieldIsInaccessible() {
+ assertThatThrownBy(() -> EqualsVerifier.forClass(InaccessibleContainer.class).verify())
+ .isInstanceOf(AssertionError.class)
+ .hasMessageContaining("Field x")
+ .hasMessageContaining("Consider opening")
+ .hasMessageContaining("add prefab values");
+ }
+
+ static final class InaccessibleContainer {
+
+ private final AttributedString x;
+
+ public InaccessibleContainer(AttributedString x) {
+ this.x = x;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof InaccessibleContainer)) {
+ return false;
+ }
+ InaccessibleContainer other = (InaccessibleContainer) obj;
+ return Objects.equals(x, other.x);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(x);
+ }
+ }
+}
diff --git a/equalsverifier-test-jpms/src/test/java/it/SanityTest.java b/equalsverifier-test-jpms/src/test/java/it/SanityTest.java
new file mode 100644
index 000000000..e90e9bb41
--- /dev/null
+++ b/equalsverifier-test-jpms/src/test/java/it/SanityTest.java
@@ -0,0 +1,14 @@
+package it;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+public class SanityTest {
+ @Test
+ void sanity() {
+ assertThat(getClass().getModule())
+ .extracting(Module::isNamed, Module::getName)
+ .containsExactly(true, "equalsverifier_jpms_test");
+ }
+}
diff --git a/equalsverifier-test-jpms/src/test/java/module-info.java b/equalsverifier-test-jpms/src/test/java/module-info.java
new file mode 100644
index 000000000..031816d1b
--- /dev/null
+++ b/equalsverifier-test-jpms/src/test/java/module-info.java
@@ -0,0 +1,11 @@
+open module equalsverifier_jpms_test {
+ requires nl.jqno.equalsverifier;
+
+ requires org.junit.jupiter.api;
+ requires org.assertj.core;
+
+ requires java.desktop;
+ requires java.naming;
+ requires java.rmi;
+ requires java.sql;
+}
diff --git a/pom.xml b/pom.xml
index 5c3ed5159..ec58c3cb7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -153,6 +153,7 @@
${version.maven-surefire-plugin}
@{argline.full}
+ false
false
false
@@ -567,6 +568,7 @@
equalsverifier-16
equalsverifier-17
equalsverifier-21
+ equalsverifier-test-jpms
equalsverifier-test-kotlin
equalsverifier-aggregator
equalsverifier-release-main