You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2024/08/01 21:45:13 UTC

(freemarker) branch java-impl-static-linking updated (3b462a5f -> f292ff3f)

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a change to branch java-impl-static-linking
in repository https://gitbox.apache.org/repos/asf/freemarker.git


 discard 3b462a5f Statically linked JavaNImpl classes, utilizing "JEP 238: Multi-Release JAR Files"
     add ebb38fff FREEMARKER-227: Fixed bug, where if classic_compatible mode was true (rarely used), then sequence built-ins, like seq?seq_contains(value), has failed with NullPointerException if values of incompatible types had to be compared. The intended behavior is to convert the values to string, and then compare them (to mimic the behavior of FreeMarker 1.x).
     add ec3fd7c9 Corrected/improved Javadoc related to classicCompatible mode. Fixed issue where setClassicCompatibleAsInt did not mirror the value change in the Properties object. Added more tests for classicCompatible=true behavior. Some minor code cleanup (simplifications allowed by newer Java versions).
     new f292ff3f Statically linked JavaNImpl classes, utilizing "JEP 238: Multi-Release JAR Files"

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (3b462a5f)
            \
             N -- N -- N   refs/heads/java-impl-static-linking (f292ff3f)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../main/java/freemarker/core/Configurable.java    | 159 ++++++++++++---------
 .../src/main/java/freemarker/core/EvalUtil.java    |  14 +-
 .../freemarker/core/ClassicCompatibleTest.java     | 117 +++++++++++++++
 freemarker-manual/src/main/docgen/en_US/book.xml   |  24 +++-
 4 files changed, 236 insertions(+), 78 deletions(-)
 create mode 100644 freemarker-core/src/test/java/freemarker/core/ClassicCompatibleTest.java


(freemarker) 01/01: Statically linked JavaNImpl classes, utilizing "JEP 238: Multi-Release JAR Files"

Posted by dd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch java-impl-static-linking
in repository https://gitbox.apache.org/repos/asf/freemarker.git

commit f292ff3f42eaca823e96e8fdffb3d7bdaecd3ca8
Author: ddekany <dd...@apache.org>
AuthorDate: Tue Jun 4 09:05:52 2024 +0200

    Statically linked JavaNImpl classes, utilizing "JEP 238: Multi-Release JAR Files"
---
 build.gradle.kts                                   |  5 +-
 .../src/main/java/freemarker/core/_Java16.java     |  9 ++
 .../core/{_Java16.java => _Java16Impl.java}        | 24 +++++-
 .../src/main/java/freemarker/core/_Java9.java      |  9 ++
 .../core/{_Java16.java => _Java9Impl.java}         | 22 +++--
 .../main/java/freemarker/core/_JavaVersions.java   | 95 ----------------------
 .../freemarker/ext/beans/ClassIntrospector.java    |  9 +-
 .../ext/beans/ClassIntrospectorBuilder.java        |  9 +-
 .../src/main/java/freemarker/core/_Java16Impl.java | 13 ++-
 .../beans/Java16TestClassLoadingCorrectTest.java   | 23 +++---
 .../ext/beans/NotExportedInternalPackageTest.java  |  3 +
 .../src/main/java/freemarker/core/_Java9Impl.java  | 13 ++-
 .../test/java/freemarker/core/Java9ImplTest.java   | 10 +--
 .../core/Java9TestClassLoadingCorrectTest.java     | 22 ++---
 14 files changed, 109 insertions(+), 157 deletions(-)

diff --git a/build.gradle.kts b/build.gradle.kts
index 85309947..e2575431 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -20,7 +20,7 @@
 import java.io.FileOutputStream
 import java.nio.charset.StandardCharsets
 import java.nio.file.Files
-import java.util.Properties
+import java.util.*
 import java.util.stream.Collectors
 
 plugins {
@@ -144,6 +144,9 @@ tasks.sourcesJar.configure {
         into("META-INF")
     }
 
+    exclude("freemarker/core/_Java9Impl.java")
+    exclude("freemarker/core/_Java16Impl.java")
+
     // Depend on the createBuildInfo task and include the generated file
     dependsOn(createBuildInfo)
     from(buildInfoFile())
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java16.java b/freemarker-core/src/main/java/freemarker/core/_Java16.java
index 19de70a3..396c83bb 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java16.java
+++ b/freemarker-core/src/main/java/freemarker/core/_Java16.java
@@ -26,6 +26,15 @@ import java.util.Set;
  * Used for accessing functionality that's only present in Java 16 or later.
  */
 public interface _Java16 {
+    /**
+     * Using "JEP 238: Multi-Release JAR Files", links to the proper version of the {@link _Java16Impl} class.
+     */
+    _Java16 INSTANCE = new _Java16Impl();
+
+    /**
+     * Tells if Java 16 features can be used in the current run-time environment.
+     */
+    boolean isSupported();
 
     boolean isRecord(Class<?> cl);
 
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java16.java b/freemarker-core/src/main/java/freemarker/core/_Java16Impl.java
similarity index 55%
copy from freemarker-core/src/main/java/freemarker/core/_Java16.java
copy to freemarker-core/src/main/java/freemarker/core/_Java16Impl.java
index 19de70a3..5ae2f102 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java16.java
+++ b/freemarker-core/src/main/java/freemarker/core/_Java16Impl.java
@@ -16,6 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
+
 package freemarker.core;
 
 import java.lang.reflect.Method;
@@ -23,12 +24,27 @@ import java.util.Set;
 
 /**
  * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 16 or later.
+ * Pre-Java-16 implementation of {@link _Java16}.
  */
-public interface _Java16 {
+// We also have a Java 16 versions of this class in freemarker-core16, and we put all versions into
+// the jar artifact via "JEP 238: Multi-Release JAR Files".
+public final class _Java16Impl implements _Java16 {
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
 
-    boolean isRecord(Class<?> cl);
+    @Override
+    public boolean isRecord(Class<?> cl) {
+        throw newNoRecordSupportException();
+    }
 
-    Set<Method> getComponentAccessors(Class<?> recordClass);
+    @Override
+    public Set<Method> getComponentAccessors(Class<?> recordClass){
+        throw newNoRecordSupportException();
+    }
 
+    private UnsupportedOperationException newNoRecordSupportException() {
+        return new UnsupportedOperationException("Record support needs at least Java 16");
+    }
 }
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java9.java b/freemarker-core/src/main/java/freemarker/core/_Java9.java
index 6f1a56ee..a446848f 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java9.java
+++ b/freemarker-core/src/main/java/freemarker/core/_Java9.java
@@ -23,6 +23,15 @@ package freemarker.core;
  * Used for accessing functionality that's only present in Java 9 or later.
  */
 public interface _Java9 {
+    /**
+     * Using "JEP 238: Multi-Release JAR Files", links to the proper version of the {@link _Java9Impl} class.
+     */
+    _Java9 INSTANCE = new _Java9Impl();
+
+    /**
+     * Tells if Java 9 features can be used in the current run-time environment.
+     */
+    boolean isSupported();
 
     boolean isAccessibleAccordingToModuleExports(Class<?> m);
 
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java16.java b/freemarker-core/src/main/java/freemarker/core/_Java9Impl.java
similarity index 64%
copy from freemarker-core/src/main/java/freemarker/core/_Java16.java
copy to freemarker-core/src/main/java/freemarker/core/_Java9Impl.java
index 19de70a3..68b16761 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java16.java
+++ b/freemarker-core/src/main/java/freemarker/core/_Java9Impl.java
@@ -16,19 +16,23 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package freemarker.core;
 
-import java.lang.reflect.Method;
-import java.util.Set;
+package freemarker.core;
 
 /**
  * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 16 or later.
+ * Pre-Java-9 implementation of {@link _Java9}.
  */
-public interface _Java16 {
-
-    boolean isRecord(Class<?> cl);
-
-    Set<Method> getComponentAccessors(Class<?> recordClass);
+// We also have a Java 9 versions of this class in freemarker-core9, and we put all versions into
+// the jar artifact via "JEP 238: Multi-Release JAR Files".
+public class _Java9Impl implements _Java9 {
+    @Override
+    public boolean isSupported() {
+        return false;
+    }
 
+    @Override
+    public boolean isAccessibleAccordingToModuleExports(Class<?> m) {
+        throw new UnsupportedOperationException("Requires at least Java 9");
+    }
 }
diff --git a/freemarker-core/src/main/java/freemarker/core/_JavaVersions.java b/freemarker-core/src/main/java/freemarker/core/_JavaVersions.java
deleted file mode 100644
index 4a3957e4..00000000
--- a/freemarker-core/src/main/java/freemarker/core/_JavaVersions.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you 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.
- */
-package freemarker.core;
-
-import freemarker.log.Logger;
-import freemarker.template.Version;
-import freemarker.template.utility.SecurityUtilities;
-
-/**
- * Used internally only, might change without notice!
- */
-public final class _JavaVersions {
-    
-    private _JavaVersions() {
-        // Not meant to be instantiated
-    }
-
-    /**
-     * {@code null} if Java 9 is not available, otherwise the object through with the Java 9 operations are available.
-     */
-    static public final _Java9 JAVA_9 = isAtLeast(9, "java.lang.Module")
-            ? tryLoadJavaSupportSingleton(9, _Java9.class)
-            : null;
-
-    /**
-     * {@code null} if Java 16 is not available, otherwise the object through with the Java 16 operations are available.
-     */
-    static public final _Java16 JAVA_16 = isAtLeast(16, "java.net.UnixDomainSocketAddress")
-            ? tryLoadJavaSupportSingleton(16, _Java16.class)
-            : null;
-
-    @SuppressWarnings("unchecked")
-    private static <T> T tryLoadJavaSupportSingleton(int javaVersion, Class<T> javaSupportInterface) {
-        String implClassName = "freemarker.core._Java" + javaVersion + "Impl";
-        try {
-            return (T) Class.forName(implClassName)
-                    .getField("INSTANCE")
-                    .get(null);
-        } catch (Exception e) {
-            try {
-                if (e instanceof ClassNotFoundException) {
-                    // Happens when we run JUnit tests
-                    Logger.getLogger("freemarker.runtime").warn(
-                            "Seems that the Java " + javaVersion + " support class (" + implClassName
-                                    + ") wasn't included in the build");
-                } else {
-                    Logger.getLogger("freemarker.runtime").error(
-                            "Failed to load Java " + javaVersion + " support class",
-                            e);
-                }
-            } catch (Exception e2) {
-                // Suppressed
-            }
-            return null;
-        }
-    }
-
-    private static boolean isAtLeast(int minimumMajorVersion, String proofClassPresence) {
-        boolean result = false;
-        String vStr = SecurityUtilities.getSystemProperty("java.version", null);
-        if (vStr != null) {
-            try {
-                Version v = new Version(vStr);
-                result = v.getMajor() >= minimumMajorVersion;
-            } catch (Exception e) {
-                // Ignore
-            }
-        } else {
-            try {
-                Class.forName(proofClassPresence);
-                result = true;
-            } catch (Exception e) {
-                // Ignore
-            }
-        }
-        return result;
-    }
-
-}
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
index 45f76de7..5e229937 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospector.java
@@ -48,7 +48,8 @@ import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
 import freemarker.core.BugException;
-import freemarker.core._JavaVersions;
+import freemarker.core._Java16;
+import freemarker.core._Java9;
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecision;
 import freemarker.ext.beans.BeansWrapper.MethodAppearanceDecisionInput;
 import freemarker.ext.util.ModelCache;
@@ -200,7 +201,7 @@ class ClassIntrospector {
         this.defaultZeroArgumentNonVoidMethodPolicy = builder.getDefaultZeroArgumentNonVoidMethodPolicy();
         this.recordZeroArgumentNonVoidMethodPolicy = builder.getRecordZeroArgumentNonVoidMethodPolicy();
         this.recordAware = defaultZeroArgumentNonVoidMethodPolicy != recordZeroArgumentNonVoidMethodPolicy;
-        if (recordAware && _JavaVersions.JAVA_16 == null) {
+        if (recordAware && !_Java16.INSTANCE.isSupported()) {
             throw new IllegalArgumentException(
                     "defaultZeroArgumentNonVoidMethodPolicy != recordZeroArgumentNonVoidMethodPolicy, " +
                     "but record support is not available (as Java 16 support is not available).");
@@ -343,7 +344,7 @@ class ClassIntrospector {
             ClassMemberAccessPolicy effClassMemberAccessPolicy) throws IntrospectionException {
         BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
 
-        boolean treatClassAsRecord = recordAware && _JavaVersions.JAVA_16.isRecord(clazz);
+        boolean treatClassAsRecord = recordAware && _Java16.INSTANCE.isRecord(clazz);
         ZeroArgumentNonVoidMethodPolicy zeroArgumentNonVoidMethodPolicy = treatClassAsRecord
                 ? recordZeroArgumentNonVoidMethodPolicy
                 : defaultZeroArgumentNonVoidMethodPolicy;
@@ -835,7 +836,7 @@ class ClassIntrospector {
     private static void discoverAccessibleMethods(
             Class<?> clazz, Map<ExecutableMemberSignature, List<Method>> accessibles) {
         if (Modifier.isPublic(clazz.getModifiers())
-                && (_JavaVersions.JAVA_9 == null || _JavaVersions.JAVA_9.isAccessibleAccordingToModuleExports(clazz))) {
+                && (!_Java9.INSTANCE.isSupported() || _Java9.INSTANCE.isAccessibleAccordingToModuleExports(clazz))) {
             try {
                 Method[] methods = clazz.getMethods();
                 for (int i = 0; i < methods.length; i++) {
diff --git a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
index 7ff87072..141917cc 100644
--- a/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
+++ b/freemarker-core/src/main/java/freemarker/ext/beans/ClassIntrospectorBuilder.java
@@ -26,7 +26,7 @@ import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 
-import freemarker.core._JavaVersions;
+import freemarker.core._Java16;
 import freemarker.template.Configuration;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
@@ -76,9 +76,10 @@ final class ClassIntrospectorBuilder implements Cloneable {
         this.incompatibleImprovements = normalizeIncompatibleImprovementsVersion(incompatibleImprovements);
         treatDefaultMethodsAsBeanMembers = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_26;
         defaultZeroArgumentNonVoidMethodPolicy = ZeroArgumentNonVoidMethodPolicy.METHOD_ONLY;
-        recordZeroArgumentNonVoidMethodPolicy = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_33 && _JavaVersions.JAVA_16 != null
-                ? ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
-                : defaultZeroArgumentNonVoidMethodPolicy;
+        recordZeroArgumentNonVoidMethodPolicy
+                = incompatibleImprovements.intValue() >= _VersionInts.V_2_3_33 && _Java16.INSTANCE.isSupported()
+                        ? ZeroArgumentNonVoidMethodPolicy.BOTH_METHOD_AND_PROPERTY_UNLESS_BEAN_PROPERTY_READ_METHOD
+                        : defaultZeroArgumentNonVoidMethodPolicy;
         memberAccessPolicy = DefaultMemberAccessPolicy.getInstance(this.incompatibleImprovements);
     }
 
diff --git a/freemarker-core16/src/main/java/freemarker/core/_Java16Impl.java b/freemarker-core16/src/main/java/freemarker/core/_Java16Impl.java
index ba1ba879..e498f8c3 100644
--- a/freemarker-core16/src/main/java/freemarker/core/_Java16Impl.java
+++ b/freemarker-core16/src/main/java/freemarker/core/_Java16Impl.java
@@ -25,16 +25,15 @@ import java.util.Set;
 
 /**
  * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 16 or later.
+ * Java 16 implementation of {@link _Java16}.
  */
-// Compile this against Java 16
+// We also have a pre-Java-16 versions of this class in freemarker-core, and we put all versions into
+// the jar artifact via "JEP 238: Multi-Release JAR Files".
 @SuppressWarnings("Since15") // For IntelliJ inspection
 public class _Java16Impl implements _Java16 {
-
-    public static final _Java16 INSTANCE = new _Java16Impl();
-
-    private _Java16Impl() {
-        // Not meant to be instantiated
+    @Override
+    public boolean isSupported() {
+        return true;
     }
 
     @Override
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java16.java b/freemarker-core16/src/test/java/freemarker/ext/beans/Java16TestClassLoadingCorrectTest.java
similarity index 57%
copy from freemarker-core/src/main/java/freemarker/core/_Java16.java
copy to freemarker-core16/src/test/java/freemarker/ext/beans/Java16TestClassLoadingCorrectTest.java
index 19de70a3..d4f4f020 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java16.java
+++ b/freemarker-core16/src/test/java/freemarker/ext/beans/Java16TestClassLoadingCorrectTest.java
@@ -16,19 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package freemarker.core;
 
-import java.lang.reflect.Method;
-import java.util.Set;
+package freemarker.ext.beans;
 
-/**
- * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 16 or later.
- */
-public interface _Java16 {
+import static org.junit.Assert.*;
 
-    boolean isRecord(Class<?> cl);
+import org.junit.Test;
 
-    Set<Method> getComponentAccessors(Class<?> recordClass);
+import freemarker.core._Java16;
+import freemarker.core._Java16Impl;
 
+public class Java16TestClassLoadingCorrectTest {
+    @Test
+    public void java16Supported() {
+        // This can be a problem if the test is not ran from the jar, and the impl class from the core takes precedence
+        assertTrue(
+                "Multi-Release JAR selection of the proper " + _Java16Impl.class.getName() + " variant didn't happen in the Java 16 test environment",
+                _Java16.INSTANCE.isSupported());
+    }
 }
diff --git a/freemarker-core16/src/test/java/freemarker/ext/beans/NotExportedInternalPackageTest.java b/freemarker-core16/src/test/java/freemarker/ext/beans/NotExportedInternalPackageTest.java
index 117544cc..38a7f131 100644
--- a/freemarker-core16/src/test/java/freemarker/ext/beans/NotExportedInternalPackageTest.java
+++ b/freemarker-core16/src/test/java/freemarker/ext/beans/NotExportedInternalPackageTest.java
@@ -33,6 +33,7 @@ import org.junit.Test;
 import org.w3c.dom.Document;
 import org.xml.sax.InputSource;
 
+import freemarker.core._Java16;
 import freemarker.template.TemplateHashModel;
 import freemarker.template.TemplateMethodModelEx;
 
@@ -42,6 +43,8 @@ import freemarker.template.TemplateMethodModelEx;
 public class NotExportedInternalPackageTest {
     @Test
     public void java16InternalClassAvoidanceTest() throws Exception {
+        assertTrue(_Java16.INSTANCE.isSupported());
+
         BeansWrapper bw = new BeansWrapper();
 
         Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder()
diff --git a/freemarker-core9/src/main/java/freemarker/core/_Java9Impl.java b/freemarker-core9/src/main/java/freemarker/core/_Java9Impl.java
index 1f5182af..aabc49cc 100644
--- a/freemarker-core9/src/main/java/freemarker/core/_Java9Impl.java
+++ b/freemarker-core9/src/main/java/freemarker/core/_Java9Impl.java
@@ -22,18 +22,17 @@ import freemarker.ext.beans.BeansWrapper;
 
 /**
  * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 9 or later.
+ * Java 9 implementation of {@link _Java9}.
  */
-// Compile this against Java 9
+// We also have a pre-Java-9 versions of this class in freemarker-core, and we put all versions into
+// the jar artifact via "JEP 238: Multi-Release JAR Files".
 @SuppressWarnings("Since15") // For IntelliJ inspection
 public class _Java9Impl implements _Java9 {
-
-    public static final _Java9 INSTANCE = new _Java9Impl();
-
     private static final Module ACCESSOR_MODULE = BeansWrapper.class.getModule();
 
-    private _Java9Impl() {
-        // Not meant to be instantiated
+    @Override
+    public boolean isSupported() {
+        return true;
     }
 
     @Override
diff --git a/freemarker-core9/src/test/java/freemarker/core/Java9ImplTest.java b/freemarker-core9/src/test/java/freemarker/core/Java9ImplTest.java
index 9612d5ba..12807460 100644
--- a/freemarker-core9/src/test/java/freemarker/core/Java9ImplTest.java
+++ b/freemarker-core9/src/test/java/freemarker/core/Java9ImplTest.java
@@ -38,11 +38,11 @@ public class Java9ImplTest {
 
     @Test
     public void testIsAccessibleAccordingToModuleExports() throws Exception {
-        assertNotNull(_JavaVersions.JAVA_9);
-        assertTrue(_JavaVersions.JAVA_9.isAccessibleAccordingToModuleExports(Document.class));
-        assertFalse(_JavaVersions.JAVA_9.isAccessibleAccordingToModuleExports(getSomeInternalClass()));
-        assertTrue(_JavaVersions.JAVA_9.isAccessibleAccordingToModuleExports(String[].class));
-        assertTrue(_JavaVersions.JAVA_9.isAccessibleAccordingToModuleExports(int.class));
+        assertTrue(_Java9.INSTANCE.isSupported());
+        assertTrue(_Java9.INSTANCE.isAccessibleAccordingToModuleExports(Document.class));
+        assertFalse(_Java9.INSTANCE.isAccessibleAccordingToModuleExports(getSomeInternalClass()));
+        assertTrue(_Java9.INSTANCE.isAccessibleAccordingToModuleExports(String[].class));
+        assertTrue(_Java9.INSTANCE.isAccessibleAccordingToModuleExports(int.class));
     }
 
     private static Class<?> getSomeInternalClass() throws SAXException, IOException, ParserConfigurationException,
diff --git a/freemarker-core/src/main/java/freemarker/core/_Java16.java b/freemarker-core9/src/test/java/freemarker/core/Java9TestClassLoadingCorrectTest.java
similarity index 62%
copy from freemarker-core/src/main/java/freemarker/core/_Java16.java
copy to freemarker-core9/src/test/java/freemarker/core/Java9TestClassLoadingCorrectTest.java
index 19de70a3..5ef21763 100644
--- a/freemarker-core/src/main/java/freemarker/core/_Java16.java
+++ b/freemarker-core9/src/test/java/freemarker/core/Java9TestClassLoadingCorrectTest.java
@@ -16,19 +16,19 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package freemarker.core;
-
-import java.lang.reflect.Method;
-import java.util.Set;
 
-/**
- * Used internally only, might change without notice!
- * Used for accessing functionality that's only present in Java 16 or later.
- */
-public interface _Java16 {
+package freemarker.core;
 
-    boolean isRecord(Class<?> cl);
+import static org.junit.Assert.*;
 
-    Set<Method> getComponentAccessors(Class<?> recordClass);
+import org.junit.Test;
 
+public class Java9TestClassLoadingCorrectTest {
+    @Test
+    public void java9Supported() {
+        // This can be a problem if the test is not ran from the jar, and the impl class from the core takes precedence
+        assertTrue(
+                "Multi-Release JAR selection of the proper " + _Java9Impl.class.getName() + " variant didn't happen in the Java 9 test environment",
+                _Java9.INSTANCE.isSupported());
+    }
 }