You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@uima.apache.org by re...@apache.org on 2021/11/05 13:07:57 UTC

[uima-uimaj] branch bugfix/UIMA-6393-Circular-imports-break-resource-manager-cache updated: [UIMA-6393]: Circular imports break resource manager cache

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

rec pushed a commit to branch bugfix/UIMA-6393-Circular-imports-break-resource-manager-cache
in repository https://gitbox.apache.org/repos/asf/uima-uimaj.git


The following commit(s) were added to refs/heads/bugfix/UIMA-6393-Circular-imports-break-resource-manager-cache by this push:
     new d9e7c20  [UIMA-6393]: Circular imports break resource manager cache
d9e7c20 is described below

commit d9e7c2081b7f7c3b74a7babde6553f139042ae20
Author: Richard Eckart de Castilho <re...@apache.org>
AuthorDate: Fri Nov 5 14:07:50 2021 +0100

    [UIMA-6393]: Circular imports break resource manager cache
    
    - Started refactoring the import resolving - WIP
    - Added another test
    - Renamed some test files
---
 .../metadata/impl/TypeSystemDescription_impl.java  | 290 ++++++++++++++++-----
 .../impl/TypeSystemDescription_implTest.java       |  45 +++-
 .../{Circular1.xml => Loop-with-2-nodes-1.xml}     |   2 +-
 .../{Circular2.xml => Loop-with-2-nodes-2.xml}     |   2 +-
 .../{Circular1.xml => Loop-with-3-nodes-1.xml}     |   4 +-
 .../{Circular1.xml => Loop-with-3-nodes-2.xml}     |   4 +-
 .../{Circular1.xml => Loop-with-3-nodes-3.xml}     |   4 +-
 7 files changed, 264 insertions(+), 87 deletions(-)

diff --git a/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_impl.java b/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_impl.java
index 6e9f3f2..7e5f1cb 100644
--- a/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_impl.java
+++ b/uimaj-core/src/main/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_impl.java
@@ -19,10 +19,12 @@
 
 package org.apache.uima.resource.metadata.impl;
 
+import static java.util.Arrays.asList;
+import static org.apache.uima.UIMAFramework.getXMLParser;
+
 import java.io.IOException;
 import java.net.URL;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -46,11 +48,11 @@ import org.apache.uima.util.XMLizable;
  * 
  * 
  */
-public class TypeSystemDescription_impl extends MetaDataObject_impl implements
-        TypeSystemDescription {
+public class TypeSystemDescription_impl extends MetaDataObject_impl
+        implements TypeSystemDescription {
 
   static final long serialVersionUID = -3372766232454730201L;
-    
+
   private String mName;
 
   private String mVersion;
@@ -73,6 +75,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#getName()
    */
+  @Override
   public String getName() {
     return mName;
   }
@@ -80,6 +83,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#setName(String)
    */
+  @Override
   public void setName(String aName) {
     mName = aName;
   }
@@ -87,6 +91,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#getVersion()
    */
+  @Override
   public String getVersion() {
     return mVersion;
   }
@@ -94,6 +99,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#setVersion(String)
    */
+  @Override
   public void setVersion(String aVersion) {
     mVersion = aVersion;
   }
@@ -101,6 +107,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#getDescription()
    */
+  @Override
   public String getDescription() {
     return mDescription;
   }
@@ -108,6 +115,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#setDescription(String)
    */
+  @Override
   public void setDescription(String aDescription) {
     mDescription = aDescription;
   }
@@ -115,6 +123,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#getVendor()
    */
+  @Override
   public String getVendor() {
     return mVendor;
   }
@@ -122,6 +131,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see ResourceMetaData#setVendor(String)
    */
+  @Override
   public void setVendor(String aVendor) {
     mVendor = aVendor;
   }
@@ -129,6 +139,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#getImports()
    */
+  @Override
   public Import[] getImports() {
     return mImports;
   }
@@ -136,6 +147,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#setImports(Import[])
    */
+  @Override
   public void setImports(Import[] aImports) {
     if (aImports == null) {
       throw new UIMA_IllegalArgumentException(UIMA_IllegalArgumentException.ILLEGAL_ARGUMENT,
@@ -147,6 +159,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#getTypes()
    */
+  @Override
   public TypeDescription[] getTypes() {
     return mTypes;
   }
@@ -154,6 +167,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#setTypes(TypeDescription[])
    */
+  @Override
   public void setTypes(TypeDescription[] aTypes) {
     if (aTypes == null) {
       throw new UIMA_IllegalArgumentException(UIMA_IllegalArgumentException.ILLEGAL_ARGUMENT,
@@ -165,6 +179,7 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#addType(String, String, String)
    */
+  @Override
   public TypeDescription addType(String aTypeName, String aDescription, String aSupertypeName) {
     // create new type description
     TypeDescription newType = new TypeDescription_impl(aTypeName, aDescription, aSupertypeName);
@@ -186,10 +201,12 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
   /**
    * @see TypeSystemDescription#getType(java.lang.String)
    */
+  @Override
   public TypeDescription getType(String aTypeName) {
     for (int i = 0; i < mTypes.length; i++) {
-      if (aTypeName.equals(mTypes[i].getName()))
+      if (aTypeName.equals(mTypes[i].getName())) {
         return mTypes[i];
+      }
     }
     return null;
   }
@@ -198,95 +215,224 @@ public class TypeSystemDescription_impl extends MetaDataObject_impl implements
    * @see TypeSystemDescription#resolveImports()
    */
   // allow these calls to be done multiple times on this same object, in different threads
+  @Override
   public synchronized void resolveImports() throws InvalidXMLException {
-    if (getImports().length == 0) {
-      resolveImports(null, null);
-    } else {
-      resolveImports(new TreeSet<>(), UIMAFramework.newDefaultResourceManager());
-    }
+    resolveImports(null, UIMAFramework.newDefaultResourceManager());
   }
 
-  public synchronized void resolveImports(ResourceManager aResourceManager) throws InvalidXMLException {
-    resolveImports((getImports().length == 0) ? null : new TreeSet<>(), aResourceManager);
+  @Override
+  public synchronized void resolveImports(ResourceManager aResourceManager)
+          throws InvalidXMLException {
+    resolveImports(null, aResourceManager);
   }
 
+  @Override
   public synchronized void resolveImports(Collection<String> aAlreadyImportedTypeSystemURLs,
+          ResourceManager aResMgr) throws InvalidXMLException {
+
+    if (getImports().length == 0) {
+      this.setImports(Import.EMPTY_IMPORTS);
+    }
+
+    List<String> stack = new ArrayList<>();
+    Set<String> seen = new TreeSet<>();
+    if (aAlreadyImportedTypeSystemURLs != null) {
+      seen.addAll(aAlreadyImportedTypeSystemURLs);
+    }
+
+    // add our own URL, if known, to the collection of already imported URLs
+    String url = getSourceUrl() != null ? getSourceUrl().toString() : "<TOP>";
+    seen.add(url);
+    stack.add(url);
+
+    Map<String, XMLizable> importCache = ((ResourceManager_impl) aResMgr).getImportCache();
+    synchronized (importCache) {
+      _resolveImports(url, this, stack, seen, aResMgr);
+    }
+  }
+
+  /**
+   * @param aUrl
+   *          URL of the currently processed description
+   * @param aStack
+   *          current traversal stack. This is used to discover <b>at which point</b> we entered
+   *          into a loop.
+   * @param aSeen
+   *          all URLs that were already processed. This is used to see <b>if</b> we enter into any
+   *          kind of loop and to discover the transitive imports of a given import.
+   * @param aResourceManager
+   *          the resource manager used to resolve imports.
+   * @return the stack depth of the item in the stack which triggered the current loop or
+   *         {@code -1}.
+   * @throws InvalidXMLException
+   *           if an import could not be resolved.
+   */
+  private static Loop _resolveImports(String aUrl, TypeSystemDescription_impl aDesc,
+          List<String> aStack, Collection<String> aSeen,
           ResourceManager aResourceManager) throws InvalidXMLException {
-    List<TypeDescription> importedTypes = null;
-    if (getImports().length != 0) {
-      // add our own URL, if known, to the collection of already imported URLs
-      if (getSourceUrl() != null) {
-        aAlreadyImportedTypeSystemURLs.add(getSourceUrl().toString());
+
+    Map<String, XMLizable> importCache = ((ResourceManager_impl) aResourceManager).getImportCache();
+    Map<String, Set<String>> transitiveImportCache = ((ResourceManager_impl) aResourceManager)
+            .getImportUrlsCache();
+
+    List<TypeDescription> importedTypes = new ArrayList<>();
+    Loop loop = null;
+    for (Import imp : aDesc.getImports()) {
+      // ensure Import's relative path base is set, to allow for users who create new import objects
+      if (imp instanceof Import_impl) {
+        ((Import_impl) imp).setSourceUrlIfNull(aDesc.getSourceUrl());
+      }
+
+      URL impUrl = imp.findAbsoluteUrl(aResourceManager);
+      String sImpUrl = impUrl.toString();
+      if (aStack.contains(sImpUrl)) {
+        loop = loop == null ? new Loop(sImpUrl, aStack) : loop.extend(sImpUrl, aStack, loop);
+        continue;
       }
-  
-      importedTypes = new ArrayList<>();
-      Import[] imports = getImports();
-      for (int i = 0; i < imports.length; i++) {
-        // make sure Import's relative path base is set, to allow for users who create
-        // new import objects
-        if (imports[i] instanceof Import_impl) {
-          ((Import_impl) imports[i]).setSourceUrlIfNull(this.getSourceUrl());
+
+      aSeen.add(sImpUrl);
+      aStack.add(sImpUrl);
+
+      // check the import cache
+      String urlString = sImpUrl;
+      XMLizable cachedObject = importCache.get(urlString);
+      if (cachedObject instanceof TypeSystemDescription) {
+        TypeSystemDescription desc = (TypeSystemDescription) cachedObject;
+        // Add the URLs parsed for this cached object to the list already-parsed (UIMA-5058)
+        Set<String> transitiveImports = transitiveImportCache.get(urlString);
+        assert transitiveImports != null : "TSD found in cache but no cached transitive imports info";
+        importedTypes.addAll(asList(desc.getTypes()));
+
+        for (String tImp : transitiveImports) {
+          if (aSeen.contains(tImp)) {
+            loop = loop == null ? new Loop(tImp, aStack) : loop.extend(tImp, aStack, loop);
+          } else {
+            aSeen.add(tImp);
+          }
         }
-        URL url = imports[i].findAbsoluteUrl(aResourceManager);
-        if (!aAlreadyImportedTypeSystemURLs.contains(url.toString())) {
-          aAlreadyImportedTypeSystemURLs.add(url.toString());
-          try {
-            resolveImport(url, aAlreadyImportedTypeSystemURLs, importedTypes, aResourceManager);
-          } catch (IOException e) {
-            throw new InvalidXMLException(InvalidXMLException.IMPORT_FAILED_COULD_NOT_READ_FROM_URL,
-                    new Object[] { url, imports[i].getSourceUrlString() }, e);
+      } else {
+        // Make a snapshot of the URLs we have "seen" until now
+        TreeSet<String> seenBeforeResolvingImport = new TreeSet<>(aSeen);
+
+        // Resolve the current import - this adds new URLs to the "seen" list
+        TypeSystemDescription_impl desc = loadImportedTypeSystem(imp, impUrl);
+        Loop nestedLoop = _resolveImports(urlString, desc, aStack, aSeen, aResourceManager);
+        importCache.put(urlString, desc);
+
+        // Calculate which URLs were newly added by the above resolving and add them to the
+        // transitive import cache
+        TreeSet<String> transitiveImports = new TreeSet<>(aSeen);
+        transitiveImports.removeAll(seenBeforeResolvingImport);
+        transitiveImportCache.put(urlString, transitiveImports);
+        importedTypes.addAll(asList(desc.getTypes()));
+
+        if (nestedLoop != null) {
+          for (String tImp : nestedLoop.members) {
+            if (aStack.contains(tImp)) {
+              loop = loop == null ? new Loop(tImp, aStack) : loop.extend(tImp, aStack, loop);
+            } else {
+              aSeen.add(tImp);
+            }
           }
         }
       }
+
+      aStack.remove(aStack.size() - 1);
     }
-    // maybe update this object
-    TypeDescription[] existingTypes = this.getTypes();
-    if (existingTypes == null) {
-      this.setTypes(existingTypes = TypeDescription.EMPTY_TYPE_DESCRIPTIONS);
-    }
-    if (null != importedTypes) {      
-      TypeDescription[] newTypes = new TypeDescription[existingTypes.length + importedTypes.size()];
-      System.arraycopy(existingTypes, 0, newTypes, 0, existingTypes.length);
-      for (int i = 0; i < importedTypes.size(); i++) {
-        newTypes[existingTypes.length + i] = importedTypes.get(i);
+
+    aDesc.addTypes(importedTypes);
+
+    // On the way back up here, we need to account for circular imports. So if we skipped
+    // resolving imports because the URL of the descriptor was in the "already imported" list,
+    // then we need to check if the descriptor imports any of the already imported descriptors
+    // and if so update its type list with the types from that descriptor...
+    // See: https://issues.apache.org/jira/browse/UIMA-6393
+    if (loop != null) {
+      int depth = aStack.indexOf(aUrl);
+      assert depth != -1 : "Current type system description not found on stack during import traversal";
+      if (depth == loop.entryPoint) {
+        importCache.put(aUrl, aDesc);
+        Set<String> transitiveImports = new TreeSet<>();
+        for (String member : loop.members) {
+          TypeSystemDescription tsd = (TypeSystemDescription) importCache.get(member);
+          tsd.setTypes(aDesc.getTypes());
+          Set<String> memberTransitiveImports = transitiveImportCache.get(member);
+          if (memberTransitiveImports != null) {
+            transitiveImports.addAll(memberTransitiveImports);
+          }
+        }
+        transitiveImportCache.put(aUrl, transitiveImports);
+        loop = null;
       }
-      this.setTypes(newTypes);
     }
+
     // clear imports
-    this.setImports(Import.EMPTY_IMPORTS);
+    aDesc.setImports(Import.EMPTY_IMPORTS);
+
+    return loop;
   }
 
-  private void resolveImport(URL aURL, Collection<String> aAlreadyImportedTypeSystemURLs,
-          Collection<TypeDescription> aResults, ResourceManager aResourceManager) throws InvalidXMLException,
-          IOException {
-    //check the import cache
-    TypeSystemDescription desc;    
-    String urlString = aURL.toString();
-    Map<String, XMLizable> importCache = ((ResourceManager_impl)aResourceManager).getImportCache();
-    Map<String, Set<String>> importUrlsCache = ((ResourceManager_impl)aResourceManager).getImportUrlsCache();
-    synchronized(importCache) {
-      XMLizable cachedObject = importCache.get(urlString);
-      if (cachedObject instanceof TypeSystemDescription) {
-        desc = (TypeSystemDescription)cachedObject;
-        // Add the URLs parsed for this cached object to the list already-parsed (UIMA-5058)
-        aAlreadyImportedTypeSystemURLs.addAll(importUrlsCache.get(urlString));
-      } else {   
-        XMLInputSource input;
-        input = new XMLInputSource(aURL);
-        desc = UIMAFramework.getXMLParser().parseTypeSystemDescription(input);
-        TreeSet<String> previouslyImported = new TreeSet<>(aAlreadyImportedTypeSystemURLs);
-        desc.resolveImports(aAlreadyImportedTypeSystemURLs, aResourceManager);
-        importCache.put(urlString, desc);
-        // Save the URLS parsed by this import 
-        TreeSet<String> locallyImported = new TreeSet<>(aAlreadyImportedTypeSystemURLs);
-        locallyImported.removeAll(previouslyImported);
-        importUrlsCache.put(urlString, locallyImported);
+  private static TypeSystemDescription_impl loadImportedTypeSystem(Import imp, URL impUrl)
+          throws InvalidXMLException {
+    try {
+      return (TypeSystemDescription_impl) getXMLParser()
+              .parseTypeSystemDescription(new XMLInputSource(impUrl));
+    } catch (IOException e) {
+      throw new InvalidXMLException(InvalidXMLException.IMPORT_FAILED_COULD_NOT_READ_FROM_URL,
+              new Object[] { impUrl, imp.getSourceUrlString() }, e);
+    }
+
+  }
+
+  private static class Loop {
+    final int entryPoint;
+    final String[] members;
+
+    /**
+     * Create new loop.
+     */
+    Loop(String aUrl, List<String> aStack) {
+      entryPoint = aStack.indexOf(aUrl);
+      members = aStack.subList(entryPoint, aStack.size()).stream().toArray(String[]::new);
+    }
+
+    private Loop(int aEntryPoint, String aUrl, Loop aOtherLoop) {
+      entryPoint = Math.min(aEntryPoint, aOtherLoop.entryPoint);
+      members = new String[aOtherLoop.members.length + 1];
+      members[0] = aUrl;
+      System.arraycopy(aOtherLoop.members, 0, members, 1, aOtherLoop.members.length);
+    }
+
+    Loop extend(String aUrl, List<String> aStack, Loop aOtherLoop) {
+      if (asList(aOtherLoop.members).indexOf(aUrl) != -1) {
+        return aOtherLoop;
       }
-      
+
+      return new Loop(aStack.indexOf(aUrl), aUrl, aOtherLoop);
+    }
+  }
+
+  private void addTypes(Collection<TypeDescription> aTypes) {
+    if (aTypes == null || aTypes.isEmpty()) {
+      return;
+    }
+
+    TypeDescription[] existingTypes = this.getTypes();
+    if (existingTypes == null) {
+      this.setTypes(existingTypes = TypeDescription.EMPTY_TYPE_DESCRIPTIONS);
+    }
+
+    TypeDescription[] newTypes = new TypeDescription[existingTypes.length + aTypes.size()];
+    System.arraycopy(existingTypes, 0, newTypes, 0, existingTypes.length);
+    int i = 0;
+    for (TypeDescription d : aTypes) {
+      newTypes[existingTypes.length + i] = d;
+      i++;
     }
-    aResults.addAll(Arrays.asList(desc.getTypes()));
+    this.setTypes(newTypes);
   }
 
+  @Override
   protected XmlizationInfo getXmlizationInfo() {
     return XMLIZATION_INFO;
   }
diff --git a/uimaj-core/src/test/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_implTest.java b/uimaj-core/src/test/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_implTest.java
index 2684808..f010bdd 100644
--- a/uimaj-core/src/test/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_implTest.java
+++ b/uimaj-core/src/test/java/org/apache/uima/resource/metadata/impl/TypeSystemDescription_implTest.java
@@ -277,24 +277,55 @@ public class TypeSystemDescription_implTest {
 
   @Test
   public void thatCircularImportsDoNotCrash() throws Exception {
-    File descriptor = getFile("TypeSystemDescriptionImplTest/Circular1.xml");
+    File descriptor = getFile("TypeSystemDescriptionImplTest/Loop-with-2-nodes-1.xml");
     TypeSystemDescription ts = xmlParser.parseTypeSystemDescription(new XMLInputSource(descriptor));
     ts.resolveImports();
     assertEquals(2, ts.getTypes().length);
   }
 
   @Test
-  public void thatCircularImportsDoNotConfuseResourceManagerCache() throws Exception {
+  public void thatLoopWithTwoNodesDoNotConfuseResourceManagerCache() throws Exception {
     ResourceManager resMgr = newDefaultResourceManager();
-    File descriptor = getFile("TypeSystemDescriptionImplTest/Circular1.xml");
-    TypeSystemDescription ts = xmlParser.parseTypeSystemDescription(new XMLInputSource(descriptor));
+    File circular1 = getFile("TypeSystemDescriptionImplTest/Loop-with-2-nodes-1.xml");
+    File circular2 = getFile("TypeSystemDescriptionImplTest/Loop-with-2-nodes-2.xml");
+    TypeSystemDescription ts = xmlParser.parseTypeSystemDescription(new XMLInputSource(circular1));
     ts.resolveImports(resMgr);
     assertThat(ts.getTypes()).hasSize(2);
 
     Map<String, XMLizable> cache = resMgr.getImportCache();
-    assertThat(cache).hasSize(1);
-    TypeSystemDescription cachedTsd = (TypeSystemDescription) cache.values().iterator().next();
-    assertThat(cachedTsd.getTypes()).hasSize(2);
+    assertThat(cache).containsOnlyKeys(circular1.toURL().toString(), circular2.toURL().toString());
+
+    TypeSystemDescription cachedCircular1Tsd = (TypeSystemDescription) cache
+            .get(circular1.toURL().toString());
+    TypeSystemDescription cachedCircular2Tsd = (TypeSystemDescription) cache
+            .get(circular2.toURL().toString());
+    assertThat(cachedCircular1Tsd.getTypes()).hasSize(2);
+    assertThat(cachedCircular2Tsd.getTypes()).hasSize(2);
+  }
+
+  @Test
+  public void thatLoopWithThreeNodesDoNotConfuseResourceManagerCache() throws Exception {
+    ResourceManager resMgr = newDefaultResourceManager();
+    File circular1 = getFile("TypeSystemDescriptionImplTest/Loop-with-3-nodes-1.xml");
+    File circular2 = getFile("TypeSystemDescriptionImplTest/Loop-with-3-nodes-2.xml");
+    File circular3 = getFile("TypeSystemDescriptionImplTest/Loop-with-3-nodes-3.xml");
+    TypeSystemDescription ts = xmlParser.parseTypeSystemDescription(new XMLInputSource(circular1));
+    ts.resolveImports(resMgr);
+    assertThat(ts.getTypes()).hasSize(3);
+
+    Map<String, XMLizable> cache = resMgr.getImportCache();
+    assertThat(cache).containsOnlyKeys(circular1.toURL().toString(), circular2.toURL().toString(),
+            circular3.toURL().toString());
+
+    TypeSystemDescription cachedCircular1Tsd = (TypeSystemDescription) cache
+            .get(circular1.toURL().toString());
+    TypeSystemDescription cachedCircular2Tsd = (TypeSystemDescription) cache
+            .get(circular2.toURL().toString());
+    TypeSystemDescription cachedCircular3Tsd = (TypeSystemDescription) cache
+            .get(circular3.toURL().toString());
+    assertThat(cachedCircular1Tsd.getTypes()).hasSize(3);
+    assertThat(cachedCircular2Tsd.getTypes()).hasSize(3);
+    assertThat(cachedCircular3Tsd.getTypes()).hasSize(3);
   }
 
   @Test
diff --git a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-1.xml
similarity index 96%
copy from uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
copy to uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-1.xml
index 8be9616..41c376f 100644
--- a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
+++ b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-1.xml
@@ -27,7 +27,7 @@
 <version>0.1</version>
 <vendor>The Apache Software Foundation</vendor>
 <imports>
-<import location="Circular2.xml"/>
+<import location="Loop-with-2-nodes-2.xml"/>
 </imports>
 <types>
 <typeDescription>
diff --git a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular2.xml b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-2.xml
similarity index 96%
rename from uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular2.xml
rename to uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-2.xml
index a633781..bb806e8 100644
--- a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular2.xml
+++ b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-2-nodes-2.xml
@@ -26,7 +26,7 @@
 <version>0.1</version>
 <vendor>The Apache Software Foundation</vendor>
 <imports>
-<import location="Circular1.xml"/>
+<import location="Loop-with-2-nodes-1.xml"/>
 </imports>
 <types>
 <typeDescription>
diff --git a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-1.xml
similarity index 95%
copy from uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
copy to uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-1.xml
index 8be9616..77903b4 100644
--- a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
+++ b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-1.xml
@@ -22,12 +22,12 @@
    -->
 
 
-<name>Circular1</name>
+<name>Circular3-1</name>
 <description>For testing circular imports.</description>
 <version>0.1</version>
 <vendor>The Apache Software Foundation</vendor>
 <imports>
-<import location="Circular2.xml"/>
+<import location="Loop-with-3-nodes-2.xml"/>
 </imports>
 <types>
 <typeDescription>
diff --git a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-2.xml
similarity index 95%
copy from uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
copy to uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-2.xml
index 8be9616..451947a 100644
--- a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
+++ b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-2.xml
@@ -22,12 +22,12 @@
    -->
 
 
-<name>Circular1</name>
+<name>Circular3-2</name>
 <description>For testing circular imports.</description>
 <version>0.1</version>
 <vendor>The Apache Software Foundation</vendor>
 <imports>
-<import location="Circular2.xml"/>
+<import location="Loop-with-3-nodes-3.xml"/>
 </imports>
 <types>
 <typeDescription>
diff --git a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-3.xml
similarity index 95%
rename from uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
rename to uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-3.xml
index 8be9616..f9e29bc 100644
--- a/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Circular1.xml
+++ b/uimaj-core/src/test/resources/TypeSystemDescriptionImplTest/Loop-with-3-nodes-3.xml
@@ -22,12 +22,12 @@
    -->
 
 
-<name>Circular1</name>
+<name>Circular3-3</name>
 <description>For testing circular imports.</description>
 <version>0.1</version>
 <vendor>The Apache Software Foundation</vendor>
 <imports>
-<import location="Circular2.xml"/>
+<import location="Loop-with-3-nodes-1.xml"/>
 </imports>
 <types>
 <typeDescription>