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

rec pushed a commit to branch 
bugfix/382-Warning-when-PEAR-contains-a-JCAS-class-that-is-used-as-a-feature-range-outside-the-PEAR
in repository https://gitbox.apache.org/repos/asf/uima-uimaj.git

commit 2e69c7576319483b7c7221da31d27859c53b2def
Author: Richard Eckart de Castilho <[email protected]>
AuthorDate: Wed Aug 21 17:48:45 2024 +0200

    Issue #382: Warning when PEAR contains a JCAS class that is used as a 
feature range outside the PEAR
    
    - Added a "test" which reproduces the warning although there is no assert 
that checks if the warning is logged
    - Some cleaning up
---
 .../org/apache/uima/cas/CASRuntimeException.java   |   5 +-
 .../org/apache/uima/cas/impl/FSClassRegistry.java  |  30 +-
 .../apache/uima/UIMAException_Messages.properties  |   2 +-
 .../apache/uima/cas/impl/FSClassRegistryTest.java  |  36 +-
 .../uima/cas/test/FSCreatedInPearContextTest.java  |  30 +-
 .../apache/uima/cas/test/JCasClassLoaderTest.java  | 378 +++++++++------------
 uimaj-parent/pom.xml                               |   5 +
 .../org/apache/uima/test/IsolatingClassloader.java | 157 +++++++++
 8 files changed, 368 insertions(+), 275 deletions(-)

diff --git 
a/uimaj-core/src/main/java/org/apache/uima/cas/CASRuntimeException.java 
b/uimaj-core/src/main/java/org/apache/uima/cas/CASRuntimeException.java
index 0668d61d3..c4c2a7051 100644
--- a/uimaj-core/src/main/java/org/apache/uima/cas/CASRuntimeException.java
+++ b/uimaj-core/src/main/java/org/apache/uima/cas/CASRuntimeException.java
@@ -128,8 +128,9 @@ public class CASRuntimeException extends 
UIMARuntimeException {
   public static final String JCAS_TYPE_NOT_IN_CAS = "JCAS_TYPE_NOT_IN_CAS";
 
   /**
-   * CAS type system type "{0}" defines field "{1}" with range "{2}", but JCas 
class has range
-   * "{3}".
+   * CAS type system type "{0}" (loaded by {1}) defines field "{2}" with range 
"{3}" (loaded by
+   * {4}), but JCas getter method is returning "{5}" (loaded by {6}) which is 
not a subtype of the
+   * declared range.
    */
   public static final String JCAS_TYPE_RANGE_MISMATCH = 
"JCAS_TYPE_RANGE_MISMATCH";
 
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java 
b/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java
index 50246d6a2..1b882da2e 100644
--- a/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java
+++ b/uimaj-core/src/main/java/org/apache/uima/cas/impl/FSClassRegistry.java
@@ -1366,26 +1366,22 @@ public abstract class FSClassRegistry { // abstract to 
prevent instantiating; th
           rangeClass = range.getComponentType().getJavaClass();
         }
       }
-      if (!rangeClass.isAssignableFrom(returnClass)) { // can return subclass 
of TOP, OK if range is
-                                                       // TOP
-        if (rangeClass.getName().equals("org.apache.uima.jcas.cas.Sofa") && // 
exception: for
-                                                                            // 
backwards compat
-                                                                            // 
reasons, sofaRef
-                                                                            // 
returns SofaFS, not
-                                                                            // 
Sofa.
-                returnClass.getName().equals("org.apache.uima.cas.SofaFS")) {
+
+      // can return subclass of TOP, OK if range is TOP
+      if (!rangeClass.isAssignableFrom(returnClass)) {
+        // exception: for backwards compatibility reasons, sofaRef returns 
SofaFS, not Sofa.
+        if (rangeClass.getName().equals("org.apache.uima.jcas.cas.Sofa")
+                && returnClass.getName().equals("org.apache.uima.cas.SofaFS")) 
{
           // empty
         } else {
-
-          /**
-           * CAS type system type "{0}" defines field "{1}" with range "{2}", 
but JCas getter method
-           * is returning "{3}" which is not a subtype of the declared range.
-           */
+          // CAS type system type "{0}" (loaded by {1}) defines field "{2}" 
with range "{3}" (loaded
+          // by {4}), but JCas getter method is returning "{5}" (loaded by 
{6}) which is not a
+          // subtype of the declared range.
+          //
+          // should throw, but some code breaks!
           add2errors(errorSet, new 
CASRuntimeException(CASRuntimeException.JCAS_TYPE_RANGE_MISMATCH,
-                  ti.getName(), fi.getShortName(), rangeClass, returnClass), 
false); // should
-                                                                               
      // throw, but
-                                                                               
      // some code
-                                                                               
      // breaks!
+                  ti.getName(), ti.getJavaClass().getClassLoader(), 
fi.getShortName(), rangeClass,
+                  rangeClass.getClassLoader(), returnClass, 
returnClass.getClassLoader()), false);
         }
       }
     } // end of checking methods
diff --git 
a/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
 
b/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
index c079e6643..9b0a6ead6 100644
--- 
a/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
+++ 
b/uimaj-core/src/main/resources/org/apache/uima/UIMAException_Messages.properties
@@ -562,7 +562,7 @@ JCAS_FIELD_MISSING_IN_TYPE_SYSTEM = JCAS class "{0}" 
defines a UIMA field "{1}"
 JCAS_FIELD_ADJ_OFFSET_CHANGED = In JCAS class "{0}", UIMA field "{1}" was set 
up when this class was previously loaded and initialized, to have an adjusted 
offset of "{2}" but now the feature has a different adjusted offset of "{3}"; 
this may be due to something else other than type system commit actions loading 
and initializing the JCas class, or to having a different non-compatible type 
system for this class, trying to use a common JCas cover class, which is not 
supported. 
 JCAS_CAS_MISMATCH_SUPERTYPE = JCas class supertypes for "{0}", "{1}" and the 
corresponding UIMA supertypes for "{2}", "{3}" do not have an intersection.
 JCAS_MISMATCH_SUPERTYPE = The JCas class: "{0}" has supertypes: "{1}" which do 
not match the UIMA type "{2}"''s supertypes "{3}".
-JCAS_TYPE_RANGE_MISMATCH = CAS type system type "{0}" defines field "{1}" with 
range "{2}", but JCas getter method is returning "{3}" which is not a subtype 
of the declared range.
+JCAS_TYPE_RANGE_MISMATCH = CAS type system type "{0}" (loaded by {1}) defines 
field "{2}" with range "{3}" (loaded by {4}), but JCas getter method is 
returning "{5}" (loaded by {6}) which is not a subtype of the declared range.
 JCAS_GET_NTH_ON_EMPTY_LIST = JCas getNthElement method called via invalid 
object - an empty list: {0}.
 JCAS_GET_NTH_NEGATIVE_INDEX = JCas getNthElement method called with index 
"{0}" which is negative.
 JCAS_GET_NTH_PAST_END = JCas getNthElement method called with index "{0}" 
larger than the length of the list.
diff --git 
a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java 
b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
index 0fba3aa30..20490f020 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/impl/FSClassRegistryTest.java
@@ -21,12 +21,7 @@ package org.apache.uima.cas.impl;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.entry;
 
-import java.util.Map;
-
 import org.apache.uima.UIMAFramework;
-import org.apache.uima.jcas.JCas;
-import org.apache.uima.jcas.cas.TOP;
-import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.spi.SpiSentence;
 import org.apache.uima.spi.SpiToken;
 import org.apache.uima.util.CasCreationUtils;
@@ -34,9 +29,10 @@ import org.apache.uima.util.Level;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
-public class FSClassRegistryTest {
+class FSClassRegistryTest {
+
   @BeforeEach
-  public void setup() {
+  void setup() {
     System.setProperty(FSClassRegistry.RECORD_JCAS_CLASSLOADERS, "true");
 
     // Calls to FSClassRegistry will fail unless the static initializer block 
in TypeSystemImpl
@@ -48,15 +44,15 @@ public class FSClassRegistryTest {
   }
 
   @Test
-  public void 
thatCreatingResourceManagersWithExtensionClassloaderDoesNotFillUpCache()
-          throws Exception {
+  void 
thatCreatingResourceManagersWithExtensionClassloaderDoesNotFillUpCache() throws 
Exception {
     int numberOfCachedClassloadersAtStart = 
FSClassRegistry.clToType2JCasSize();
     for (int i = 0; i < 5; i++) {
-      ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
+      var resMgr = UIMAFramework.newDefaultResourceManager();
       resMgr.setExtensionClassLoader(getClass().getClassLoader(), true);
-      JCas jcas = CasCreationUtils.createCas(null, null, null, 
resMgr).getJCas();
 
-      ClassLoader cl = jcas.getCasImpl().getJCasClassLoader();
+      var jcas = CasCreationUtils.createCas(null, null, null, 
resMgr).getJCas();
+
+      var cl = jcas.getCasImpl().getJCasClassLoader();
       
assertThat(cl.getResource(FSClassRegistryTest.class.getName().replace(".", "/") 
+ ".class")) //
               .isNotNull();
 
@@ -70,14 +66,15 @@ public class FSClassRegistryTest {
   }
 
   @Test
-  public void 
thatCreatingResourceManagersWithExtensionPathDoesNotFillUpCache() throws 
Exception {
-    int numberOfCachedClassloadersAtStart = 
FSClassRegistry.clToType2JCasSize();
+  void thatCreatingResourceManagersWithExtensionPathDoesNotFillUpCache() 
throws Exception {
+    var numberOfCachedClassloadersAtStart = 
FSClassRegistry.clToType2JCasSize();
+
     for (int i = 0; i < 5; i++) {
-      ResourceManager resMgr = UIMAFramework.newDefaultResourceManager();
+      var resMgr = UIMAFramework.newDefaultResourceManager();
       resMgr.setExtensionClassPath("src/test/java", true);
-      JCas jcas = CasCreationUtils.createCas(null, null, null, 
resMgr).getJCas();
+      var jcas = CasCreationUtils.createCas(null, null, null, 
resMgr).getJCas();
 
-      ClassLoader cl = jcas.getCasImpl().getJCasClassLoader();
+      var cl = jcas.getCasImpl().getJCasClassLoader();
       
assertThat(cl.getResource(FSClassRegistryTest.class.getName().replace(".", "/") 
+ ".java")) //
               .isNotNull();
 
@@ -91,9 +88,8 @@ public class FSClassRegistryTest {
   }
 
   @Test
-  public void thatJCasClassesCanBeLoadedThroughSPI() throws Exception {
-    Map<String, Class<? extends TOP>> jcasClasses = FSClassRegistry
-            .loadJCasClassesFromSPI(getClass().getClassLoader());
+  void thatJCasClassesCanBeLoadedThroughSPI() throws Exception {
+    var jcasClasses = 
FSClassRegistry.loadJCasClassesFromSPI(getClass().getClassLoader());
 
     assertThat(jcasClasses).containsOnly( //
             entry(SpiToken.class.getName(), SpiToken.class), //
diff --git 
a/uimaj-core/src/test/java/org/apache/uima/cas/test/FSCreatedInPearContextTest.java
 
b/uimaj-core/src/test/java/org/apache/uima/cas/test/FSCreatedInPearContextTest.java
index a130d6a0d..ef372c774 100644
--- 
a/uimaj-core/src/test/java/org/apache/uima/cas/test/FSCreatedInPearContextTest.java
+++ 
b/uimaj-core/src/test/java/org/apache/uima/cas/test/FSCreatedInPearContextTest.java
@@ -27,12 +27,10 @@ import java.io.File;
 import java.io.IOException;
 import java.net.URL;
 
-import org.apache.uima.cas.Type;
 import org.apache.uima.cas.impl.CASImpl;
-import org.apache.uima.cas.test.JCasClassLoaderTest.IsolatingClassloader;
 import org.apache.uima.internal.util.UIMAClassLoader;
-import org.apache.uima.jcas.tcas.Annotation;
 import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.test.IsolatingClassloader;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
 import org.junit.jupiter.api.Test;
@@ -42,17 +40,17 @@ public class FSCreatedInPearContextTest {
   @Test
   public void thatOneTrampolineIsUsedWhenClassLoaderIsSwitched() throws 
Exception, IOException {
 
-    ClassLoader rootCl = getClass().getClassLoader();
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForToken = new IsolatingClassloader("Token", rootCl)
+    var clForToken = new IsolatingClassloader("Token", rootCl)
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    CASImpl casImpl = (CASImpl) createCas(loadTokensAndSentencesTS(), null, 
null, null);
+    var casImpl = (CASImpl) createCas(loadTokensAndSentencesTS(), null, null, 
null);
     casImpl.switchClassLoaderLockCasCL(new UIMAClassLoader(new URL[0], 
clForToken));
     casImpl.setDocumentText("Test");
 
-    Type tokenType = casImpl.getTypeSystem().getType(Token.class.getName());
-    Annotation token = casImpl.createAnnotation(tokenType, 0, 1);
+    var tokenType = casImpl.getTypeSystem().getType(Token.class.getName());
+    var token = casImpl.createAnnotation(tokenType, 0, 1);
     token.addToIndexes();
     assertThat(token.getClass().getClassLoader())
             .as("Trampoline returned by createAnnotation after classloader 
switch")
@@ -61,23 +59,25 @@ public class FSCreatedInPearContextTest {
     assertThat(casImpl.select(Token.type).asList()) //
             .as("Same trampoline returned by [select(Token.type)] after 
classloader switch")
             .usingElementComparator((a, b) -> a == b ? 0 : 1) //
-            .containsExactly(token).allMatch(t -> 
t.getClass().getClassLoader() == clForToken);
+            .containsExactly(token) //
+            .allMatch(t -> t.getClass().getClassLoader() == clForToken);
 
     casImpl.restoreClassLoaderUnlockCas();
     assertThat(casImpl.select(Token.type).asList()) //
             .as("After switching back out of the the classloader context, we 
get the base FS")
             .usingElementComparator((a, b) -> a._id() == b._id() ? 0 : 1) //
-            .containsExactly(token).allMatch(t -> 
t.getClass().getClassLoader() == rootCl);
+            .containsExactly(token) //
+            .allMatch(t -> t.getClass().getClassLoader() == rootCl);
   }
 
   @Test
   public void thatResettingCasInPearContextWorks() throws Exception, 
IOException {
-    ClassLoader rootCl = getClass().getClassLoader();
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForToken = new IsolatingClassloader("Token", rootCl)
+    var clForToken = new IsolatingClassloader("Token", rootCl)
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    CASImpl casImpl = (CASImpl) createCas(loadTokensAndSentencesTS(), null, 
null, null);
+    var casImpl = (CASImpl) createCas(loadTokensAndSentencesTS(), null, null, 
null);
     casImpl.switchClassLoaderLockCasCL(new UIMAClassLoader(new URL[0], 
clForToken));
     casImpl.setDocumentText("Test");
 
@@ -86,8 +86,8 @@ public class FSCreatedInPearContextTest {
     casImpl.resetNoQuestions();
 
     assertThatNoException().isThrownBy(() -> {
-      Type tokenType = casImpl.getTypeSystem().getType(Token.class.getName());
-      Annotation token = casImpl.createAnnotation(tokenType, 0, 1);
+      var tokenType = casImpl.getTypeSystem().getType(Token.class.getName());
+      var token = casImpl.createAnnotation(tokenType, 0, 1);
       token.addToIndexes();
     });
   }
diff --git 
a/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java 
b/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
index ce7fddfd9..d7c93c5a5 100644
--- a/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/cas/test/JCasClassLoaderTest.java
@@ -20,6 +20,7 @@ package org.apache.uima.cas.test;
 
 import static java.util.Arrays.asList;
 import static java.util.stream.Collectors.joining;
+import static org.apache.commons.io.FileUtils.copyFile;
 import static org.apache.uima.UIMAFramework.getResourceSpecifierFactory;
 import static org.apache.uima.UIMAFramework.getXMLParser;
 import static org.apache.uima.UIMAFramework.newDefaultResourceManager;
@@ -32,26 +33,16 @@ import static org.assertj.core.api.Assertions.assertThat;
 
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
 import java.net.MalformedURLException;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.io.IOUtils;
+import org.apache.uima.UIMAFramework;
 import org.apache.uima.analysis_component.Annotator_ImplBase;
 import org.apache.uima.analysis_component.JCasAnnotator_ImplBase;
 import org.apache.uima.analysis_engine.AnalysisEngine;
-import org.apache.uima.analysis_engine.AnalysisEngineDescription;
 import org.apache.uima.analysis_engine.AnalysisEngineProcessException;
 import org.apache.uima.analysis_engine.impl.PrimitiveAnalysisEngine_impl;
 import org.apache.uima.cas.CAS;
@@ -66,27 +57,36 @@ import 
org.apache.uima.resource.ResourceInitializationException;
 import org.apache.uima.resource.ResourceManager;
 import org.apache.uima.resource.metadata.TypeDescription;
 import org.apache.uima.resource.metadata.TypeSystemDescription;
+import org.apache.uima.spi.JCasClassProviderForTesting;
+import org.apache.uima.test.IsolatingClassloader;
 import org.apache.uima.util.InvalidXMLException;
 import org.apache.uima.util.XMLInputSource;
 import org.assertj.core.api.AutoCloseableSoftAssertions;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import x.y.z.Token;
+import x.y.z.TokenType;
 
 public class JCasClassLoaderTest {
+  private static final Logger LOG = 
LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
 
   public static final String TYPE_NAME_TOKEN = Token.class.getName();
   public static final String TYPE_NAME_ARRAY_HOST = "uima.testing.ArrayHost";
   public static final String FEAT_NAME_ARRAY_HOST_VALUES = "values";
 
-  public static Class casTokenClassViaClassloader;
-  public static Class casTokenClassViaCas;
-  public static Class addTokenAETokenClass;
-  public static Class fetchTokenAETokenClass;
-  public static Class indexedTokenClass;
+  public static Class<?> casTokenClassViaClassloader;
+  public static Class<?> casTokenClassViaCas;
+  public static Class<?> addTokenAETokenClass;
+  public static Class<?> fetchTokenAETokenClass;
+  public static Class<?> indexedTokenClass;
   public static boolean fetchThrowsClassCastException;
 
-  public static Class tokenClassAddedToArray;
-  public static Class tokenClassFetchedFromArray;
+  public static Class<?> tokenClassAddedToArray;
+  public static Class<?> tokenClassFetchedFromArray;
 
   @BeforeEach
   public void setup() {
@@ -119,37 +119,36 @@ public class JCasClassLoaderTest {
    * engines should use its own version of the JCas wrappers to access the CAS.
    */
   @Test
-  public void 
thatCASCanBeDefinedWithoutJCasWrappersAndTheyComeInWithAnnotatorsViaClasspath()
+  void 
thatCASCanBeDefinedWithoutJCasWrappersAndTheyComeInWithAnnotatorsViaClasspath()
           throws Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+    var rootCl = getClass().getClassLoader();
 
     // We do not want the CAS to know the Token JCas wrapper when it gets 
initialized
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+    var clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    File cpBase = new File("target/test-output/JCasClassLoaderTest/classes");
-    File cpPackageBase = new File(cpBase, "org/apache/uima/cas/test");
+    var cpBase = new File("target/test-output/JCasClassLoaderTest/classes");
+    var cpPackageBase = new File(cpBase, "org/apache/uima/cas/test");
     cpPackageBase.mkdirs();
-    FileUtils.copyFile(new 
File("target/test-classes/org/apache/uima/cas/test/Token.class"),
+    copyFile(new 
File("target/test-classes/org/apache/uima/cas/test/Token.class"),
             new File(cpPackageBase, "Token.class"));
-    FileUtils.copyFile(new File(
+    copyFile(new File(
             
"target/test-classes/org/apache/uima/cas/test/JCasClassLoaderTest$AddATokenAnnotator.class"),
             new File(cpPackageBase, 
"JCasClassLoaderTest$AddATokenAnnotator.class"));
-    FileUtils.copyFile(new File(
+    copyFile(new File(
             
"target/test-classes/org/apache/uima/cas/test/JCasClassLoaderTest$FetchTheTokenAnnotator.class"),
             new File(cpPackageBase, 
"JCasClassLoaderTest$FetchTheTokenAnnotator.class"));
 
-    JCas jcas = makeJCas(clForCas);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotator.class, cpBase);
-    AnalysisEngine fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
-            cpBase);
+    var jcas = makeJCas(clForCas);
+    var addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, 
cpBase);
+    var fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class, cpBase);
 
     jcas.setDocumentText("test");
 
     addATokenAnnotator.process(jcas);
     fetchTheTokenAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNull();
       softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
@@ -183,26 +182,25 @@ public class JCasClassLoaderTest {
    * engines should use its own version of the JCas wrappers to access the CAS.
    */
   @Test
-  public void 
thatCASCanBeDefinedWithoutJCasWrappersAndTheyComeInWithAnnotatorsViaClassloader()
+  void 
thatCASCanBeDefinedWithoutJCasWrappersAndTheyComeInWithAnnotatorsViaClassloader()
           throws Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+    var rootCl = getClass().getClassLoader();
 
     // We do not want the CAS to know the Token JCas wrapper when it gets 
initialized
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+    var clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
+    var clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
             .redefining("^.*AddATokenAnnotator$")
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator",
-            rootCl).redefining("^.*FetchTheTokenAnnotator$")
-                    
.redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
+    var clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator", rootCl)
+            .redefining("^.*FetchTheTokenAnnotator$")
+            .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    JCas jcas = makeJCas(clForCas);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotator.class,
-            clForAddATokenAnnotator);
-    AnalysisEngine fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
+    var jcas = makeJCas(clForCas);
+    var addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, 
clForAddATokenAnnotator);
+    var fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
             clForFetchTheTokenAnnotator);
 
     jcas.setDocumentText("test");
@@ -210,7 +208,7 @@ public class JCasClassLoaderTest {
     addATokenAnnotator.process(jcas);
     fetchTheTokenAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNull();
       softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
@@ -245,23 +243,22 @@ public class JCasClassLoaderTest {
    * was first initialized.
    */
   @Test
-  public void thatAnnotatorsCanLocallyUseDifferentJCasWrappers() throws 
Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+  void thatAnnotatorsCanLocallyUseDifferentJCasWrappers() throws Exception {
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl);
+    var clForCas = new IsolatingClassloader("CAS", rootCl);
 
-    ClassLoader clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
+    var clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
             .redefining("^.*AddATokenAnnotator$")
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator",
-            rootCl).redefining("^.*FetchTheTokenAnnotator$")
-                    
.redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
+    var clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator", rootCl)
+            .redefining("^.*FetchTheTokenAnnotator$")
+            .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    JCas jcas = makeJCas(clForCas);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotator.class,
-            clForAddATokenAnnotator);
-    AnalysisEngine fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
+    var jcas = makeJCas(clForCas);
+    var addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, 
clForAddATokenAnnotator);
+    var fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
             clForFetchTheTokenAnnotator);
 
     jcas.setDocumentText("test");
@@ -269,7 +266,7 @@ public class JCasClassLoaderTest {
     addATokenAnnotator.process(jcas);
     fetchTheTokenAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNotNull();
       softly.assertThat(casTokenClassViaCas)
               .as("System-level Token wrapper loader and Token wrapper in the 
CAS are the same")
@@ -314,27 +311,26 @@ public class JCasClassLoaderTest {
    * type in both annotators.
    */
   @Test
-  public void thatTypeSystemCanComeFromItsOwnClassLoader() throws Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+  void thatTypeSystemCanComeFromItsOwnClassLoader() throws Exception {
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+    var clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForTS = new IsolatingClassloader("TS", rootCl)
+    var clForTS = new IsolatingClassloader("TS", rootCl)
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
-            .redefining("^.*AddATokenAnnotator$")
+    var clForAddATokenAnnotator = new 
IsolatingClassloader("AddATokenAnnotator", rootCl)
+            .redefining(AddATokenAnnotator.class)
             .delegating("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*", 
clForTS);
 
-    ClassLoader clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator",
-            rootCl).redefining("^.*FetchTheTokenAnnotator$")
-                    
.delegating("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*", clForTS);
+    var clForFetchTheTokenAnnotator = new 
IsolatingClassloader("FetchTheTokenAnnotator", rootCl)
+            .redefining(FetchTheTokenAnnotator.class)
+            .delegating("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*", 
clForTS);
 
-    JCas jcas = makeJCas(clForCas);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotator.class,
-            clForAddATokenAnnotator);
-    AnalysisEngine fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
+    var jcas = makeJCas(clForCas);
+    var addATokenAnnotator = makeAnalysisEngine(AddATokenAnnotator.class, 
clForAddATokenAnnotator);
+    var fetchTheTokenAnnotator = 
makeAnalysisEngine(FetchTheTokenAnnotator.class,
             clForFetchTheTokenAnnotator);
 
     jcas.setDocumentText("test");
@@ -342,7 +338,7 @@ public class JCasClassLoaderTest {
     addATokenAnnotator.process(jcas);
     fetchTheTokenAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNull();
       softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
@@ -365,30 +361,28 @@ public class JCasClassLoaderTest {
   }
 
   @Test
-  public void thatFSArraySpliteratorReturnsProperJCasWrapper() throws 
Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+  void thatFSArraySpliteratorReturnsProperJCasWrapper() throws Exception {
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+    var clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForCreators = new IsolatingClassloader("Creators", rootCl)
+    var clForCreators = new IsolatingClassloader("Creators", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
-            .redefining("^.*AddATokenAnnotatorNoJCas$")
-            .redefining("^.*AddTokenToArrayAnnotatorNoJCas$");
+            .redefining(AddATokenAnnotatorNoJCas.class)
+            .redefining(AddTokenToArrayAnnotatorNoJCas.class);
 
-    ClassLoader clForAccessors = new IsolatingClassloader("Accessors", rootCl)
+    var clForAccessors = new IsolatingClassloader("Accessors", rootCl)
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
-            .redefining("^.*FetchTokenFromArrayViaSpliteratorAnnotator$");
+            .redefining(FetchTokenFromArrayViaSpliteratorAnnotator.class);
 
-    TypeSystemDescription tsd = mergeTypeSystems(
-            asList(loadTokensAndSentencesTS(), makeArrayTestTS()));
+    var tsd = mergeTypeSystems(asList(loadTokensAndSentencesTS(), 
makeArrayTestTS()));
 
-    JCas jcas = makeJCas(clForCas, tsd);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotatorNoJCas.class,
+    var jcas = makeJCas(clForCas, tsd);
+    var addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotatorNoJCas.class, clForCreators);
+    var addTokenToArrayAnnotator = 
makeAnalysisEngine(AddTokenToArrayAnnotatorNoJCas.class,
             clForCreators);
-    AnalysisEngine addTokenToArrayAnnotator = makeAnalysisEngine(
-            AddTokenToArrayAnnotatorNoJCas.class, clForCreators);
-    AnalysisEngine fetchTokenFromArrayViaSpliteratorAnnotator = 
makeAnalysisEngine(
+    var fetchTokenFromArrayViaSpliteratorAnnotator = makeAnalysisEngine(
             FetchTokenFromArrayViaSpliteratorAnnotator.class, clForAccessors);
 
     jcas.setDocumentText("test");
@@ -397,7 +391,7 @@ public class JCasClassLoaderTest {
     addTokenToArrayAnnotator.process(jcas);
     fetchTokenFromArrayViaSpliteratorAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNull();
       softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
@@ -410,30 +404,28 @@ public class JCasClassLoaderTest {
   }
 
   @Test
-  public void thatFSArrayToArrayReturnsProperJCasWrapper() throws Exception {
-    ClassLoader rootCl = getClass().getClassLoader();
+  void thatFSArrayToArrayReturnsProperJCasWrapper() throws Exception {
+    var rootCl = getClass().getClassLoader();
 
-    IsolatingClassloader clForCas = new IsolatingClassloader("CAS", rootCl)
+    var clForCas = new IsolatingClassloader("CAS", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*");
 
-    ClassLoader clForCreators = new IsolatingClassloader("Creators", rootCl)
+    var clForCreators = new IsolatingClassloader("Creators", rootCl)
             .hiding("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
-            .redefining("^.*AddATokenAnnotatorNoJCas$")
-            .redefining("^.*AddTokenToArrayAnnotatorNoJCas$");
+            .redefining(AddATokenAnnotatorNoJCas.class)
+            .redefining(AddTokenToArrayAnnotatorNoJCas.class);
 
-    ClassLoader clForAccessors = new IsolatingClassloader("Accessors", rootCl)
+    var clForAccessors = new IsolatingClassloader("Accessors", rootCl)
             .redefining("org\\.apache\\.uima\\.cas\\.test\\.Token(_Type)?.*")
-            .redefining("^.*FetchTokenFromArrayViaToArrayAnnotator$");
+            .redefining(FetchTokenFromArrayViaToArrayAnnotator.class);
 
-    TypeSystemDescription tsd = mergeTypeSystems(
-            asList(loadTokensAndSentencesTS(), makeArrayTestTS()));
+    var tsd = mergeTypeSystems(asList(loadTokensAndSentencesTS(), 
makeArrayTestTS()));
 
-    JCas jcas = makeJCas(clForCas, tsd);
-    AnalysisEngine addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotatorNoJCas.class,
+    var jcas = makeJCas(clForCas, tsd);
+    var addATokenAnnotator = 
makeAnalysisEngine(AddATokenAnnotatorNoJCas.class, clForCreators);
+    var addTokenToArrayAnnotator = 
makeAnalysisEngine(AddTokenToArrayAnnotatorNoJCas.class,
             clForCreators);
-    AnalysisEngine addTokenToArrayAnnotator = makeAnalysisEngine(
-            AddTokenToArrayAnnotatorNoJCas.class, clForCreators);
-    AnalysisEngine fetchTokenFromArrayViaSpliteratorAnnotator = 
makeAnalysisEngine(
+    var fetchTokenFromArrayViaSpliteratorAnnotator = makeAnalysisEngine(
             FetchTokenFromArrayViaToArrayAnnotator.class, clForAccessors);
 
     jcas.setDocumentText("test");
@@ -442,7 +434,7 @@ public class JCasClassLoaderTest {
     addTokenToArrayAnnotator.process(jcas);
     fetchTokenFromArrayViaSpliteratorAnnotator.process(jcas);
 
-    try (AutoCloseableSoftAssertions softly = new 
AutoCloseableSoftAssertions()) {
+    try (var softly = new AutoCloseableSoftAssertions()) {
       softly.assertThat(casTokenClassViaClassloader).isNull();
       softly.assertThat(casTokenClassViaCas).isSameAs(Annotation.class);
       softly.assertThat(addTokenAETokenClass).isNotNull();
@@ -454,6 +446,41 @@ public class JCasClassLoaderTest {
     }
   }
 
+  @Test
+  void thatFeatureRangeClassRedefinedInPearDoesNotCauseProblems(@TempDir File 
aTemp)
+          throws Exception {
+    LOG.info("-- Base runtime context 
--------------------------------------------------");
+    LOG.info("{} loaded using {}", Token.class, Token.class.getClassLoader());
+    LOG.info("{} loaded using {}", TokenType.class, 
TokenType.class.getClassLoader());
+
+    var tsd = UIMAFramework.getXMLParser().parseTypeSystemDescription(
+            new 
XMLInputSource("src/test/java/org/apache/uima/jcas/test/generatedx.xml"));
+
+    LOG.info("-- JCas classloader context 
----------------------------------------------");
+    var rootCl = getClass().getClassLoader();
+    var clForCas = new IsolatingClassloader("CAS Classloader", rootCl) //
+            .redefining(TokenType.class) //
+            .redefining(JCasClassProviderForTesting.class) //
+            .redefining(JCasCreator.class);
+
+    var jcasCreatorClass = clForCas.loadClass(JCasCreatorImpl.class.getName());
+    var creator = (JCasCreator) 
jcasCreatorClass.getDeclaredConstructor().newInstance();
+    var jcas = creator.createJCas(clForCas, tsd);
+    var cas = jcas.getCas();
+
+    var t = cas.createFS(cas.getTypeSystem().getType(Token.class.getName()));
+    var tt = 
cas.createFS(cas.getTypeSystem().getType(TokenType.class.getName()));
+
+    LOG.info("{} loaded using {}", t.getClass(), 
t.getClass().getClassLoader());
+    LOG.info("{} loaded using {}", tt.getClass(), 
tt.getClass().getClassLoader());
+
+    assertThat(t.getClass().getClassLoader()) //
+            .isSameAs(Token.class.getClassLoader());
+
+    assertThat(tt.getClass().getClassLoader()) //
+            .isSameAs(clForCas);
+  }
+
   public static Class<?> loadTokenClass(ClassLoader cl) {
     try {
       return cl.loadClass(TYPE_NAME_TOKEN);
@@ -493,9 +520,11 @@ public class JCasClassLoaderTest {
   }
 
   private JCas makeJCas(IsolatingClassloader cl, TypeSystemDescription tsd) 
throws Exception {
-    cl.redefining("^.*JCasCreatorImpl$");
-    Class jcasCreatorClass = cl.loadClass(JCasCreatorImpl.class.getName());
-    JCasCreator creator = (JCasCreator) jcasCreatorClass.newInstance();
+    cl.redefining(JCasCreatorImpl.class);
+    var jcasCreatorClass = cl.loadClass(JCasCreatorImpl.class.getName());
+    var declaredConstructor = jcasCreatorClass.getDeclaredConstructor();
+    declaredConstructor.setAccessible(true);
+    var creator = (JCasCreator) declaredConstructor.newInstance();
     return creator.createJCas(cl, tsd);
   }
 
@@ -505,12 +534,13 @@ public class JCasClassLoaderTest {
    */
   private AnalysisEngine makeAnalysisEngine(Class<? extends 
Annotator_ImplBase> aeClass,
           ClassLoader cl) throws ResourceInitializationException, 
MalformedURLException {
-    ResourceManager resMgr = newDefaultResourceManager();
+    var resMgr = newDefaultResourceManager();
     resMgr.setExtensionClassLoader(cl, false);
+
     printTokenClassLoaderInfo("AE creation: " + aeClass.getSimpleName(),
             resMgr.getExtensionClassLoader());
-    AnalysisEngineDescription desc = getResourceSpecifierFactory()
-            .createAnalysisEngineDescription();
+
+    var desc = getResourceSpecifierFactory().createAnalysisEngineDescription();
     desc.setAnnotatorImplementationName(aeClass.getName());
     desc.setPrimitive(true);
     return produceAnalysisEngine(desc, resMgr, null);
@@ -522,13 +552,14 @@ public class JCasClassLoaderTest {
    */
   private AnalysisEngine makeAnalysisEngine(Class<? extends 
Annotator_ImplBase> aeClass,
           File... classPath) throws ResourceInitializationException, 
MalformedURLException {
-    String cp = 
Stream.of(classPath).map(Object::toString).collect(joining(File.pathSeparator));
-    ResourceManager resMgr = newDefaultResourceManager();
+    var cp = 
Stream.of(classPath).map(Object::toString).collect(joining(File.pathSeparator));
+    var resMgr = newDefaultResourceManager();
     resMgr.setExtensionClassPath(cp, false);
+
     printTokenClassLoaderInfo("AE creation: " + aeClass.getSimpleName(),
             resMgr.getExtensionClassLoader());
-    AnalysisEngineDescription desc = getResourceSpecifierFactory()
-            .createAnalysisEngineDescription();
+
+    var desc = getResourceSpecifierFactory().createAnalysisEngineDescription();
     desc.setAnnotatorImplementationName(aeClass.getName());
     desc.setPrimitive(true);
     return produceAnalysisEngine(desc, resMgr, null);
@@ -575,8 +606,8 @@ public class JCasClassLoaderTest {
   public static class AddATokenAnnotatorNoJCas extends JCasAnnotator_ImplBase {
     @Override
     public void process(JCas aJCas) throws AnalysisEngineProcessException {
-      AnnotationFS token = aJCas.getCas().createAnnotation(
-              aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN), 0, 
aJCas.getDocumentText().length());
+      var token = 
aJCas.getCas().createAnnotation(aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN), 
0,
+              aJCas.getDocumentText().length());
       addTokenAETokenClass = token.getClass();
       System.out.printf("[AE runtime: %s] CAS class loader: %s%n", 
getClass().getName(),
               aJCas.getCasImpl().getJCasClassLoader());
@@ -593,13 +624,13 @@ public class JCasClassLoaderTest {
     @Override
     public void process(JCas aJCas) throws AnalysisEngineProcessException {
 
-      AnnotationFS token = (AnnotationFS) aJCas
-              .select(aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN)).single();
+      var token = (AnnotationFS) 
aJCas.select(aJCas.getTypeSystem().getType(TYPE_NAME_TOKEN))
+              .single();
       tokenClassAddedToArray = token.getClass();
 
-      AnnotationFS arrayHost = aJCas.getCas()
+      var arrayHost = aJCas.getCas()
               
.createAnnotation(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST), 0, 0);
-      FSArray array = new FSArray<>(aJCas, 1);
+      var array = new FSArray<>(aJCas, 1);
       array.set(0, token);
       arrayHost.setFeatureValue(
               
arrayHost.getType().getFeatureByBaseName(FEAT_NAME_ARRAY_HOST_VALUES), array);
@@ -608,12 +639,13 @@ public class JCasClassLoaderTest {
   }
 
   public static class FetchTokenFromArrayViaSpliteratorAnnotator extends 
JCasAnnotator_ImplBase {
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
     public void process(JCas aJCas) throws AnalysisEngineProcessException {
       FeatureStructure arrayHost = 
aJCas.select(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST))
               .single();
 
-      FSArray array = (FSArray) arrayHost.getFeatureValue(
+      var array = (FSArray) arrayHost.getFeatureValue(
               
arrayHost.getType().getFeatureByBaseName(FEAT_NAME_ARRAY_HOST_VALUES));
 
       tokenClassFetchedFromArray = StreamSupport.stream(array.spliterator(), 
false).findFirst()
@@ -622,15 +654,15 @@ public class JCasClassLoaderTest {
   }
 
   public static class FetchTokenFromArrayViaToArrayAnnotator extends 
JCasAnnotator_ImplBase {
+    @SuppressWarnings({ "unchecked", "rawtypes" })
     @Override
     public void process(JCas aJCas) throws AnalysisEngineProcessException {
-      FeatureStructure arrayHost = 
aJCas.select(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST))
-              .single();
+      var arrayHost = 
aJCas.select(aJCas.getTypeSystem().getType(TYPE_NAME_ARRAY_HOST)).single();
 
-      FSArray array = (FSArray) arrayHost.getFeatureValue(
+      var array = (FSArray) arrayHost.getFeatureValue(
               
arrayHost.getType().getFeatureByBaseName(FEAT_NAME_ARRAY_HOST_VALUES));
 
-      Class withEmptyTemplate = array.toArray(new TOP[0])[0].getClass();
+      var withEmptyTemplate = array.toArray(new TOP[0])[0].getClass();
       tokenClassFetchedFromArray = array.toArray(new TOP[1])[0].getClass();
       assertThat(tokenClassFetchedFromArray).isSameAs(withEmptyTemplate);
     }
@@ -643,13 +675,13 @@ public class JCasClassLoaderTest {
       System.out.printf("%s class loader: %s%n", getClass().getName(), 
getClass().getClassLoader());
       System.out.printf("[AE runtime: %s] %s %d %n", getClass().getName(), 
Token.class.getName(),
               Token.class.hashCode());
-      Object casToken = aJCas.getAllIndexedFS(Token.class).get();
+      var casToken = aJCas.getAllIndexedFS(Token.class).get();
       System.out.printf("[AE runtime CAS: %s] %s %d %n", getClass().getName(),
               casToken.getClass().getName(), casToken.getClass().hashCode());
       indexedTokenClass = casToken.getClass();
 
       try {
-        List<Token> tokens = new ArrayList<>();
+        var tokens = new ArrayList<Token>();
         aJCas.getAllIndexedFS(Token.class).forEachRemaining(tokens::add);
       } catch (ClassCastException e) {
         fetchThrowsClassCastException = true;
@@ -657,98 +689,4 @@ public class JCasClassLoaderTest {
       }
     }
   }
-
-  /**
-   * Special ClassLoader that helps us modeling different class loader 
topologies.
-   */
-  public static class IsolatingClassloader extends ClassLoader {
-
-    private final Set<String> hideClassesPatterns = new HashSet<>();
-    private final Set<String> redefineClassesPatterns = new HashSet<>();
-    private final Map<String, ClassLoader> delegates = new LinkedHashMap<>();
-    private final String id;
-
-    private Map<String, Class<?>> loadedClasses = new HashMap<>();
-
-    public IsolatingClassloader(String name, ClassLoader parent) {
-      super(parent);
-
-      id = name;
-    }
-
-    public IsolatingClassloader hiding(String... patterns) {
-      hideClassesPatterns.addAll(asList(patterns));
-      return this;
-    }
-
-    public IsolatingClassloader redefining(String... patterns) {
-      redefineClassesPatterns.addAll(asList(patterns));
-      return this;
-    }
-
-    public IsolatingClassloader delegating(String pattern, ClassLoader 
delegate) {
-      delegates.put(pattern, delegate);
-      return this;
-    }
-
-    @Override
-    public String toString() {
-      StringBuilder sb = new StringBuilder();
-      sb.append("[");
-      sb.append(id);
-      sb.append(", loaded=");
-      sb.append(loadedClasses.size());
-      sb.append("]");
-      return sb.toString();
-    }
-
-    @Override
-    protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
-      synchronized (getClassLoadingLock(name)) {
-        Optional<ClassLoader> delegate = delegates.entrySet().stream()
-                .filter(e -> 
name.matches(e.getKey())).map(Entry::getValue).findFirst();
-        if (delegate.isPresent()) {
-          return delegate.get().loadClass(name);
-        }
-
-        if (hideClassesPatterns.stream().anyMatch(name::matches)) {
-          System.out.printf("[%s] prevented access to hidden class: %s%n", id, 
name);
-          throw new ClassNotFoundException(name);
-        }
-
-        if (redefineClassesPatterns.stream().anyMatch(name::matches)) {
-          Class<?> loadedClass = loadedClasses.get(name);
-          if (loadedClass != null) {
-            return loadedClass;
-          }
-
-          System.out.printf("[%s] redefining class: %s%n", id, name);
-
-          String internalName = name.replace(".", "/") + ".class";
-          InputStream is = getParent().getResourceAsStream(internalName);
-          if (is == null) {
-            throw new ClassNotFoundException(name);
-          }
-
-          try {
-            byte[] bytes = IOUtils.toByteArray(is);
-            Class<?> cls = defineClass(name, bytes, 0, bytes.length);
-            if (cls.getPackage() == null) {
-              int packageSeparator = name.lastIndexOf('.');
-              if (packageSeparator != -1) {
-                String packageName = name.substring(0, packageSeparator);
-                definePackage(packageName, null, null, null, null, null, null, 
null);
-              }
-            }
-            loadedClasses.put(name, cls);
-            return cls;
-          } catch (IOException ex) {
-            throw new ClassNotFoundException("Cannot load resource for class 
[" + name + "]", ex);
-          }
-        }
-
-        return super.loadClass(name, resolve);
-      }
-    }
-  }
-}
\ No newline at end of file
+}
diff --git a/uimaj-parent/pom.xml b/uimaj-parent/pom.xml
index 2419a8d5d..7ae42db3a 100644
--- a/uimaj-parent/pom.xml
+++ b/uimaj-parent/pom.xml
@@ -225,6 +225,11 @@
         <artifactId>slf4j-api</artifactId>
         <version>${slf4j-version}</version>
       </dependency>
+      <dependency>
+        <groupId>org.slf4j</groupId>
+        <artifactId>slf4j-simple</artifactId>
+        <version>${slf4j-version}</version>
+      </dependency>
 
       <dependency>
         <groupId>org.apache.logging.log4j</groupId>
diff --git 
a/uimaj-test-util/src/main/java/org/apache/uima/test/IsolatingClassloader.java 
b/uimaj-test-util/src/main/java/org/apache/uima/test/IsolatingClassloader.java
new file mode 100644
index 000000000..4abcb4dc2
--- /dev/null
+++ 
b/uimaj-test-util/src/main/java/org/apache/uima/test/IsolatingClassloader.java
@@ -0,0 +1,157 @@
+/*
+ * 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 org.apache.uima.test;
+
+import static java.util.Arrays.asList;
+import static java.util.regex.Pattern.quote;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+
+/**
+ * Special ClassLoader that helps us modeling different class loader 
topologies.
+ */
+public class IsolatingClassloader extends ClassLoader {
+
+  private final Set<String> hideClassesPatterns = new HashSet<>();
+  private final Set<String> redefineClassesPatterns = new HashSet<>();
+  private final Map<String, ClassLoader> delegates = new LinkedHashMap<>();
+  private final String id;
+
+  private Map<String, Class<?>> loadedClasses = new HashMap<>();
+
+  public IsolatingClassloader(String name, ClassLoader parent) {
+    super(parent);
+
+    id = name;
+  }
+
+  public IsolatingClassloader hiding(Package... packages) {
+    for (var pack : packages) {
+      hideClassesPatterns.add(quote(pack.getName()) + "\\..*");
+    }
+    return this;
+  }
+
+  public IsolatingClassloader hiding(Class<?>... classes) {
+    for (var clazz : classes) {
+      hideClassesPatterns.add(quote(clazz.getName()));
+    }
+    return this;
+  }
+
+  public IsolatingClassloader hiding(String... patterns) {
+    hideClassesPatterns.addAll(asList(patterns));
+    return this;
+  }
+
+  public IsolatingClassloader redefining(Package... packages) {
+    for (var pack : packages) {
+      redefineClassesPatterns.add(quote(pack.getName()) + "\\..*");
+    }
+    return this;
+  }
+
+  public IsolatingClassloader redefining(Class<?>... classes) {
+    for (var clazz : classes) {
+      redefineClassesPatterns.add(quote(clazz.getName()));
+    }
+    return this;
+  }
+
+  public IsolatingClassloader redefining(String... patterns) {
+    redefineClassesPatterns.addAll(asList(patterns));
+    return this;
+  }
+
+  public IsolatingClassloader delegating(String pattern, ClassLoader delegate) 
{
+    delegates.put(pattern, delegate);
+    return this;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("[");
+    sb.append(id);
+    sb.append(", loaded=");
+    sb.append(loadedClasses.size());
+    sb.append("]");
+    return sb.toString();
+  }
+
+  @Override
+  protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException {
+    synchronized (getClassLoadingLock(name)) {
+      Optional<ClassLoader> delegate = delegates.entrySet().stream()
+              .filter(e -> 
name.matches(e.getKey())).map(Entry::getValue).findFirst();
+      if (delegate.isPresent()) {
+        return delegate.get().loadClass(name);
+      }
+
+      if (hideClassesPatterns.stream().anyMatch(name::matches)) {
+        System.out.printf("[%s] prevented access to hidden class: %s%n", id, 
name);
+        throw new ClassNotFoundException(name);
+      }
+
+      if (redefineClassesPatterns.stream().anyMatch(name::matches)) {
+        Class<?> loadedClass = loadedClasses.get(name);
+        if (loadedClass != null) {
+          return loadedClass;
+        }
+
+        System.out.printf("[%s] redefining class: %s%n", id, name);
+
+        String internalName = name.replace(".", "/") + ".class";
+        InputStream is = getParent().getResourceAsStream(internalName);
+        if (is == null) {
+          throw new ClassNotFoundException(name);
+        }
+
+        try {
+          var buffer = new ByteArrayOutputStream();
+          is.transferTo(buffer);
+          byte[] bytes = buffer.toByteArray();
+          Class<?> cls = defineClass(name, bytes, 0, bytes.length);
+          if (cls.getPackage() == null) {
+            int packageSeparator = name.lastIndexOf('.');
+            if (packageSeparator != -1) {
+              String packageName = name.substring(0, packageSeparator);
+              definePackage(packageName, null, null, null, null, null, null, 
null);
+            }
+          }
+          loadedClasses.put(name, cls);
+          return cls;
+        } catch (IOException ex) {
+          throw new ClassNotFoundException("Cannot load resource for class [" 
+ name + "]", ex);
+        }
+      }
+
+      return super.loadClass(name, resolve);
+    }
+  }
+}

Reply via email to