Repository: quarkusio/quarkus
Issue: Spring PathMatchingResourcePatternResolver does not return the subdirectory of a URL resource.

**Describe the bug**
I use `org.springframework.core.io.support.PathMatchingResourcePatternResolver` to find resources packaged inside a jar which are in a subdirectory.

When I do this in a normal Java application the resource that is found correctly looks like this:

    URL [jar:file:/home/nbasjes/.m2/repository/org/acme/resourcelib/1.0-SNAPSHOT/resourcelib-1.0-SNAPSHOT.jar!/Content/world.txt]

yet when I run the exact same code in a Quarkus application it finds the file yet the returned resource does not have the subdirectory of the file.

    URL [jar:file:/home/nbasjes/.m2/repository/org/acme/resourcelib/1.0-SNAPSHOT/resourcelib-1.0-SNAPSHOT.jar!/world.txt]

So the difference is that in the Quarkus setup the resource is not reported in the actual subdirectory it is really stored in.
Thus: loading the resource will fail with a file not found.

**Expected behavior**
The located resources are found in the correct place.

**Actual behavior**
The subdirectory name is missing in the path of the found resources.

**To Reproduce**
https://github.com/nielsbasjes/BugReport-SpringQuarkus-ResourceLoading

**Environment (please complete the following information):**
 - Output of `uname -a` or `ver`:  `Linux lw7-06766 4.4.0-185-generic #215-Ubuntu SMP Mon Jun 8 21:53:19 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux`
 - Output of `java -version`: `openjdk version "1.8.0_252"
OpenJDK Runtime Environment (build 1.8.0_252-b09)
OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 25.252-b09-jvmci-20.1-b02, mixed mode)` 
 - GraalVM version (if different from Java): 
 - Quarkus version or git rev: `1.6.1.Final`
 - Build tool (ie. output of `mvnw --version` or `gradlew --version`): `Apache Maven 3.6.2`

**Additional context**
This was reported as a bug in the library I have written: https://github.com/nielsbasjes/yauaa/issues/216


Changed files:
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathResource.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java
independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/DirectoryClassPathElement.java

diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathResource.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathResource.java
index 4d2bd36f4e0..315cd0cd02e 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathResource.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/ClassPathResource.java
@@ -30,4 +30,10 @@ public interface ClassPathResource {
      */
     byte[] getData();
 
+    /**
+     *
+     * @return <code>true</code> if the entry is a directory
+     */
+    boolean isDirectory();
+
 }
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/DirectoryClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/DirectoryClassPathElement.java
index c5d8dfd697d..d1042eb3688 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/DirectoryClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/DirectoryClassPathElement.java
@@ -108,6 +108,11 @@ public byte[] getData() {
                         throw new RuntimeException("Unable to read " + file, e);
                     }
                 }
+
+                @Override
+                public boolean isDirectory() {
+                    return Files.isDirectory(file);
+                }
             };
         }
         return null;
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
index 06f7baa41ce..e3d0cb0779e 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/JarClassPathElement.java
@@ -105,6 +105,11 @@ public byte[] apply(JarFile jarFile) {
                                 }
                             });
                         }
+
+                        @Override
+                        public boolean isDirectory() {
+                            return res.getName().endsWith("/");
+                        }
                     };
                 }
                 return null;
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java
index 94c5baa2a79..05db1d17360 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/MemoryClassPathElement.java
@@ -79,6 +79,11 @@ public URL getUrl() {
             public byte[] getData() {
                 return res;
             }
+
+            @Override
+            public boolean isDirectory() {
+                return false;
+            }
         };
     }
 
diff --git a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
index 2c82d5497b1..00e9579101b 100644
--- a/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
+++ b/independent-projects/bootstrap/core/src/main/java/io/quarkus/bootstrap/classloading/QuarkusClassLoader.java
@@ -8,6 +8,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.reflect.InvocationTargetException;
+import java.net.MalformedURLException;
 import java.net.URL;
 import java.security.ProtectionDomain;
 import java.util.ArrayList;
@@ -152,6 +153,7 @@ public void reset(Map<String, byte[]> resources,
 
     @Override
     public Enumeration<URL> getResources(String unsanitisedName) throws IOException {
+        boolean endsWithTrailingSlash = unsanitisedName.endsWith("/");
         ClassLoaderState state = getState();
         String name = sanitizeName(unsanitisedName);
         //for resources banned means that we don't delegate to the parent, as there can be multiple resources
@@ -184,7 +186,21 @@ public Enumeration<URL> getResources(String unsanitisedName) throws IOException
         ClassPathElement[] providers = state.loadableResources.get(name);
         if (providers != null) {
             for (ClassPathElement element : providers) {
-                resources.add(element.getResource(name).getUrl());
+                ClassPathResource res = element.getResource(name);
+                //if the requested name ends with a trailing / we make sure
+                //that the resource is a directory, and return a URL that ends with a /
+                //this matches the behaviour of URLClassLoader
+                if (endsWithTrailingSlash) {
+                    if (res.isDirectory()) {
+                        try {
+                            resources.add(new URL(res.getUrl().toString() + "/"));
+                        } catch (MalformedURLException e) {
+                            throw new RuntimeException(e);
+                        }
+                    }
+                } else {
+                    resources.add(res.getUrl());
+                }
             }
         } else if (name.isEmpty()) {
             for (ClassPathElement i : elements) {
@@ -263,6 +279,7 @@ private ClassLoaderState getState() {
 
     @Override
     public URL getResource(String unsantisedName) {
+        boolean endsWithTrailingSlash = unsantisedName.endsWith("/");
         String name = sanitizeName(unsantisedName);
         ClassLoaderState state = getState();
         if (state.bannedResources.contains(name)) {
@@ -271,7 +288,7 @@ public URL getResource(String unsantisedName) {
         //TODO: because of dev mode we iterate, to see if any resources were added
         //not for .class files though, adding them causes a restart
         //this is very important for bytebuddy performance
-        if (name.endsWith(".class")) {
+        if (name.endsWith(".class") && !endsWithTrailingSlash) {
             ClassPathElement[] providers = state.loadableResources.get(name);
             if (providers != null) {
                 return providers[0].getResource(name).getUrl();
@@ -280,7 +297,20 @@ public URL getResource(String unsantisedName) {
             for (ClassPathElement i : elements) {
                 ClassPathResource res = i.getResource(name);
                 if (res != null) {
-                    return res.getUrl();
+                    //if the requested name ends with a trailing / we make sure
+                    //that the resource is a directory, and return a URL that ends with a /
+                    //this matches the behaviour of URLClassLoader
+                    if (endsWithTrailingSlash) {
+                        if (res.isDirectory()) {
+                            try {
+                                return new URL(res.getUrl().toString() + "/");
+                            } catch (MalformedURLException e) {
+                                throw new RuntimeException(e);
+                            }
+                        }
+                    } else {
+                        return res.getUrl();
+                    }
                 }
             }
         }