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

rzo1 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opennlp.git

commit fa79a524382734d997a2754c9acfb628e3d74f3d
Author: Richard Zowalla <[email protected]>
AuthorDate: Thu Jul 16 15:31:54 2026 +0200

    OPENNLP-1890: Using allowlist based class loading in StringInterners
---
 .../tools/util/jvm/CHMStringDeduplicator.java      |  2 +-
 .../opennlp/tools/util/jvm/CHMStringInterner.java  |  2 +-
 .../opennlp/tools/util/jvm/HMStringInterner.java   |  2 +-
 .../opennlp/tools/util/jvm/JvmStringInterner.java  |  2 +-
 .../opennlp/tools/util/jvm/NoOpStringInterner.java |  2 +-
 .../opennlp/tools/util/jvm/StringInterners.java    | 12 ++--
 .../tools/util/jvm/StringInternersTest.java        | 74 ++++++++++++++++++++++
 7 files changed, 86 insertions(+), 10 deletions(-)

diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringDeduplicator.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringDeduplicator.java
index d8a0a50d9..bf0148976 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringDeduplicator.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringDeduplicator.java
@@ -35,7 +35,7 @@ import opennlp.tools.commons.ThreadSafe;
  */
 @Internal
 @ThreadSafe
-class CHMStringDeduplicator implements StringInterner {
+public class CHMStringDeduplicator implements StringInterner {
   private final int prob;
   private final Map<String, String> map;
 
diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringInterner.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringInterner.java
index 597cae26a..812c343f1 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringInterner.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/CHMStringInterner.java
@@ -32,7 +32,7 @@ import opennlp.tools.commons.ThreadSafe;
  */
 @Internal
 @ThreadSafe
-class CHMStringInterner implements StringInterner {
+public class CHMStringInterner implements StringInterner {
   private final Map<String, String> map;
   public CHMStringInterner() {
     map = new ConcurrentHashMap<>();
diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/HMStringInterner.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/HMStringInterner.java
index 2cef86ff8..84f816863 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/HMStringInterner.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/HMStringInterner.java
@@ -30,7 +30,7 @@ import opennlp.tools.commons.Internal;
  * https://shipilev.net/jvm/anatomy-quarks/10-string-intern/</a>
  */
 @Internal
-class HMStringInterner implements StringInterner {
+public class HMStringInterner implements StringInterner {
 
   private final Map<String, String> map;
 
diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/JvmStringInterner.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/JvmStringInterner.java
index 26567b4d0..a6b040f3c 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/JvmStringInterner.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/JvmStringInterner.java
@@ -31,7 +31,7 @@ import opennlp.tools.commons.ThreadSafe;
  */
 @Internal
 @ThreadSafe
-class JvmStringInterner implements StringInterner {
+public class JvmStringInterner implements StringInterner {
 
   /**
    * {@inheritDoc}
diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/NoOpStringInterner.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/NoOpStringInterner.java
index 8398191c8..1bb8111a6 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/NoOpStringInterner.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/NoOpStringInterner.java
@@ -25,7 +25,7 @@ import opennlp.tools.commons.ThreadSafe;
  */
 @Internal
 @ThreadSafe
-class NoOpStringInterner implements StringInterner {
+public class NoOpStringInterner implements StringInterner {
 
   /**
    * {@inheritDoc}
diff --git 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/StringInterners.java
 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/StringInterners.java
index 295def2d6..d65c99b8c 100644
--- 
a/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/StringInterners.java
+++ 
b/opennlp-core/opennlp-runtime/src/main/java/opennlp/tools/util/jvm/StringInterners.java
@@ -17,17 +17,21 @@
 
 package opennlp.tools.util.jvm;
 
-import java.lang.reflect.Constructor;
-
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import opennlp.tools.util.ext.ExtensionLoader;
+
 /**
  * Provides string interning utility methods. Interning mechanism can be 
configured via the
  * system property {@code opennlp.interner.class} by specifying an 
implementation via its
  * fully qualified classname. It needs to implement {@link StringInterner}.
  * <p>
  * If not specified by the user, the default interner is {@link 
CHMStringInterner}.
+ * <p>
+ * The implementation is loaded via the {@link ExtensionLoader}. Custom 
implementations
+ * must provide a public no-arg constructor and reside in an allowed package,
+ * see {@link ExtensionLoader#registerAllowedPackage(String)}.
  */
 public class StringInterners {
 
@@ -40,9 +44,7 @@ public class StringInterners {
         CHMStringInterner.class.getCanonicalName());
 
     try {
-      final Class<?> clazz = Class.forName(clazzName);
-      final Constructor<?> cons = clazz.getDeclaredConstructor();
-      INTERNER = (StringInterner) cons.newInstance();
+      INTERNER = ExtensionLoader.instantiateExtension(StringInterner.class, 
clazzName);
       LOGGER.debug("Using '{}' as String interner implementation.", clazzName);
     } catch (Exception e) {
       throw new RuntimeException("Could not load specified String interner 
implementation: '"
diff --git 
a/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/util/jvm/StringInternersTest.java
 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/util/jvm/StringInternersTest.java
new file mode 100644
index 000000000..0522ebf11
--- /dev/null
+++ 
b/opennlp-core/opennlp-runtime/src/test/java/opennlp/tools/util/jvm/StringInternersTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 opennlp.tools.util.jvm;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import opennlp.tools.util.ext.ExtensionLoader;
+import opennlp.tools.util.ext.ExtensionNotLoadedException;
+
+public class StringInternersTest {
+
+  @Test
+  void testIntern() {
+    final String interned = StringInterners.intern("opennlp");
+    Assertions.assertSame(interned, StringInterners.intern(new 
String("opennlp".toCharArray())));
+  }
+
+  /**
+   * Positive: all built-in interner implementations can be loaded via the
+   * ExtensionLoader, as done by {@link StringInterners} when configured 
through
+   * the {@code opennlp.interner.class} system property.
+   */
+  @ParameterizedTest
+  @ValueSource(strings = {
+      "opennlp.tools.util.jvm.CHMStringInterner",
+      "opennlp.tools.util.jvm.CHMStringDeduplicator",
+      "opennlp.tools.util.jvm.HMStringInterner",
+      "opennlp.tools.util.jvm.JvmStringInterner",
+      "opennlp.tools.util.jvm.NoOpStringInterner"})
+  void testBuiltInInternersLoadable(String clazzName) {
+    final StringInterner interner =
+        ExtensionLoader.instantiateExtension(StringInterner.class, clazzName);
+    Assertions.assertNotNull(interner);
+  }
+
+  /**
+   * Negative: an interner class in a non-allowed package is rejected by
+   * the ExtensionLoader allowlist before Class.forName() is called.
+   */
+  @Test
+  void testInternerFromBlockedPackageThrows() {
+    Assertions.assertThrows(ExtensionNotLoadedException.class, () ->
+        ExtensionLoader.instantiateExtension(StringInterner.class, 
"com.malicious.Interner"));
+  }
+
+  /**
+   * Negative: an opennlp.* class that does NOT implement StringInterner is
+   * rejected by the isAssignableFrom type check.
+   */
+  @Test
+  void testInternerOfWrongTypeThrows() {
+    Assertions.assertThrows(ExtensionNotLoadedException.class, () ->
+        ExtensionLoader.instantiateExtension(StringInterner.class,
+            "opennlp.tools.util.jvm.StringInterners"));
+  }
+}

Reply via email to