8064924: Update java.net.URL to work with modules
authorchegar
Mon, 23 Feb 2015 15:48:20 +0000
changeset 29097 01a70c01f5a1
parent 29096 e91fff38083a
child 29098 fd65e2764b56
8064924: Update java.net.URL to work with modules Reviewed-by: alanb, plevart, psandoz
jdk/src/java.base/share/classes/java/net/URL.java
jdk/src/java.base/share/classes/java/net/URLStreamHandlerFactory.java
jdk/src/java.base/share/classes/java/net/package-info.java
jdk/src/java.base/share/classes/java/net/spi/URLStreamHandlerProvider.java
jdk/src/java.base/share/classes/java/net/spi/package-info.java
jdk/test/java/net/spi/URLStreamHandlerProvider/Basic.java
jdk/test/java/net/spi/URLStreamHandlerProvider/Child.java
jdk/test/java/net/spi/URLStreamHandlerProvider/bad.provider.template
jdk/test/java/net/spi/URLStreamHandlerProvider/basic.policy
jdk/test/java/net/spi/URLStreamHandlerProvider/provider.template
jdk/test/javax/net/ssl/FixingJavadocs/ComURLNulls.java
jdk/test/sun/net/www/protocol/https/NewImpl/ComHTTPSConnection.java
jdk/test/sun/net/www/protocol/https/NewImpl/ComHostnameVerifier.java
--- a/jdk/src/java.base/share/classes/java/net/URL.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/URL.java	Mon Feb 23 15:48:20 2015 +0000
@@ -27,8 +27,15 @@
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.spi.URLStreamHandlerProvider;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
 import java.util.Hashtable;
-import java.util.StringTokenizer;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.ServiceConfigurationError;
+import java.util.ServiceLoader;
+
 import sun.security.util.SecurityConstants;
 
 /**
@@ -248,23 +255,19 @@
      *     stream protocol handler.
      * <li>If no {@code URLStreamHandlerFactory} has yet been set up,
      *     or if the factory's {@code createURLStreamHandler} method
-     *     returns {@code null}, then the constructor finds the
-     *     value of the system property:
-     *     <blockquote><pre>
-     *         java.protocol.handler.pkgs
-     *     </pre></blockquote>
-     *     If the value of that system property is not {@code null},
-     *     it is interpreted as a list of packages separated by a vertical
-     *     slash character '{@code |}'. The constructor tries to load
-     *     the class named:
-     *     <blockquote><pre>
-     *         &lt;<i>package</i>&gt;.&lt;<i>protocol</i>&gt;.Handler
-     *     </pre></blockquote>
-     *     where &lt;<i>package</i>&gt; is replaced by the name of the package
-     *     and &lt;<i>protocol</i>&gt; is replaced by the name of the protocol.
-     *     If this class does not exist, or if the class exists but it is not
-     *     a subclass of {@code URLStreamHandler}, then the next package
-     *     in the list is tried.
+     *     returns {@code null}, then the {@linkplain java.util.ServiceLoader
+     *     ServiceLoader} mechanism is used to locate {@linkplain
+     *     java.net.spi.URLStreamHandlerProvider URLStreamHandlerProvider}
+     *     implementations using the system class
+     *     loader. The order that providers are located is implementation
+     *     specific, and an implementation is free to cache the located
+     *     providers. A {@linkplain java.util.ServiceConfigurationError
+     *     ServiceConfigurationError}, {@code Error} or {@code RuntimeException}
+     *     thrown from the {@code createURLStreamHandler}, if encountered, will
+     *     be propagated to the calling thread. The {@code
+     *     createURLStreamHandler} method of each provider, if instantiated, is
+     *     invoked, with the protocol string, until a provider returns non-null,
+     *     or all providers have been exhausted.
      * <li>If the previous step fails to find a protocol handler, then the
      *     constructor tries to load a built-in protocol handler.
      *     If this class does not exist, or if the class exists but it is not a
@@ -277,8 +280,12 @@
      * <blockquote><pre>
      *     http, https, file, and jar
      * </pre></blockquote>
-     * Protocol handlers for additional protocols may also be
-     * available.
+     * Protocol handlers for additional protocols may also be  available.
+     * Some protocol handlers, for example those used for loading platform
+     * classes or classes on the class path, may not be overridden. The details
+     * of such restrictions, and when those restrictions apply (during
+     * initialization of the runtime for example), are implementation specific
+     * and therefore not specified
      *
      * <p>No validation of the inputs is performed by this constructor.
      *
@@ -1107,20 +1114,115 @@
             }
             handlers.clear();
 
-            // ensure the core protocol handlers are loaded before setting
-            // a custom URLStreamHandlerFactory
-            ensureHandlersLoaded("jrt", "jar", "file");
-
             // safe publication of URLStreamHandlerFactory with volatile write
             factory = fac;
         }
     }
 
+    private static final URLStreamHandlerFactory defaultFactory = new DefaultFactory();
+
+    private static class DefaultFactory implements URLStreamHandlerFactory {
+        private static String PREFIX = "sun.net.www.protocol";
+
+        public URLStreamHandler createURLStreamHandler(String protocol) {
+            String name = PREFIX + "." + protocol + ".Handler";
+            try {
+                Class<?> c = Class.forName(name);
+                return (URLStreamHandler)c.newInstance();
+            } catch (ClassNotFoundException x) {
+                // ignore
+            } catch (Exception e) {
+                // For compatibility, all Exceptions are ignored.
+                // any number of exceptions can get thrown here
+            }
+            return null;
+        }
+    }
+
+    private static Iterator<URLStreamHandlerProvider> providers() {
+        return new Iterator<URLStreamHandlerProvider>() {
+
+            ClassLoader cl = ClassLoader.getSystemClassLoader();
+            ServiceLoader<URLStreamHandlerProvider> sl =
+                    ServiceLoader.load(URLStreamHandlerProvider.class, cl);
+            Iterator<URLStreamHandlerProvider> i = sl.iterator();
+
+            URLStreamHandlerProvider next = null;
+
+            private boolean getNext() {
+                while (next == null) {
+                    try {
+                        if (!i.hasNext())
+                            return false;
+                        next = i.next();
+                    } catch (ServiceConfigurationError sce) {
+                        if (sce.getCause() instanceof SecurityException) {
+                            // Ignore security exceptions
+                            continue;
+                        }
+                        throw sce;
+                    }
+                }
+                return true;
+            }
+
+            public boolean hasNext() {
+                return getNext();
+            }
+
+            public URLStreamHandlerProvider next() {
+                if (!getNext())
+                    throw new NoSuchElementException();
+                URLStreamHandlerProvider n = next;
+                next = null;
+                return n;
+            }
+        };
+    }
+
+    // Thread-local gate to prevent recursive provider lookups
+    private static ThreadLocal<Object> gate = new ThreadLocal<>();
+
+    private static URLStreamHandler lookupViaProviders(final String protocol) {
+        if (!sun.misc.VM.isBooted())
+            return null;
+
+        if (gate.get() != null)
+            throw new Error("Circular loading of URL stream handler providers detected");
+
+        gate.set(gate);
+        try {
+            return AccessController.doPrivileged(
+                new PrivilegedAction<URLStreamHandler>() {
+                    public URLStreamHandler run() {
+                        Iterator<URLStreamHandlerProvider> itr = providers();
+                        while (itr.hasNext()) {
+                            URLStreamHandlerProvider f = itr.next();
+                            URLStreamHandler h = f.createURLStreamHandler(protocol);
+                            if (h != null)
+                                return h;
+                        }
+                        return null;
+                    }
+                });
+        } finally {
+            gate.set(null);
+        }
+    }
+
+    private static final String[] NON_OVERRIDEABLE_PROTOCOLS = {"file", "jrt"};
+    private static boolean isOverrideable(String protocol) {
+        for (String p : NON_OVERRIDEABLE_PROTOCOLS)
+            if (protocol.equalsIgnoreCase(p))
+                return false;
+        return true;
+    }
+
     /**
      * A table of protocol handlers.
      */
     static Hashtable<String,URLStreamHandler> handlers = new Hashtable<>();
-    private static Object streamHandlerLock = new Object();
+    private static final Object streamHandlerLock = new Object();
 
     /**
      * Returns the Stream Handler.
@@ -1129,66 +1231,33 @@
     static URLStreamHandler getURLStreamHandler(String protocol) {
 
         URLStreamHandler handler = handlers.get(protocol);
-        if (handler == null) {
+
+        if (handler != null) {
+            return handler;
+        }
 
-            boolean checkedWithFactory = false;
+        URLStreamHandlerFactory fac;
+        boolean checkedWithFactory = false;
 
+        if (isOverrideable(protocol)) {
             // Use the factory (if any). Volatile read makes
             // URLStreamHandlerFactory appear fully initialized to current thread.
-            URLStreamHandlerFactory fac = factory;
+            fac = factory;
             if (fac != null) {
                 handler = fac.createURLStreamHandler(protocol);
                 checkedWithFactory = true;
             }
 
-            // Try java protocol handler
             if (handler == null) {
-                String packagePrefixList = null;
-
-                packagePrefixList
-                    = java.security.AccessController.doPrivileged(
-                    new sun.security.action.GetPropertyAction(
-                        protocolPathProp,""));
-                if (packagePrefixList != "") {
-                    packagePrefixList += "|";
-                }
-
-                // REMIND: decide whether to allow the "null" class prefix
-                // or not.
-                packagePrefixList += "sun.net.www.protocol";
-
-                StringTokenizer packagePrefixIter =
-                    new StringTokenizer(packagePrefixList, "|");
-
-                while (handler == null &&
-                       packagePrefixIter.hasMoreTokens()) {
+                handler = lookupViaProviders(protocol);
+            }
+        }
 
-                    String packagePrefix =
-                      packagePrefixIter.nextToken().trim();
-                    try {
-                        String clsName = packagePrefix + "." + protocol +
-                          ".Handler";
-                        Class<?> cls = null;
-                        try {
-                            cls = Class.forName(clsName);
-                        } catch (ClassNotFoundException e) {
-                            ClassLoader cl = ClassLoader.getSystemClassLoader();
-                            if (cl != null) {
-                                cls = cl.loadClass(clsName);
-                            }
-                        }
-                        if (cls != null) {
-                            handler  =
-                              (URLStreamHandler)cls.newInstance();
-                        }
-                    } catch (Exception e) {
-                        // any number of exceptions can get thrown here
-                    }
-                }
-            }
-
-            synchronized (streamHandlerLock) {
-
+        synchronized (streamHandlerLock) {
+            if (handler == null) {
+                // Try the built-in protocol handler
+                handler = defaultFactory.createURLStreamHandler(protocol);
+            } else {
                 URLStreamHandler handler2 = null;
 
                 // Check again with hashtable just in case another
@@ -1202,7 +1271,7 @@
                 // Check with factory if another thread set a
                 // factory since our last check
                 if (!checkedWithFactory && (fac = factory) != null) {
-                    handler2 = fac.createURLStreamHandler(protocol);
+                    handler2 =  fac.createURLStreamHandler(protocol);
                 }
 
                 if (handler2 != null) {
@@ -1211,30 +1280,18 @@
                     // this thread created.
                     handler = handler2;
                 }
+            }
 
-                // Insert this handler into the hashtable
-                if (handler != null) {
-                    handlers.put(protocol, handler);
-                }
-
+            // Insert this handler into the hashtable
+            if (handler != null) {
+                handlers.put(protocol, handler);
             }
         }
 
         return handler;
-
     }
 
     /**
-     * Ensures that the given protocol handlers are loaded
-     */
-    private static void ensureHandlersLoaded(String... protocols) {
-        for (String protocol: protocols) {
-            getURLStreamHandler(protocol);
-        }
-    }
-
-
-    /**
      * WriteObject is called to save the state of the URL to an
      * ObjectOutputStream. The handler is not saved since it is
      * specific to this system.
--- a/jdk/src/java.base/share/classes/java/net/URLStreamHandlerFactory.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/URLStreamHandlerFactory.java	Mon Feb 23 15:48:20 2015 +0000
@@ -44,7 +44,9 @@
      *
      * @param   protocol   the protocol ("{@code ftp}",
      *                     "{@code http}", "{@code nntp}", etc.).
-     * @return  a {@code URLStreamHandler} for the specific protocol.
+     * @return  a {@code URLStreamHandler} for the specific protocol, or {@code
+     *          null} if this factory cannot create a handler for the specific
+     *          protocol
      * @see     java.net.URLStreamHandler
      */
     URLStreamHandler createURLStreamHandler(String protocol);
--- a/jdk/src/java.base/share/classes/java/net/package-info.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/src/java.base/share/classes/java/net/package-info.java	Mon Feb 23 15:48:20 2015 +0000
@@ -143,13 +143,11 @@
  * a similar URL will try to instantiate the handler for the specified protocol;
  * if it doesn't exist an exception will be thrown.
  * <p>By default the protocol handlers are loaded dynamically from the default
- *    location. It is, however, possible to add to the search path by setting
- *    the {@code java.protocol.handler.pkgs} system property. For instance if
- *    it is set to {@code myapp.protocols}, then the URL code will try, in the
- *    case of http, first to load {@code myapp.protocols.http.Handler}, then,
- *    if this fails, {@code http.Handler} from the default location.
- * <p>Note that the Handler class <b>has to</b> be a subclass of the abstract
- *    class {@link java.net.URLStreamHandler}.</p>
+ *    location. It is, however, possible to deploy additional protocols handlers
+ *    as {@link java.util.ServiceLoader services}. Service providers of type
+ *    {@linkplain java.net.spi.URLStreamHandlerProvider} are located at
+ *    runtime, as specified in the {@linkplain
+ *    java.net.URL#URL(String,String,int,String) URL constructor}.
  * <h2>Additional Specification</h2>
  * <ul>
  *       <li><a href="doc-files/net-properties.html">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/java/net/spi/URLStreamHandlerProvider.java	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+package java.net.spi;
+
+import java.net.URLStreamHandlerFactory;
+
+/**
+ * URL stream handler service-provider class.
+ *
+ *<p> A URL stream handler provider is a concrete subclass of this class that
+ * has a zero-argument constructor. URL stream handler providers may be
+ * installed in an instance of the Java platform by adding them to the
+ * application class path.
+ *
+ * <p> A URL stream handler provider identifies itself with a
+ * provider-configuration file named java.net.spi.URLStreamHandlerProvider in
+ * the resource directory META-INF/services. The file should contain a list of
+ * fully-qualified concrete URL stream handler provider class names, one per
+ * line.
+ *
+ * @since 1.9
+ */
+public abstract class URLStreamHandlerProvider
+    implements URLStreamHandlerFactory
+{
+    private static Void checkPermission() {
+        SecurityManager sm = System.getSecurityManager();
+        if (sm != null)
+            sm.checkPermission(new RuntimePermission("setFactory"));
+        return null;
+    }
+    private URLStreamHandlerProvider(Void ignore) { }
+
+    /**
+     * Initializes a new URL stream handler provider.
+     *
+     * @throws  SecurityException
+     *          If a security manager has been installed and it denies
+     *          {@link RuntimePermission}{@code ("setFactory")}.
+     */
+    protected URLStreamHandlerProvider() {
+        this(checkPermission());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.base/share/classes/java/net/spi/package-info.java	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2015, 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/**
+ * Service-provider classes for the <tt>{@link java.net}</tt> package.
+ *
+ * <p> Only developers who are defining new URL stream handler providers
+ * should need to make direct use of this package.
+ *
+ * @since 1.9
+ */
+
+package java.net.spi;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/spi/URLStreamHandlerProvider/Basic.java	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,313 @@
+/*
+ * Copyright (c) 2015, 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.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.tools.JavaCompiler;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardJavaFileManager;
+import javax.tools.StandardLocation;
+import javax.tools.ToolProvider;
+import jdk.testlibrary.FileUtils;
+import jdk.testlibrary.JDKToolFinder;
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+
+/*
+ * @test
+ * @bug 8064924
+ * @summary Basic test for URLStreamHandlerProvider
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.FileUtils jdk.testlibrary.JDKToolFinder
+ * @compile Basic.java Child.java
+ * @run main Basic
+ */
+
+public class Basic {
+
+    static final Path TEST_SRC = Paths.get(System.getProperty("test.src", "."));
+    static final Path TEST_CLASSES = Paths.get(System.getProperty("test.classes", "."));
+
+    public static void main(String[] args) throws Throwable {
+        unknownProtocol("foo", UNKNOWN);
+        unknownProtocol("bar", UNKNOWN);
+        viaProvider("baz", KNOWN);
+        viaProvider("bert", KNOWN);
+        viaProvider("ernie", UNKNOWN, "-Djava.security.manager");
+        viaProvider("curly", UNKNOWN, "-Djava.security.manager");
+        viaProvider("larry", KNOWN, "-Djava.security.manager",
+                "-Djava.security.policy=" + TEST_SRC + File.separator + "basic.policy");
+        viaProvider("moe", KNOWN, "-Djava.security.manager",
+                "-Djava.security.policy=" + TEST_SRC + File.separator + "basic.policy");
+        viaBadProvider("tom", SCE);
+        viaBadProvider("jerry", SCE);
+    }
+
+    static final Consumer<Result> KNOWN = r -> {
+        if (r.exitValue != 0 || !r.output.isEmpty())
+            throw new RuntimeException(r.output);
+    };
+    static final Consumer<Result> UNKNOWN = r -> {
+        if (r.exitValue == 0 ||
+            !r.output.contains("java.net.MalformedURLException: unknown protocol")) {
+            throw new RuntimeException("exitValue: "+ r.exitValue + ", output:[" +r.output +"]");
+        }
+    };
+    static final Consumer<Result> SCE = r -> {
+        if (r.exitValue == 0 ||
+            !r.output.contains("java.util.ServiceConfigurationError")) {
+            throw new RuntimeException("exitValue: "+ r.exitValue + ", output:[" +r.output +"]");
+        }
+    };
+
+    static void unknownProtocol(String protocol, Consumer<Result> resultChecker) {
+        System.out.println("\nTesting " + protocol);
+        Result r = java(Collections.emptyList(), asList(TEST_CLASSES),
+                "Child", protocol);
+        resultChecker.accept(r);
+    }
+
+    static void viaProvider(String protocol, Consumer<Result> resultChecker,
+                            String... sysProps)
+        throws Exception
+    {
+        viaProviderWithTemplate(protocol, resultChecker,
+                                TEST_SRC.resolve("provider.template"),
+                                sysProps);
+    }
+
+    static void viaBadProvider(String protocol, Consumer<Result> resultChecker,
+                               String... sysProps)
+        throws Exception
+    {
+        viaProviderWithTemplate(protocol, resultChecker,
+                                TEST_SRC.resolve("bad.provider.template"),
+                                sysProps);
+    }
+
+    static void viaProviderWithTemplate(String protocol,
+                                        Consumer<Result> resultChecker,
+                                        Path template, String... sysProps)
+        throws Exception
+    {
+        System.out.println("\nTesting " + protocol);
+        Path testRoot = Paths.get("URLStreamHandlerProvider-" + protocol);
+        if (Files.exists(testRoot))
+            FileUtils.deleteFileTreeWithRetry(testRoot);
+        Files.createDirectory(testRoot);
+
+        Path srcPath = Files.createDirectory(testRoot.resolve("src"));
+        Path srcClass = createProvider(protocol, template, srcPath);
+
+        Path build = Files.createDirectory(testRoot.resolve("build"));
+        javac(build, srcClass);
+        createServices(build, protocol);
+        Path testJar = testRoot.resolve("test.jar");
+        jar(testJar, build);
+
+        List<String> props = new ArrayList<>();
+        for (String p : sysProps)
+            props.add(p);
+
+        Result r = java(props, asList(testJar, TEST_CLASSES),
+                        "Child", protocol);
+
+        resultChecker.accept(r);
+    }
+
+    static String platformPath(String p) { return p.replace("/", File.separator); }
+    static String binaryName(String name) { return name.replace(".", "/"); }
+
+    static final String SERVICE_IMPL_PREFIX = "net.java.openjdk.test";
+
+    static void createServices(Path dst, String protocol) throws IOException {
+        Path services = Files.createDirectories(dst.resolve("META-INF")
+                                                   .resolve("services"));
+
+        final String implName =  SERVICE_IMPL_PREFIX + "." + protocol + ".Provider";
+        Path s = services.resolve("java.net.spi.URLStreamHandlerProvider");
+        FileWriter fw = new FileWriter(s.toFile());
+        try {
+            fw.write(implName);
+        } finally {
+            fw.close();
+        }
+    }
+
+    static Path createProvider(String protocol, Path srcTemplate, Path dst)
+        throws IOException
+    {
+        String pkg = SERVICE_IMPL_PREFIX + "." + protocol;
+        Path classDst = dst.resolve(platformPath(binaryName(pkg)));
+        Files.createDirectories(classDst);
+        Path classPath = classDst.resolve("Provider.java");
+
+        List<String> lines = Files.lines(srcTemplate)
+                                  .map(s -> s.replaceAll("\\$package", pkg))
+                                  .map(s -> s.replaceAll("\\$protocol", protocol))
+                                  .collect(Collectors.toList());
+        Files.write(classPath, lines);
+
+        return classPath;
+    }
+
+    static void jar(Path jarName, Path jarRoot) { String jar = getJDKTool("jar");
+        ProcessBuilder p = new ProcessBuilder(jar, "cf", jarName.toString(),
+                "-C", jarRoot.toString(), ".");
+        quickFail(run(p));
+    }
+
+    static void javac(Path dest, Path... sourceFiles) throws IOException {
+        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+        try (StandardJavaFileManager fileManager =
+                    compiler.getStandardFileManager(null, null, null)) {
+
+            List<File> files = Stream.of(sourceFiles)
+                    .map(p -> p.toFile())
+                    .collect(Collectors.toList());
+            List<File> dests = Stream.of(dest)
+                    .map(p -> p.toFile())
+                    .collect(Collectors.toList());
+            Iterable<? extends JavaFileObject> compilationUnits =
+                    fileManager.getJavaFileObjectsFromFiles(files);
+            fileManager.setLocation(StandardLocation.CLASS_OUTPUT, dests);
+            JavaCompiler.CompilationTask task =
+                    compiler.getTask(null, fileManager, null, null, null, compilationUnits);
+            boolean passed = task.call();
+            if (!passed)
+                throw new RuntimeException("Error compiling " + files);
+        }
+    }
+
+    static void quickFail(Result r) {
+        if (r.exitValue != 0)
+            throw new RuntimeException(r.output);
+    }
+
+    static Result java(List<String> sysProps, Collection<Path> classpath,
+                       String classname, String arg) {
+        String java = getJDKTool("java");
+
+        List<String> commands = new ArrayList<>();
+        commands.add(java);
+        for (String prop : sysProps)
+            commands.add(prop);
+
+        String cp = classpath.stream()
+                .map(Path::toString)
+                .collect(Collectors.joining(File.pathSeparator));
+        commands.add("-cp");
+        commands.add(cp);
+        commands.add(classname);
+        commands.add(arg);
+
+        return run(new ProcessBuilder(commands));
+    }
+
+    static Result run(ProcessBuilder pb) {
+        Process p = null;
+        System.out.println("running: " + pb.command());
+        try {
+            p = pb.start();
+        } catch (IOException e) {
+            throw new RuntimeException(
+                    format("Couldn't start process '%s'", pb.command()), e);
+        }
+
+        String output;
+        try {
+            output = toString(p.getInputStream(), p.getErrorStream());
+        } catch (IOException e) {
+            throw new RuntimeException(
+                    format("Couldn't read process output '%s'", pb.command()), e);
+        }
+
+        try {
+            p.waitFor();
+        } catch (InterruptedException e) {
+            throw new RuntimeException(
+                    format("Process hasn't finished '%s'", pb.command()), e);
+        }
+
+        return new Result(p.exitValue(), output);
+    }
+
+    static final String DEFAULT_IMAGE_BIN = System.getProperty("java.home")
+            + File.separator + "bin" + File.separator;
+
+    static String getJDKTool(String name) {
+        try {
+            return JDKToolFinder.getJDKTool(name);
+        } catch (Exception x) {
+            return DEFAULT_IMAGE_BIN + name;
+        }
+    }
+
+    static String toString(InputStream... src) throws IOException {
+        StringWriter dst = new StringWriter();
+        Reader concatenated =
+                new InputStreamReader(
+                        new SequenceInputStream(
+                                Collections.enumeration(asList(src))));
+        copy(concatenated, dst);
+        return dst.toString();
+    }
+
+    static void copy(Reader src, Writer dst) throws IOException {
+        int len;
+        char[] buf = new char[1024];
+        try {
+            while ((len = src.read(buf)) != -1)
+                dst.write(buf, 0, len);
+        } finally {
+            try {
+                src.close();
+            } catch (IOException ignored1) {
+            } finally {
+                try {
+                    dst.close();
+                } catch (IOException ignored2) {
+                }
+            }
+        }
+    }
+
+    static class Result {
+        final int exitValue;
+        final String output;
+
+        private Result(int exitValue, String output) {
+            this.exitValue = exitValue;
+            this.output = output;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/spi/URLStreamHandlerProvider/Child.java	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2015, 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.net.MalformedURLException;
+import java.net.URL;
+
+public class Child {
+
+    public static void main(String[] args) throws MalformedURLException {
+        if (args.length != 1) {
+            System.err.println("Usage: java Child <protocol>");
+            return;
+        }
+        String protocol = args[0];
+        URL url = new URL(protocol + "://");
+
+        // toExternalForm should return the protocol string
+        String s = url.toExternalForm();
+        if (!s.equals(protocol)) {
+            System.err.println("Expected url.toExternalForm to return "
+                                       + protocol + ", but got: " + s);
+            System.exit(1);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/spi/URLStreamHandlerProvider/bad.provider.template	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,41 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package $package;
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.spi.URLStreamHandlerProvider;
+
+public class Provider extends URLStreamHandlerProvider {
+
+    public Provider(String someRandomArg) {  // No no-args constructor
+        super();
+    }
+
+    @Override
+    public URLStreamHandler createURLStreamHandler(String protocol) {
+        return null;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/spi/URLStreamHandlerProvider/basic.policy	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+ grant {
+    permission java.lang.RuntimePermission "setFactory";
+};
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/net/spi/URLStreamHandlerProvider/provider.template	Mon Feb 23 15:48:20 2015 +0000
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2015, 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.
+ */
+
+package $package;
+
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+import java.net.spi.URLStreamHandlerProvider;
+
+public class Provider extends URLStreamHandlerProvider {
+
+    private static final String PROTOCOL = "$protocol";
+
+    @Override
+    public URLStreamHandler createURLStreamHandler(String protocol) {
+        if (!PROTOCOL.equals(protocol))
+            return null;
+
+        return new Handler();
+    }
+
+    static class Handler extends URLStreamHandler {
+        public URLConnection openConnection(URL u) throws java.io.IOException {
+            return null;
+        }
+
+        public String toExternalForm(URL u) { return PROTOCOL; }
+    }
+}
--- a/jdk/test/javax/net/ssl/FixingJavadocs/ComURLNulls.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/test/javax/net/ssl/FixingJavadocs/ComURLNulls.java	Mon Feb 23 15:48:20 2015 +0000
@@ -46,12 +46,23 @@
 
 public class ComURLNulls {
 
+    private static class ComSunHTTPSHandlerFactory implements URLStreamHandlerFactory {
+        private static String SUPPORTED_PROTOCOL = "https";
+
+        public URLStreamHandler createURLStreamHandler(String protocol) {
+            if (!protocol.equalsIgnoreCase(SUPPORTED_PROTOCOL))
+                return null;
+
+            return new com.sun.net.ssl.internal.www.protocol.https.Handler();
+        }
+    }
+
     public static void main(String[] args) throws Exception {
         HostnameVerifier reservedHV =
             HttpsURLConnection.getDefaultHostnameVerifier();
         try {
-            System.setProperty("java.protocol.handler.pkgs",
-                                    "com.sun.net.ssl.internal.www.protocol");
+            URL.setURLStreamHandlerFactory(new ComSunHTTPSHandlerFactory());
+
             /**
              * This test does not establish any connection to the specified
              * URL, hence a dummy URL is used.
--- a/jdk/test/sun/net/www/protocol/https/NewImpl/ComHTTPSConnection.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/test/sun/net/www/protocol/https/NewImpl/ComHTTPSConnection.java	Mon Feb 23 15:48:20 2015 +0000
@@ -188,6 +188,17 @@
         }
     }
 
+    private static class ComSunHTTPSHandlerFactory implements URLStreamHandlerFactory {
+        private static String SUPPORTED_PROTOCOL = "https";
+
+        public URLStreamHandler createURLStreamHandler(String protocol) {
+            if (!protocol.equalsIgnoreCase(SUPPORTED_PROTOCOL))
+                return null;
+
+            return new com.sun.net.ssl.internal.www.protocol.https.Handler();
+        }
+    }
+
     /*
      * Define the client side of the test.
      *
@@ -205,8 +216,7 @@
         HostnameVerifier reservedHV =
             HttpsURLConnection.getDefaultHostnameVerifier();
         try {
-            System.setProperty("java.protocol.handler.pkgs",
-                "com.sun.net.ssl.internal.www.protocol");
+            URL.setURLStreamHandlerFactory(new ComSunHTTPSHandlerFactory());
             HttpsURLConnection.setDefaultHostnameVerifier(new NameVerifier());
 
             URL url = new URL("https://" + "localhost:" + serverPort +
--- a/jdk/test/sun/net/www/protocol/https/NewImpl/ComHostnameVerifier.java	Mon Feb 23 11:37:36 2015 +0100
+++ b/jdk/test/sun/net/www/protocol/https/NewImpl/ComHostnameVerifier.java	Mon Feb 23 15:48:20 2015 +0000
@@ -186,6 +186,17 @@
         }
     }
 
+    private static class ComSunHTTPSHandlerFactory implements URLStreamHandlerFactory {
+        private static String SUPPORTED_PROTOCOL = "https";
+
+        public URLStreamHandler createURLStreamHandler(String protocol) {
+            if (!protocol.equalsIgnoreCase(SUPPORTED_PROTOCOL))
+                return null;
+
+            return new com.sun.net.ssl.internal.www.protocol.https.Handler();
+        }
+    }
+
     /*
      * Define the client side of the test.
      *
@@ -200,8 +211,7 @@
             Thread.sleep(50);
         }
 
-        System.setProperty("java.protocol.handler.pkgs",
-            "com.sun.net.ssl.internal.www.protocol");
+        URL.setURLStreamHandlerFactory(new ComSunHTTPSHandlerFactory());
 
         System.setProperty("https.cipherSuites",
                 "SSL_DH_anon_WITH_3DES_EDE_CBC_SHA");