8161230: ClassLoader: add resource methods returning java.util.stream.Stream
authorpsandoz
Fri, 09 Sep 2016 14:54:41 -0700
changeset 40807 aa6a4a3f9874
parent 40806 46132d430504
child 40808 99330a831848
8161230: ClassLoader: add resource methods returning java.util.stream.Stream Reviewed-by: psandoz, alanb, mchung, tvaleev Contributed-by: Patrick Reinhart <patrick@reini.net>
jdk/src/java.base/share/classes/java/lang/ClassLoader.java
jdk/test/java/lang/ClassLoader/ResourcesStreamTest.java
--- a/jdk/src/java.base/share/classes/java/lang/ClassLoader.java	Fri Sep 09 14:54:29 2016 -0700
+++ b/jdk/src/java.base/share/classes/java/lang/ClassLoader.java	Fri Sep 09 14:54:41 2016 -0700
@@ -27,6 +27,7 @@
 
 import java.io.InputStream;
 import java.io.IOException;
+import java.io.UncheckedIOException;
 import java.io.File;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
@@ -46,12 +47,16 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.Spliterator;
+import java.util.Spliterators;
 import java.util.Stack;
 import java.util.NoSuchElementException;
 import java.util.Vector;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
 
 import jdk.internal.perf.PerfCounter;
 import jdk.internal.module.ServicesCatalog;
@@ -1344,6 +1349,57 @@
     }
 
     /**
+     * Returns a stream whose elements are the URLs of all the resources with
+     * the given name. A resource is some data (images, audio, text, etc) that
+     * can be accessed by class code in a way that is independent of the
+     * location of the code.
+     *
+     * Resources in a named module are private to that module. This method does
+     * not find resources in named modules.
+     *
+     * <p> The name of a resource is a {@code /}-separated path name that
+     * identifies the resource.
+     *
+     * <p> The search order is described in the documentation for {@link
+     * #getResource(String)}.
+     *
+     * <p> The resources will be located when the returned stream is evaluated.
+     * If the evaluation results in an {@code IOException} then the I/O
+     * exception is wrapped in an {@link UncheckedIOException} that is then
+     * thrown.
+     *
+     * @apiNote When overriding this method it is recommended that an
+     * implementation ensures that any delegation is consistent with the {@link
+     * #getResource(java.lang.String) getResource(String)} method. This should
+     * ensure that the first element returned by the stream is the same
+     * resource that the {@code getResource(String)} method would return.
+     *
+     * @param  name
+     *         The resource name
+     *
+     * @return  A stream of resource {@link java.net.URL URL} objects. If no
+     *          resources could  be found, the stream will be empty.  Resources
+     *          that the class loader doesn't have access to will not be in the
+     *          stream.
+     *
+     * @see  #findResources(String)
+     *
+     * @since  9
+     */
+    public Stream<URL> resources(String name) {
+        int characteristics = Spliterator.NONNULL | Spliterator.IMMUTABLE;
+        Supplier<Spliterator<URL>> si = () -> {
+            try {
+                return Spliterators.spliteratorUnknownSize(
+                    getResources(name).asIterator(), characteristics);
+            } catch (IOException e) {
+                throw new UncheckedIOException(e);
+            }
+        };
+        return StreamSupport.stream(si, characteristics, false);
+    }
+
+    /**
      * Finds the resource with the given name. Class loader implementations
      * should override this method to specify where to find resources.
      *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/lang/ClassLoader/ResourcesStreamTest.java	Fri Sep 09 14:54:41 2016 -0700
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URL;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/*
+ * @test
+ * @bug 8161230
+ * @summary Test java.lang.ClassLoader.resources() method
+ *
+ * @build ResourcesStreamTest
+ * @run main ResourcesStreamTest
+ */
+public class ResourcesStreamTest {
+
+    public static void main(String[] args) throws Exception {
+        testSuccess();
+        testFailure();
+    }
+
+    public static void testSuccess() throws Exception {
+        // failing part first
+        try {
+            ClassLoader cl = new FailingClassLoader();
+            // should create the stream pipe
+            Stream<URL> stream = cl.resources("the name");
+            // expect function to throw an exception when calling the method
+            stream.forEach(System.out::println);
+            throw new Exception("expected UncheckedIOException not thrown");
+        } catch (UncheckedIOException uio) {
+            String causeMessage = uio.getCause().getMessage();
+            if (!"the name".equals(causeMessage))
+                throw new Exception("unexpected cause message: " + causeMessage);
+        }
+    }
+
+    public static void testFailure() throws Exception {
+        ClassLoader cl = new SuccessClassLoader();
+        long count = cl.resources("the name").count();
+        if (count != 1)
+            throw new Exception("expected resource is null or empty");
+
+        cl.resources("the name")
+          .filter(url -> "file:/somefile".equals(url.toExternalForm()))
+          .findFirst()
+          .orElseThrow(() -> new Exception("correct URL not found"));
+    }
+
+    public static class SuccessClassLoader extends ClassLoader {
+        @Override
+        public Enumeration<URL> getResources(String name) throws IOException {
+            URL url = new URL("file:/somefile");
+            return Collections.enumeration(Collections.singleton(url));
+        }
+    }
+
+    public static class FailingClassLoader extends ClassLoader {
+        @Override
+        public Enumeration<URL> getResources(String name) throws IOException {
+            throw new IOException(name);
+        }
+    }
+}