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

rec pushed a commit to branch feature/372-Allow-adding-URLs-to-the-datapath
in repository https://gitbox.apache.org/repos/asf/uima-uimaj.git

commit 3907318d7920c7b9189f12d243f6b6329478caea
Author: Richard Eckart de Castilho <[email protected]>
AuthorDate: Wed Aug 14 18:33:03 2024 +0200

    #372 - Allow adding URLs to the datapath
    
    - Added methods to set the datapath using URLs
    - Added support for URLs to the setDataPathElements(String...) method
    - Deprecated methods that return the datapath in a way that is not URLs
    - Added more unit tests
    - Cleaning up code a bit
---
 .../apache/uima/internal/util/UIMAClassLoader.java |   4 +-
 .../apache/uima/resource/RelativePathResolver.java |  71 +++++--
 .../org/apache/uima/resource/ResourceManager.java  |  95 ++++++----
 .../impl/ConfigurableDataResource_impl.java        |  22 +--
 .../uima/resource/impl/DataResource_impl.java      |  31 +--
 .../resource/impl/RelativePathResolver_impl.java   | 211 +++++++++++++++------
 .../uima/resource/impl/ResourceManager_impl.java   | 162 ++++------------
 .../metadata/ResourceManagerConfiguration.java     |   5 +-
 .../org/apache/uima/util/impl/Settings_impl.java   |   2 +-
 .../impl/RelativePathResolver_implTest.java        | 188 +++++++++++++-----
 10 files changed, 487 insertions(+), 304 deletions(-)

diff --git 
a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java 
b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
index 026351463..81b71cf59 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/internal/util/UIMAClassLoader.java
@@ -90,11 +90,11 @@ public class UIMAClassLoader extends URLClassLoader {
   /**
    * Transforms the string classpath to a URL array based classpath.
    * 
-   * The classpath string must be separated with the filesystem path separator.
+   * The classpath string must be separated with the file system path 
separator.
    * 
    * @param classpath
    *          a classpath string
-   * @return URL[] array of wellformed URL's
+   * @return URL[] array of well-formed URL's
    * @throws MalformedURLException
    *           if a malformed URL has occurred in the classpath string.
    */
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/RelativePathResolver.java 
b/uimaj-core/src/main/java/org/apache/uima/resource/RelativePathResolver.java
index b6c925dac..fde854326 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/RelativePathResolver.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/RelativePathResolver.java
@@ -26,8 +26,6 @@ import java.util.List;
 
 /**
  * Used by the resource manager to resolve relative URLs to absolute URLs.
- * 
- * 
  */
 public interface RelativePathResolver {
 
@@ -37,21 +35,39 @@ public interface RelativePathResolver {
    * Gets the data path used to resolve relative paths. More than one 
directory may be specified by
    * separating them with the System <code>path.separator</code> character (; 
on windows, : on
    * UNIX). Elements of this path may be absolute or relative file paths.
+   * <p>
+   * <b>Note:</b> This method will only return file paths. If any non-file 
URLs have been added to
+   * the data path e.g via {@link #setDataPathElements(URL...)}, these will 
not be included. Use
+   * {@link #getDataPathUrls()} to get a full list.
    * 
    * @return the data path
    * @deprecated Use {@link #getDataPathElements} instead.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   String getDataPath();
 
   /**
    * Gets the data path used to resolve relative paths. Elements of this path 
may be absolute or
    * relative file paths.
+   * <p>
+   * <b>Note:</b> This method will only return file paths. If any non-file 
URLs have been added to
+   * the data path e.g via {@link #setDataPathElements(URL...)}, these will 
not be included. Use
+   * {@link #getDataPathUrls()} to get a full list.
    * 
    * @return the data path
+   * @deprecated Use {@link #getDataPathUrls()} instead.
    */
+  @Deprecated(since = "3.6.0")
   List<String> getDataPathElements();
 
+  /**
+   * Gets the data path used to resolve relative paths. Elements of this path 
may be absolute or
+   * relative URLs.
+   * 
+   * @return the data path
+   */
+  List<URL> getDataPathUrls();
+
   /**
    * Sets the data path used to resolve relative paths. More than one 
directory may be specified by
    * separating them with the System <code>path.separator</code> character (; 
on windows, : on
@@ -64,12 +80,12 @@ public interface RelativePathResolver {
    *           if a file path could not be converted to a URL
    * @deprecated Use {@link #setDataPathElements} instead.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   void setDataPath(String aPath) throws MalformedURLException;
 
   /**
-   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be
-   * absolute or relative file paths.
+   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be URLs
+   * or absolute or relative file paths.
    * 
    * @param aElements
    *          the data path elements
@@ -92,19 +108,48 @@ public interface RelativePathResolver {
   void setDataPathElements(File... aElements) throws MalformedURLException;
 
   /**
-   * Resolves a relative URL to an absolute URL. This will attempt to resolve 
the URL relative to
-   * each element of the data path, sequentially starting with the first 
element. If this results in
-   * an absolute URL at which a file actually exists, that absolute URL is 
returned. If no file
-   * could be found, <code>null</code> is returned.
+   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be
+   * absolute or relative URLs.
+   * 
+   * @param aURLs
+   *          the data path aURLs
+   */
+  void setDataPathElements(URL... aURLs);
+
+  /**
+   * Resolves an URLrelative to each element of the data path, sequentially 
starting with the first
+   * element. If this results in an absolute URL at which a file actually 
exists, that absolute URL
+   * is returned. If no file could be found, <code>null</code> is returned.
+   * 
+   * @param aUrl
+   *          the URL to be resolved (if an absolute URL is specified, it will 
be returned
+   *          unmodified if a file actually exists at the URL; otherwise 
<code>null</code> will be
+   *          returned).
+   * 
+   * @return the absolute URL at which the file exists, <code>null</code> it 
none could be found.
+   * @deprecated Use {@link #resolveRelativePath(String)} instead.
+   */
+  @Deprecated
+  URL resolveRelativePath(URL aUrl);
+
+  /**
+   * Resolves a path relative to each element of the data path, sequentially 
starting with the first
+   * element. If this results in an absolute URL at which a file actually 
exists, that absolute URL
+   * is returned. If no file could be found, <code>null</code> is returned.
+   * <p>
+   * <b>Note:</b> For backwards compatibility, it is still allowed to specify 
relative URLs here
+   * such as {@code file:foo/foo.txt} and this will effectively work as if 
{@code foo/foo.txt} had
+   * been specified. The protocol is simply discarded. Future versions will no 
longer accept a
+   * protocol in relative paths.
    * 
-   * @param aRelativeUrl
-   *          the relative URL to be resolved (if an absolute URL is 
specified, it will be returned
+   * @param aPathOrUrl
+   *          the path/URL to be resolved (if an absolute URL is specified, it 
will be returned
    *          unmodified if a file actually exists at the URL; otherwise 
<code>null</code> will be
    *          returned).
    * 
    * @return the absolute URL at which the file exists, <code>null</code> it 
none could be found.
    */
-  URL resolveRelativePath(URL aRelativeUrl);
+  URL resolveRelativePath(String aPathOrUrl);
 
   /**
    * Sets the ClassLoader that should be used to resolve the resources.
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/ResourceManager.java 
b/uimaj-core/src/main/java/org/apache/uima/resource/ResourceManager.java
index e59ed195f..950d03e9b 100644
--- a/uimaj-core/src/main/java/org/apache/uima/resource/ResourceManager.java
+++ b/uimaj-core/src/main/java/org/apache/uima/resource/ResourceManager.java
@@ -33,8 +33,6 @@ import org.apache.uima.util.XMLizable;
 /**
  * A <code>ResourceManager</code> holds a collection of {@link 
org.apache.uima.resource.Resource}
  * objects, each registered under a specified key.
- * 
- * 
  */
 public interface ResourceManager {
 
@@ -42,21 +40,38 @@ public interface ResourceManager {
    * Gets the data path used to resolve relative paths. More than one 
directory may be specified by
    * separating them with the System <code>path.separator</code> character (; 
on windows, : on
    * UNIX).
+   * <p>
+   * <b>Note:</b> This method will only return file paths. If any non-file 
URLs have been added to
+   * the data path e.g via {@link #setDataPathUrls(URL...)}, these will not be 
included. Use
+   * {@link #getDataPathUrls()} to get a full list.
    * 
    * @return the data path
    * 
    * @deprecated Use {@link #getDataPathElements()} instead.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   String getDataPath();
 
   /**
    * Gets the data path elements used to resolve relative paths.
-   * 
+   * <p>
+   * <b>Note:</b> This method will only return file paths. If any non-file 
URLs have been added to
+   * the data path e.g via {@link #setDataPathUrls(URL...)}, these will not be 
included. Use
+   * {@link #getDataPathUrls()} to get a full list.
+   *
    * @return the data path elements
+   * @deprecated Use {@link #getDataPathUrls()}
    */
+  @Deprecated(since = "3.6.0")
   List<String> getDataPathElements();
 
+  /**
+   * Gets the data path URLs used to resolve relative paths.
+   * 
+   * @return the data path elements
+   */
+  List<URL> getDataPathUrls();
+
   /**
    * Sets the data path used to resolve relative paths. More than one 
directory may be specified by
    * separating them with the System <code>path.separator</code> character (; 
on windows, : on
@@ -69,18 +84,18 @@ public interface ResourceManager {
    *           if an element of the path is neither a valid URL or a valid 
file path
    * @deprecated Use {@link #setDataPathElements} instead.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   void setDataPath(String aPath) throws MalformedURLException;
 
   /**
-   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be
-   * absolute or relative file paths.
+   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be URLs
+   * or absolute or relative file paths.
    * 
    * @param aElements
-   *          the data path
+   *          the data path elements
    * 
    * @throws MalformedURLException
-   *           if an element of the path is neither a valid URL or a valid 
file path
+   *           if a file path could not be converted to a URL
    */
   void setDataPathElements(String... aElements) throws MalformedURLException;
 
@@ -96,6 +111,15 @@ public interface ResourceManager {
    */
   void setDataPathElements(File... aElements) throws MalformedURLException;
 
+  /**
+   * Sets the data path elements used to resolve relative paths. Elements of 
this path may be
+   * absolute or relative URLs.
+   * 
+   * @param aUrls
+   *          the data path aURLs
+   */
+  void setDataPathUrls(URL... aUrls);
+
   /**
    * Attempts to resolve a relative path to an absolute path using the same 
mechanism that the
    * ResourceManager uses to find resources -- a lookup in the datapath 
followed by a lookup in the
@@ -130,23 +154,23 @@ public interface ResourceManager {
    */
   Object getResource(String aName) throws ResourceAccessException;
 
-//@formatter:off
   /**
    * Returns one of two kinds of objects (or null):
-   *   - an instance of the implementation object for a resource, that has 
-   *     been loaded with a DataResource resource produced by the resource 
given the aParms
-   *     
-   *   - (if there is no implementation defined for this resource) 
-   *     returns an instance of the DataResource, itself, produced by the 
resource given the aParms
-   *    
-   *   An example of a parameterized Resource is a
-   *     dictionary whose data depend on a specified language identifier.
-   *   
-   *   If the implementation object class exists, but no instance has been 
-   *   created (yet) for the particular data resource corresponding to the 
parameters,
-   *   then this method will create and register a new instance and call its
-   *   load() api using the data resource corresponding to the parameters, and
-   *   return that.
+   * <ul>
+   * <li>an instance of the implementation object for a resource, that has 
been loaded with a
+   * DataResource resource produced by the resource given the aParms
+   * 
+   * <li>(if there is no implementation defined for this resource) returns an 
instance of the
+   * DataResource, itself, produced by the resource given the aParms
+   * </ul>
+   * <p>
+   * An example of a parameterized Resource is a dictionary whose data depend 
on a specified
+   * language identifier.
+   * <p>
+   * If the implementation object class exists, but no instance has been 
created (yet) for the
+   * particular data resource corresponding to the parameters, then this 
method will create and
+   * register a new instance and call its load() api using the data resource 
corresponding to the
+   * parameters, and return that.
    * 
    * @param aName
    *          the name of the parameterized resource to retrieve
@@ -171,7 +195,6 @@ public interface ResourceManager {
    *           if there is a resource registered under <code>aName</code> but 
it could not be
    *           instantiated for the specified parameters.
    */
-//@formatter:on
   Object getResource(String aName, String[] aParams) throws 
ResourceAccessException;
 
   /**
@@ -308,20 +331,20 @@ public interface ResourceManager {
           String aQualifiedContextName, Map<String, Object> aAdditionalParams)
           throws ResourceInitializationException;
 
-//@formatter:off
   /**
    * Resolves a component's external resource dependencies (bindings) using 
this resource manager.
-   * 
+   * <p>
    * The default implementation has special defaulting logic:
+   * <p>
+   * If a binding specifies a non-existing resource, an attempt is made to 
interpret the key as a
+   * file name, looked up using the current context for relative path 
resolution.
+   * <ul>
+   * <li>If successfully found, a FileResourceSpecifier is created using the 
file and used as the
+   * implementing class.
    * 
-   *   If a binding specifies a non-existing resource, 
-   *   an attempt is made to interpret the key as a file name, looked up 
-   *   using the current context for relative path resolution.  
-   *     - If successfully found, a FileResourceSpecifier is created using the 
file
-   *       and used as the implementing class. 
-   * 
-   * If no resource can be found at all, then unless the dependency is marked 
"optional", an
+   * <li>If no resource can be found at all, then unless the dependency is 
marked "optional", an
    * ResourceInitializationException is thrown.
+   * </ul>
    * 
    * Multi-threading: may be called on multiple threads, repeatedly for the 
same set of resources.
    * Implementations should recognize this and skip repeated resolutions.
@@ -335,7 +358,6 @@ public interface ResourceManager {
    * @throws ResourceInitializationException
    *           if a required dependency is not satisfied
    */
-//@formatter:on
   void resolveAndValidateResourceDependencies(ExternalResourceDependency[] 
aDependencies,
           String aQualifiedContextName) throws ResourceInitializationException;
 
@@ -423,7 +445,7 @@ public interface ResourceManager {
    * @return A map from absolute URL to the XMLizable object that was parsed 
from that URL
    * @deprecated Intended just for internal use.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   Map<String, XMLizable> getImportCache();
 
   /**
@@ -465,7 +487,6 @@ public interface ResourceManager {
   void destroy();
 
   /**
-   * 
    * @return a List of External Shared Resource instances instantiated by this 
Resource Manager. For
    *         parameterized resources, those which have been asked for (having 
unique parameter sets)
    *         are included.
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurableDataResource_impl.java
 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurableDataResource_impl.java
index c88e88e1a..15c3d7432 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurableDataResource_impl.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/ConfigurableDataResource_impl.java
@@ -94,13 +94,8 @@ public class ConfigurableDataResource_impl extends 
Resource_ImplBase implements
       relPathResolver = new RelativePathResolver_impl();
     }
 
-    // Get the file URL, resolving relative path as necessary
-    try {
-      mFileUrl = relPathResolver.resolveRelativePath(new URL(mUri.toString()));
-    } catch (IOException e) {
-      // this is OK. The URI may not be a valid URL (e.g. it may use a 
non-standard protocol).
-      // in this case getUrl returns null but getUri can still be used to 
access the URI
-    }
+    mFileUrl = relPathResolver.resolveRelativePath(mUri.toString());
+
     return true;
   }
 
@@ -153,18 +148,22 @@ public class ConfigurableDataResource_impl extends 
Resource_ImplBase implements
   @Override
   public boolean equals(Object obj) {
     // obj must be a DataResource_impl
-    if (!(obj instanceof ConfigurableDataResource_impl))
+    if (!(obj instanceof ConfigurableDataResource_impl)) {
       return false;
+    }
 
     // URIs must be the same
     URI uri = ((ConfigurableDataResource_impl) obj).getUri();
-    if (uri == null || !uri.equals(getUri()))
+    if (uri == null || !uri.equals(getUri())) {
       return false;
+    }
 
     // Local Cache Files must be the same
     File localCache = ((ConfigurableDataResource_impl) obj).getLocalCache();
-    if ((localCache == null && getLocalCache() != null) || (localCache != null 
&& !localCache.equals(getLocalCache())))
+    if ((localCache == null && getLocalCache() != null)
+            || (localCache != null && !localCache.equals(getLocalCache()))) {
       return false;
+    }
 
     return true;
   }
@@ -176,8 +175,9 @@ public class ConfigurableDataResource_impl extends 
Resource_ImplBase implements
   public int hashCode() {
     // add hash codes of member variables
     int hashCode = 0;
-    if (mUri != null)
+    if (mUri != null) {
       hashCode += mUri.hashCode();
+    }
 
     return hashCode;
   }
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/DataResource_impl.java 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/DataResource_impl.java
index b2ada38da..608f7661a 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/DataResource_impl.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/DataResource_impl.java
@@ -70,8 +70,9 @@ public class DataResource_impl extends Resource_ImplBase 
implements DataResource
   public boolean initialize(ResourceSpecifier aSpecifier, Map<String, Object> 
aAdditionalParams)
           throws ResourceInitializationException {
     // aSpecifier must be a FileResourceSpecifier
-    if (!(aSpecifier instanceof FileResourceSpecifier))
+    if (!(aSpecifier instanceof FileResourceSpecifier)) {
       return false;
+    }
 
     // If we get here, aSpecifier is supported by this implementation.
     FileResourceSpecifier spec = (FileResourceSpecifier) aSpecifier;
@@ -85,22 +86,22 @@ public class DataResource_impl extends Resource_ImplBase 
implements DataResource
       // Get the file URL from the specifier. If the user has passed a file 
path
       // (e.g. c:\Program Files\...) instead of a URL, be lenient and convert 
it to
       // a URL
-      URL relativeUrl;
+      String relativeUrl;
       try {
-        relativeUrl = new URL(spec.getFileUrl());
+        relativeUrl = new URL(spec.getFileUrl()).toString();
       } catch (MalformedURLException e) {
         // try to treat the URL as a file name.
-        File file = new File(spec.getFileUrl());
+        var file = new File(spec.getFileUrl());
         if (file.isAbsolute()) {
           // for absolute paths, use File.toURL(), which handles
           // windows absolute paths correctly
-          relativeUrl = file.toURL();
+          relativeUrl = file.toURL().toString();
         } else {
           // for relative paths, we can' use File.toURL() because it always
           // produces an absolute URL. Instead we do the following, which
           // won't work for windows absolute paths (but that's OK, since we
           // know we're working with a relative path)
-          relativeUrl = new URL("file", "", spec.getFileUrl());
+          relativeUrl = spec.getFileUrl();
         }
       }
 
@@ -176,18 +177,22 @@ public class DataResource_impl extends Resource_ImplBase 
implements DataResource
   @Override
   public boolean equals(Object obj) {
     // obj must be a DataResource_impl
-    if (!(obj instanceof DataResource_impl))
+    if (!(obj instanceof DataResource_impl)) {
       return false;
+    }
 
     // URLs must be the same (but don't use URL.equals(), which does DNS 
resolution!)
     URL url = ((DataResource_impl) obj).getUrl();
-    if (url == null || !url.toString().equals(getUrl().toString()))
+    if (url == null || !url.toString().equals(getUrl().toString())) {
       return false;
+    }
 
     // Local Cache Files must be the same
     File localCache = ((DataResource_impl) obj).getLocalCache();
-    if ((localCache == null && getLocalCache() != null) || (localCache != null 
&& !localCache.equals(getLocalCache())))
+    if ((localCache == null && getLocalCache() != null)
+            || (localCache != null && !localCache.equals(getLocalCache()))) {
       return false;
+    }
 
     return true;
   }
@@ -199,11 +204,13 @@ public class DataResource_impl extends Resource_ImplBase 
implements DataResource
   public int hashCode() {
     // add hash codes of member variables
     int hashCode = 0;
-    if (mFileUrl != null)
+    if (mFileUrl != null) {
       hashCode += mFileUrl.toString().hashCode(); // don't use URL.hashCode(), 
which does DNS
-                                                  // resolution
-    if (mLocalCache != null)
+    }
+    // resolution
+    if (mLocalCache != null) {
       hashCode += mLocalCache.hashCode();
+    }
 
     return hashCode;
   }
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/RelativePathResolver_impl.java
 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/RelativePathResolver_impl.java
index 098a9d4b4..0127e832d 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/RelativePathResolver_impl.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/RelativePathResolver_impl.java
@@ -19,23 +19,24 @@
 
 package org.apache.uima.resource.impl;
 
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
 import static java.util.Collections.emptyList;
 import static java.util.Collections.unmodifiableList;
 import static java.util.stream.Collectors.joining;
-import static java.util.stream.Collectors.toList;
 
 import java.io.File;
 import java.io.IOException;
-import java.io.InputStream;
+import java.lang.invoke.MethodHandles;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.net.URLDecoder;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 import java.util.StringTokenizer;
 
 import org.apache.uima.resource.RelativePathResolver;
-import org.apache.uima.util.impl.Constants;
 
 /**
  * Reference implementation of {@link RelativePathResolver}.
@@ -43,18 +44,17 @@ import org.apache.uima.util.impl.Constants;
 public class RelativePathResolver_impl implements RelativePathResolver {
 
   /** Data path as a string. */
+  @Deprecated(since = "3.6.0")
   private List<String> mDataPath;
 
   /** Array of base URLs parsed from the data path. */
-  private URL[] mBaseUrls;
+  private List<URL> mBaseUrls;
 
   /** ClassLoader to fall back on if resource not in data path. */
   private ClassLoader mClassLoader;
 
   public RelativePathResolver_impl() {
-    this(null);
-    mClassLoader = getClass().getClassLoader(); // default value, maybe 
overridden by
-                                                // setPathResolverClassLoader
+    this(MethodHandles.lookup().lookupClass().getClassLoader());
   }
 
   public RelativePathResolver_impl(ClassLoader aClassLoader) {
@@ -84,95 +84,199 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
       setDataPath(dataPath);
     } catch (MalformedURLException e) {
       // initialize to empty path
-      mDataPath = emptyList();
-      mBaseUrls = Constants.EMPTY_URL_ARRAY;
+      mDataPath = null;
+      mBaseUrls = null;
     }
+
     mClassLoader = aClassLoader;
   }
 
   @Override
   @Deprecated
   public String getDataPath() {
-    String pathSepChar = System.getProperty("path.separator");
+    if (mDataPath == null) {
+      return "";
+    }
+
+    var pathSepChar = System.getProperty("path.separator");
     return mDataPath.stream().collect(joining(pathSepChar));
   }
 
+  @Deprecated
   @Override
   public List<String> getDataPathElements() {
-    return mDataPath;
+    if (mDataPath == null) {
+      return emptyList();
+    }
+
+    return unmodifiableList(mDataPath);
   }
 
   @Override
-  public void setDataPathElements(File... aPaths) throws MalformedURLException 
{
-    if (aPaths == null) {
-      mDataPath = emptyList();
-      mBaseUrls = Constants.EMPTY_URL_ARRAY;
+  public List<URL> getDataPathUrls() {
+    if (mBaseUrls == null) {
+      return emptyList();
+    }
+
+    return unmodifiableList(mBaseUrls);
+  }
+
+  @SuppressWarnings("deprecation")
+  @Override
+  public void setDataPathElements(File... aElements) throws 
MalformedURLException {
+    if (aElements == null) {
+      mDataPath = null;
+      mBaseUrls = null;
       return;
     }
 
-    mDataPath = unmodifiableList(Arrays.stream(aPaths) //
-            .map(File::getPath) //
-            .map(s -> s.replace(File.separator, "/")) //
-            .collect(toList()));
-    mBaseUrls = new URL[aPaths.length];
-    for (int i = 0; i < aPaths.length; i++) {
+    var baseUrls = new ArrayList<URL>(aElements.length);
+    for (var path : aElements) {
       // Note, this URL can contain space characters if there were spaces in 
the
       // datapath. This may not be ideal but we're keeping that behavior for
       // backwards compatibility. Some components relied on this (e.g. by 
calling
       // URL.getFile() and expecting it to be a valid file name).
-      mBaseUrls[i] = aPaths[i].toURL();
+      baseUrls.add(path.toURL());
     }
+
+    mDataPath = stream(aElements) //
+            .map(File::getPath) //
+            .map(s -> s.replace(File.separator, "/")) //
+            .toList();
+    mBaseUrls = baseUrls;
   }
 
+  @SuppressWarnings("deprecation")
   @Override
-  public void setDataPathElements(String... aPaths) throws 
MalformedURLException {
-    if (aPaths == null) {
+  public void setDataPathElements(String... aElements) throws 
MalformedURLException {
+    if (aElements == null) {
       mDataPath = null;
       mBaseUrls = null;
       return;
     }
 
-    mDataPath = unmodifiableList(Arrays.stream(aPaths).collect(toList()));
-    mBaseUrls = new URL[aPaths.length];
-    for (int i = 0; i < aPaths.length; i++) {
-      // Note, this URL can contain space characters if there were spaces in 
the
-      // datapath. This may not be ideal but we're keeping that behavior for
-      // backwards compatibility. Some components relied on this (e.g. by 
calling
-      // URL.getFile() and expecting it to be a valid file name).
-      mBaseUrls[i] = new File(aPaths[i]).toURL();
+    var dataPath = new ArrayList<String>(aElements.length);
+
+    var baseUrls = new ArrayList<URL>(aElements.length);
+    for (var element : aElements) {
+      try {
+        var url = new URL(element);
+        baseUrls.add(url);
+        var protocol = url.getProtocol();
+        if ((protocol != null) && protocol.equalsIgnoreCase("file")) {
+          dataPath.add(new File(URLDecoder.decode(url.getPath(), 
UTF_8)).getPath());
+        }
+      } catch (MalformedURLException e) {
+        // Note, this URL can contain space characters if there were spaces in 
the
+        // datapath. This may not be ideal but we're keeping that behavior for
+        // backwards compatibility. Some components relied on this (e.g. by 
calling
+        // URL.getFile() and expecting it to be a valid file name).
+        baseUrls.add(new File(element).toURL());
+        dataPath.add(element);
+      }
+    }
+
+    mDataPath = dataPath;
+    mBaseUrls = baseUrls;
+  }
+
+  @Override
+  public void setDataPathElements(URL... aElements) {
+    if (aElements == null) {
+      mDataPath = null;
+      mBaseUrls = null;
+      return;
+    }
+
+    var dataPath = new ArrayList<String>(aElements.length);
+    for (var url : aElements) {
+      var protocol = url.getProtocol();
+      if ((protocol == null) || !protocol.equalsIgnoreCase("file")) {
+        continue;
+      }
+      dataPath.add(new File(URLDecoder.decode(url.getPath(), 
UTF_8)).getPath());
     }
+
+    mDataPath = dataPath;
+    mBaseUrls = asList(aElements);
   }
 
   @Override
   @Deprecated
   public void setDataPath(String aPath) throws MalformedURLException {
-    List<URL> urls = new ArrayList<>();
-    List<String> paths = new ArrayList<>();
+    var urls = new ArrayList<URL>();
+    var paths = new ArrayList<String>();
 
     // tokenize based on path.separator system property
-    String pathSepChar = System.getProperty("path.separator");
-    StringTokenizer tokenizer = new StringTokenizer(aPath, pathSepChar);
+    var pathSepChar = System.getProperty("path.separator");
+    var tokenizer = new StringTokenizer(aPath, pathSepChar);
     while (tokenizer.hasMoreTokens()) {
-      String tok = tokenizer.nextToken();
+      var tok = tokenizer.nextToken();
       paths.add(tok);
-      URL url = new File(tok).toURL();
-      urls.add(url);
       // Note, this URL can contain space characters if there were spaces in 
the
       // datapath. This may not be ideal but we're keeping that behavior for
       // backwards compatibility. Some components relied on this (e.g. by 
calling
       // URL.getFile() and expecting it to be a valid file name).
+      urls.add(new File(tok).toURL());
+    }
+
+    mDataPath = paths;
+    mBaseUrls = urls;
+  }
+
+  @Override
+  public URL resolveRelativePath(String aPathOrUrl) {
+    // check if an URL was passed in - if so, we fall back to the old logic 
which is a bit odd
+    // because for relative URLs, it basically discards the protocol. This is 
behavior we may
+    // want to change on the next major release... e.g. to require that 
relative paths are
+    // always specified without a protocol.
+    try {
+      var url = new URL(aPathOrUrl);
+      return resolveRelativePath(url);
+    } catch (MalformedURLException e) {
+      // ignore and move on
+    }
+
+    // try each base URL
+    for (var baseUrl : mBaseUrls) {
+      try {
+        var absUrl = new URL(baseUrl, aPathOrUrl);
+        // if file exists here, return this URL
+        if (fileExistsAtUrl(absUrl)) {
+          return absUrl;
+        }
+      } catch (MalformedURLException e) {
+        // ignore and move on to next base URL
+      }
+    }
+
+    // fallback on classloader
+    URL absURL = null;
+    if (mClassLoader != null) {
+      absURL = mClassLoader.getResource(aPathOrUrl);
     }
-    mBaseUrls = urls.toArray(new URL[urls.size()]);
-    mDataPath = unmodifiableList(paths);
+
+    // fallback on TCCL
+    if (absURL == null) {
+      var tccl = Thread.currentThread().getContextClassLoader();
+      absURL = tccl.getResource(aPathOrUrl);
+    }
+
+    // if no ClassLoader specified (could be the bootstrap classloader), try 
the system classloader
+    if (absURL == null && mClassLoader == null) {
+      absURL = ClassLoader.getSystemClassLoader().getResource(aPathOrUrl);
+    }
+
+    return absURL;
   }
 
+  @Deprecated
   @Override
-  public URL resolveRelativePath(URL aRelativeUrl) {
+  public URL resolveRelativePath(URL aUrl) {
     // try each base URL
-    URL[] baseUrls = getBaseUrls();
-    for (int i = 0; i < baseUrls.length; i++) {
+    for (var baseUrl : mBaseUrls) {
       try {
-        URL absUrl = new URL(baseUrls[i], aRelativeUrl.toString());
+        var absUrl = new URL(baseUrl, aUrl.toString());
         // if file exists here, return this URL
         if (fileExistsAtUrl(absUrl)) {
           return absUrl;
@@ -183,12 +287,12 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
     }
 
     // check if an absolute URL was passed in
-    if (aRelativeUrl.getPath().startsWith("/") && 
fileExistsAtUrl(aRelativeUrl)) {
-      return aRelativeUrl;
+    if (aUrl.getPath().startsWith("/") && fileExistsAtUrl(aUrl)) {
+      return aUrl;
     }
 
     // fallback on classloader
-    String f = aRelativeUrl.getFile();
+    var f = aUrl.getFile();
     URL absURL = null;
     if (mClassLoader != null) {
       absURL = mClassLoader.getResource(f);
@@ -196,7 +300,7 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
 
     // fallback on TCCL
     if (absURL == null) {
-      ClassLoader tccl = Thread.currentThread().getContextClassLoader();
+      var tccl = Thread.currentThread().getContextClassLoader();
       absURL = tccl.getResource(f);
     }
 
@@ -213,7 +317,6 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
    */
   @Override
   public void setPathResolverClassLoader(ClassLoader aClassLoader) {
-    // set ClassLoader
     mClassLoader = aClassLoader;
   }
 
@@ -221,7 +324,7 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
    * Utility method that checks to see if a file exists at the specified URL.
    */
   protected boolean fileExistsAtUrl(URL aUrl) {
-    try (InputStream testStream = aUrl.openStream()) {
+    try (var testStream = aUrl.openStream()) {
       return true;
     } catch (IOException e) {
       return false;
@@ -230,8 +333,10 @@ public class RelativePathResolver_impl implements 
RelativePathResolver {
 
   /**
    * @return the base URLs that were parsed from the data path.
+   * @deprecated Use {@link #getDataPathUrls()} instead.
    */
+  @Deprecated
   protected URL[] getBaseUrls() {
-    return mBaseUrls;
+    return mBaseUrls.toArray(URL[]::new);
   }
 }
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
index 3a07103b7..0b7dca33d 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/impl/ResourceManager_impl.java
@@ -46,7 +46,6 @@ import org.apache.uima.resource.CasManager;
 import org.apache.uima.resource.DataResource;
 import org.apache.uima.resource.ExternalResourceDependency;
 import org.apache.uima.resource.ExternalResourceDescription;
-import org.apache.uima.resource.FileResourceSpecifier;
 import org.apache.uima.resource.ParameterizedDataResource;
 import org.apache.uima.resource.RelativePathResolver;
 import org.apache.uima.resource.Resource;
@@ -62,28 +61,25 @@ import org.apache.uima.util.XMLizable;
 
 /**
  * Reference implementation of {@link 
org.apache.uima.resource.ResourceManager}.
- * 
- * 
  */
 public class ResourceManager_impl implements ResourceManager {
   private static final AtomicInteger IMPORT_URL_CACHE_WARNING_THROTTLE = new 
AtomicInteger();
 
- // @formatter:off
   /**
    * Ties an External Resource instance to
-   *   - its description 
-   *     -- name
-   *     -- textual description
-   *     -- a ResourceSpecifier describing how to create it
-   *     -- (optional) the String name of the Java class that implements the 
resource)
-   *   - its defining UIMA Context
-   *   
-   *   These are used to validate multiple declarations, and to get
-   *   a resource to tie it to a binding
-   */
-  // @formatter:on
-  static protected class ResourceRegistration { // make protected
-                                                // 
https://issues.apache.org/jira/browse/UIMA-2102
+   * <ul>
+   * <li>its description
+   * <ul>
+   * <li>name
+   * <li>textual description
+   * <li>a ResourceSpecifier describing how to create it
+   * <li>(optional) the String name of the Java class that implements the 
resource)
+   * </ul>
+   * <li>its defining UIMA Context
+   * </ul>
+   * These are used to validate multiple declarations, and to get a resource 
to tie it to a binding
+   */
+  static protected class ResourceRegistration {
     /**
      * For ParameterizedDataResources or DataResources, is the implementation 
object, which is an
      * arbitrary Java class implementing SharedDataResource (which has the 
"load" method)
@@ -97,11 +93,11 @@ public class ResourceManager_impl implements 
ResourceManager {
 
     String definingContext;
 
-    public ResourceRegistration(Object resourceOrImplementation,
-            ExternalResourceDescription description, String definingContext) {
-      resource = resourceOrImplementation;
-      this.description = description;
-      this.definingContext = definingContext;
+    public ResourceRegistration(Object aResource, ExternalResourceDescription 
aDescription,
+            String aDefiningContext) {
+      resource = aResource;
+      description = aDescription;
+      definingContext = aDefiningContext;
     }
   }
 
@@ -279,8 +275,7 @@ public class ResourceManager_impl implements 
ResourceManager {
   public ResourceManager_impl copy() {
     ResourceManager_impl rm = new ResourceManager_impl(mResourceMap,
             mInternalResourceRegistrationMap, 
mParameterizedResourceImplClassMap,
-            mInternalParameterizedResourceImplClassMap,
-            mParameterizedResourceInstanceMap);
+            mInternalParameterizedResourceImplClassMap, 
mParameterizedResourceInstanceMap);
     // non-final fields init
     rm.uimaCL = uimaCL;
     rm.importCache = importCache;
@@ -307,12 +302,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /**
-   * 
-   * /**
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#setExtensionClassPath(java.lang.String,
 boolean)
-   */
   @Override
   public synchronized void setExtensionClassPath(String classpath, boolean 
resolveResource)
           throws MalformedURLException {
@@ -325,10 +314,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /**
-   * @see 
org.apache.uima.resource.ResourceManager#setExtensionClassPath(ClassLoader,java.lang.String,
-   *      boolean)
-   */
   @Override
   public synchronized void setExtensionClassPath(ClassLoader parent, String 
classpath,
           boolean resolveResource) throws MalformedURLException {
@@ -354,30 +339,29 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /**
-   * @see org.apache.uima.resource.ResourceManager#getExtensionClassLoader()
-   */
   @Override
   public ClassLoader getExtensionClassLoader() {
     return uimaCL;
   }
 
-  /**
-   * @see org.apache.uima.resource.ResourceManager#getDataPath()
-   */
+  @Deprecated(since = "3.3.0")
   @Override
   public String getDataPath() {
     return getRelativePathResolver().getDataPath();
   }
 
+  @Deprecated(since = "3.6.0")
   @Override
   public List<String> getDataPathElements() {
     return getRelativePathResolver().getDataPathElements();
   }
 
-  /**
-   * @see org.apache.uima.resource.ResourceManager#setDataPath(String)
-   */
+  @Override
+  public List<URL> getDataPathUrls() {
+    return getRelativePathResolver().getDataPathUrls();
+  }
+
+  @Deprecated(since = "3.6.0")
   @Override
   public void setDataPath(String aPath) throws MalformedURLException {
     getRelativePathResolver().setDataPath(aPath);
@@ -393,20 +377,14 @@ public class ResourceManager_impl implements 
ResourceManager {
     getRelativePathResolver().setDataPathElements(aElements);
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#resolveRelativePath(java.lang.String)
-   */
+  @Override
+  public void setDataPathUrls(URL... aUrls) {
+    getRelativePathResolver().setDataPathElements(aUrls);
+  }
+
   @Override
   public URL resolveRelativePath(String aRelativePath) throws 
MalformedURLException {
-    URL relativeUrl;
-    try {
-      relativeUrl = new URL(aRelativePath);
-    } catch (MalformedURLException e) {
-      relativeUrl = new URL("file", "", aRelativePath);
-    }
-    return getRelativePathResolver().resolveRelativePath(relativeUrl);
+    return getRelativePathResolver().resolveRelativePath(aRelativePath);
   }
 
   private void checkDestroyed() {
@@ -415,9 +393,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /**
-   * @see org.apache.uima.resource.ResourceManager#getResource(String)
-   */
   @Override
   public Object getResource(String aName) throws ResourceAccessException {
     checkDestroyed();
@@ -430,9 +405,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     return r;
   }
 
-  /**
-   * @see 
org.apache.uima.resource.ResourceManager#getResource(java.lang.String, 
java.lang.String[])
-   */
   @Override
   public Object getResource(String aName, String[] aParams) throws 
ResourceAccessException {
    // @formatter:off
@@ -503,9 +475,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /**
-   * @see 
org.apache.uima.resource.ResourceManager#getResourceClass(java.lang.String)
-   */
   @Override
   @SuppressWarnings("unchecked")
   public Class<?> getResourceClass(String aName) {
@@ -531,23 +500,12 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#getResourceAsStream(java.lang.String,
-   * java.lang.String[])
-   */
   @Override
   public InputStream getResourceAsStream(String aKey, String[] aParams)
           throws ResourceAccessException {
     return getResourceAsStreamCommon(getResource(aKey, aParams));
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#getResourceAsStream(java.lang.String)
-   */
   @Override
   public InputStream getResourceAsStream(String aKey) throws 
ResourceAccessException {
     return getResourceAsStreamCommon(getResource(aKey));
@@ -574,22 +532,11 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#getResourceURL(java.lang.String,
-   * java.lang.String[])
-   */
   @Override
   public URL getResourceURL(String aKey, String[] aParams) throws 
ResourceAccessException {
     return getResourceAsStreamCommonUrl(getResource(aKey, aParams));
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see 
org.apache.uima.resource.ResourceManager#getResourceURL(java.lang.String)
-   */
   @Override
   public URL getResourceURL(String aKey) throws ResourceAccessException {
     return getResourceAsStreamCommonUrl(getResource(aKey));
@@ -697,22 +644,16 @@ public class ResourceManager_impl implements 
ResourceManager {
     for (int i = 0; i < aDependencies.length; i++) {
       // get resource
       String qname = aQualifiedContextName + aDependencies[i].getKey();
-      Object resourceImpl = mResourceMap.get(qname); // may or may not 
implement Resource, may
-                                                     // implement 
SharedResourceObject
+      // may or may not implement Resource, may implement SharedResourceObject
+      Object resourceImpl = mResourceMap.get(qname);
 
       if (resourceImpl == null) {
-        // no resource found
-        // try to look up in classpath/datapath
-        URL relativeUrl;
-        try {
-          relativeUrl = new URL("file", "", aDependencies[i].getKey());
-        } catch (MalformedURLException e) {
-          throw new ResourceInitializationException(e);
-        }
-        URL absUrl = 
getRelativePathResolver().resolveRelativePath(relativeUrl);
+        // no resource found - try to look up in classpath/datapath
+        var relativeUrl = aDependencies[i].getKey();
+        var absUrl = 
getRelativePathResolver().resolveRelativePath(relativeUrl);
         if (absUrl != null) {
           // found - create a DataResource object and store it in the 
mResourceMap
-          FileResourceSpecifier spec = new FileResourceSpecifier_impl();
+          var spec = new FileResourceSpecifier_impl();
           spec.setFileUrl(absUrl.toString());
           // produces an instance of DataResourceImpl
           resourceImpl = UIMAFramework.produceResource(spec, null);
@@ -839,11 +780,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     mInternalResourceRegistrationMap.put(aName, registration);
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see org.apache.uima.resource.ResourceManager#getCasManager()
-   */
   @Override
   public CasManager getCasManager() {
     // Optimization for case where mCasManager already created
@@ -859,12 +795,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see
-   * 
org.apache.uima.resource.ResourceManager#setCasManager(org.apache.uima.resource.CasManager)
-   */
   @Override
   public void setCasManager(CasManager aCasManager) {
     synchronized (casManagerMonitor) {
@@ -882,6 +812,7 @@ public class ResourceManager_impl implements 
ResourceManager {
     return mRelativePathResolver;
   }
 
+  @Deprecated(since = "3.3.0")
   @Override
   public Map<String, XMLizable> getImportCache() {
     return importCache;
@@ -894,7 +825,7 @@ public class ResourceManager_impl implements 
ResourceManager {
    * 
    * @deprecated No longer used. Scheduled for removal in UIMA 4.0.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   public Map<String, Set<String>> getImportUrlsCache() {
     Misc.decreasingWithTrace(IMPORT_URL_CACHE_WARNING_THROTTLE,
             "ResourceManager_impl.getImportUrlsCache() should not be called. 
It is no longer "
@@ -924,11 +855,6 @@ public class ResourceManager_impl implements 
ResourceManager {
     }
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see org.apache.uima.resource.ResourceManager#destroy()
-   */
   @Override
   public void destroy() {
     boolean alreadyDestroyed = isDestroyed.getAndSet(true);
@@ -980,11 +906,6 @@ public class ResourceManager_impl implements 
ResourceManager {
 
   }
 
-  /*
-   * (non-Javadoc)
-   * 
-   * @see org.apache.uima.resource.ResourceManager#getExternalResources()
-   */
   @Override
   public List<Object> getExternalResources() {
 
@@ -1001,5 +922,4 @@ public class ResourceManager_impl implements 
ResourceManager {
 
     return rs;
   }
-
 }
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/resource/metadata/ResourceManagerConfiguration.java
 
b/uimaj-core/src/main/java/org/apache/uima/resource/metadata/ResourceManagerConfiguration.java
index 11b485aa8..664b5992d 100644
--- 
a/uimaj-core/src/main/java/org/apache/uima/resource/metadata/ResourceManagerConfiguration.java
+++ 
b/uimaj-core/src/main/java/org/apache/uima/resource/metadata/ResourceManagerConfiguration.java
@@ -143,7 +143,7 @@ public interface ResourceManagerConfiguration extends 
MetaDataObject {
    * @deprecated Use {@link #getImports()} instead. There may be many imports; 
this method only
    *             returns the first.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   Import getImport();
 
   /**
@@ -154,7 +154,7 @@ public interface ResourceManagerConfiguration extends 
MetaDataObject {
    *          manager configuration. Null indicates that there is no import.
    * @deprecated Use {@link #setImports(Import[])} instead.
    */
-  @Deprecated
+  @Deprecated(since = "3.3.0")
   void setImport(Import aImport);
 
   /**
@@ -292,5 +292,4 @@ public interface ResourceManagerConfiguration extends 
MetaDataObject {
    */
   void resolveImports(Collection<String> aAlreadyImportedURLs, ResourceManager 
aResourceManager)
           throws InvalidXMLException;
-
 }
diff --git 
a/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java 
b/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
index 7928f4487..79fc5451e 100644
--- a/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
+++ b/uimaj-core/src/main/java/org/apache/uima/util/impl/Settings_impl.java
@@ -168,7 +168,7 @@ public class Settings_impl implements Settings {
           if (fname.startsWith("path:")) { // Convert to a url and search the 
datapath & classpath
             URL relativeUrl = new URL("file", "",
                     fname.substring(5).replace('.', '/') + ".settings");
-            URL relPath = 
relativePathResolver.resolveRelativePath(relativeUrl);
+            URL relPath = 
relativePathResolver.resolveRelativePath(relativeUrl.toString());
             if (relPath != null) {
               is = relPath.openStream();
             } else {
diff --git 
a/uimaj-core/src/test/java/org/apache/uima/resource/impl/RelativePathResolver_implTest.java
 
b/uimaj-core/src/test/java/org/apache/uima/resource/impl/RelativePathResolver_implTest.java
index f94f33afe..162577d0e 100644
--- 
a/uimaj-core/src/test/java/org/apache/uima/resource/impl/RelativePathResolver_implTest.java
+++ 
b/uimaj-core/src/test/java/org/apache/uima/resource/impl/RelativePathResolver_implTest.java
@@ -19,78 +19,164 @@
 
 package org.apache.uima.resource.impl;
 
+import static java.lang.String.join;
+import static java.lang.System.getProperty;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.nio.file.Files.createDirectories;
+import static java.nio.file.Files.newOutputStream;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
 
 import java.io.File;
+import java.io.IOException;
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.nio.file.Path;
 import java.util.stream.Stream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
 
 import org.apache.uima.test.junit_extension.JUnitExtension;
 import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Nested;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
 
-public class RelativePathResolver_implTest {
+class RelativePathResolver_implTest {
 
-  private final static String PATH_SEP = System.getProperty("path.separator");
-  private RelativePathResolver_impl sut;
+  private final static String PATH_SEP = getProperty("path.separator");
 
-  private final static String element1 = "/this/is/a/test";
-  private final static String element2 = "/another/test";
-  private final static String[] expectedElements = { element1, element2 };
-  private final static String expectedPath = String.join(PATH_SEP, 
expectedElements);
+  private RelativePathResolver_impl sut;
 
   @BeforeEach
-  public void setup() {
+  void setup() {
     sut = new RelativePathResolver_impl();
   }
 
-  @Test
-  public void thatPathElementsAreNotModifiable() throws Exception {
-    sut.setDataPathElements(expectedPath);
+  @Nested
+  class UrlBasedTests {
 
-    assertThatExceptionOfType(UnsupportedOperationException.class)
-            .as("Path elements should not be modifiable")
-            .isThrownBy(() -> sut.getDataPathElements().add("blah"));
-  }
+    @Test
+    void thatPathUrlsAreNotModifiable() throws Exception {
+      sut.setDataPathElements(new URL("file:foo"), new URL("file:bar"));
 
-  @SuppressWarnings("deprecation")
-  @Test
-  public void testSetDataPath() throws Exception {
-    sut.setDataPath(expectedPath);
+      assertThatExceptionOfType(UnsupportedOperationException.class)
+              .as("Path elements should not be modifiable")
+              .isThrownBy(() -> sut.getDataPathUrls().add(new 
URL("file:blah")));
+    }
 
-    assertThatGettersReturnTheRightValues(sut);
-  }
+    @SuppressWarnings("deprecation")
+    @Test
+    void setSetDataPathUrls(@TempDir Path temp) throws Exception {
+      var zipFile = temp.resolve("test.zip");
+      createZipWithTextFiles(zipFile, "bar/bar.txt");
 
-  @Test
-  public void testSetDataPathElements() throws Exception {
-    sut.setDataPathElements(expectedElements);
+      var zipBaseUrl = new URL("jar:" + zipFile.toUri() + "!/");
+      var expected = new URL[] { new URL("file:foo"), zipBaseUrl };
+      sut.setDataPathElements(expected);
+
+      assertThat(sut.getDataPathElements()) //
+              .containsExactly("foo");
+
+      assertThat(sut.getDataPath()) //
+              .isEqualTo("foo");
+
+      assertThat(sut.getDataPathUrls()).containsExactly(expected);
+
+      assertThat(sut.resolveRelativePath("bar/bar.txt"))
+              .isEqualTo(new URL(zipBaseUrl, "bar/bar.txt"));
+      assertThat(sut.resolveRelativePath("foo/bar.txt")).isNull();
+    }
 
-    assertThatGettersReturnTheRightValues(sut);
+    public static void createZipWithTextFiles(Path aZipPath, String... 
aFileNames)
+            throws IOException {
+      createDirectories(aZipPath.getParent());
+
+      try (var zipOut = new ZipOutputStream(newOutputStream(aZipPath))) {
+        for (var fileName : aFileNames) {
+          zipOut.putNextEntry(new ZipEntry(fileName));
+          zipOut.write(("This is the content of " + fileName).getBytes(UTF_8));
+          zipOut.closeEntry();
+        }
+      }
+    }
   }
 
-  @Test
-  public void testSetDataPathElementsAsFiles() throws Exception {
-    File[] expectedElementFiles = 
Stream.of(expectedElements).map(File::new).toArray(File[]::new);
+  @Nested
+  class FileBasedTests {
+    final String element1 = "/this/is/a/test";
+    final String element2 = "/another/test";
+    final String[] expectedElements = { element1, element2 };
+    final String expectedPath = join(PATH_SEP, expectedElements);
+
+    @SuppressWarnings("deprecation")
+    @Test
+    void testSetDataPath() throws Exception {
+      sut.setDataPath(expectedPath);
+
+      assertThatGettersReturnTheRightValues(sut);
+    }
+
+    @Test
+    void testSetDataPathElements() throws Exception {
+      sut.setDataPathElements(expectedElements);
+
+      assertThatGettersReturnTheRightValues(sut);
+    }
+
+    @Test
+    void testSetDataPathElementsAsFiles() throws Exception {
+      var expectedElementFiles = 
Stream.of(expectedElements).map(File::new).toArray(File[]::new);
+
+      sut.setDataPathElements(expectedElementFiles);
+
+      assertThatGettersReturnTheRightValues(sut);
+    }
 
-    sut.setDataPathElements(expectedElementFiles);
+    @Deprecated(since = "3.6.0")
+    @Test
+    void thatPathElementsAreNotModifiable() throws Exception {
+      sut.setDataPathElements("foo", "bar");
 
-    assertThatGettersReturnTheRightValues(sut);
+      assertThatExceptionOfType(UnsupportedOperationException.class)
+              .as("Path elements should not be modifiable")
+              .isThrownBy(() -> sut.getDataPathElements().add("blah"));
+    }
+
+    @SuppressWarnings("deprecation")
+    void assertThatGettersReturnTheRightValues(RelativePathResolver_impl 
aResolver) {
+
+      assertThat(aResolver.getDataPathElements()) //
+              .containsExactly(expectedElements);
+
+      assertThat(aResolver.getDataPath()) //
+              .isEqualTo(expectedPath);
+
+      assertThat(aResolver.getDataPathUrls())
+              
.containsExactlyElementsOf(Stream.of(expectedElements).map(this::toUrl).toList());
+    }
+
+    @SuppressWarnings("deprecation")
+    URL toUrl(String path) {
+      try {
+        return new File(path).toURL();
+      } catch (MalformedURLException e) {
+        throw new IllegalArgumentException(e);
+      }
+    }
   }
 
   @Test
-  public void testResolveRelativePath() throws Exception {
-    // file should not be found
-    assertThat(sut.resolveRelativePath(new 
URL("file:test/relativePathTest.dat"))) //
+  void testResolveRelativePathUsingString() throws Exception {
+    assertThat(sut.resolveRelativePath("file:test/relativePathTest.dat")) //
             .as("File should not be found") //
             .isNull();
 
     // specify path
-    String path = 
JUnitExtension.getFile("ResourceTest/subdir").getAbsolutePath();
+    var path = JUnitExtension.getFile("ResourceTest/subdir").getAbsolutePath();
     sut.setDataPathElements(path);
 
-    URL absUrl = sut.resolveRelativePath(new 
URL("file:test/relativePathTest.dat"));
+    var absUrl = sut.resolveRelativePath("file:test/relativePathTest.dat");
     assertThat(absUrl) //
             .as("now file should be found") //
             .isNotNull();
@@ -98,28 +184,28 @@ public class RelativePathResolver_implTest {
     // try resolving an absolute path even with no data path
     sut.setDataPathElements("");
 
-    assertThat(sut.resolveRelativePath(absUrl)).isEqualTo(absUrl);
+    assertThat(sut.resolveRelativePath(absUrl.toString())).isEqualTo(absUrl);
   }
 
   @SuppressWarnings("deprecation")
-  private void assertThatGettersReturnTheRightValues(RelativePathResolver_impl 
aResolver) {
+  @Test
+  void testResolveRelativePathUsingUrl() throws Exception {
+    assertThat(sut.resolveRelativePath(new 
URL("file:test/relativePathTest.dat"))) //
+            .as("File should not be found") //
+            .isNull();
 
-    assertThat(aResolver.getDataPathElements()) //
-            .containsExactly(expectedElements);
+    // specify path
+    var path = JUnitExtension.getFile("ResourceTest/subdir").getAbsolutePath();
+    sut.setDataPathElements(path);
 
-    assertThat(aResolver.getDataPath()) //
-            .isEqualTo(expectedPath);
+    var absUrl = sut.resolveRelativePath(new 
URL("file:test/relativePathTest.dat"));
+    assertThat(absUrl) //
+            .as("now file should be found") //
+            .isNotNull();
 
-    assertThat(aResolver.getBaseUrls())
-            
.containsExactly(Stream.of(expectedElements).map(this::toUrl).toArray(URL[]::new));
-  }
+    // try resolving an absolute path even with no data path
+    sut.setDataPathElements("");
 
-  @SuppressWarnings("deprecation")
-  private URL toUrl(String path) {
-    try {
-      return new File(path).toURL();
-    } catch (MalformedURLException e) {
-      throw new IllegalArgumentException(e);
-    }
+    assertThat(sut.resolveRelativePath(absUrl)).isEqualTo(absUrl);
   }
 }

Reply via email to