8064925: URLConnection::getContent needs to be updated to work with modules

Reviewed-by: chegar, alanb
This commit is contained in:
Pavel Rappo 2015-07-10 16:40:12 +01:00
parent 7c256783a5
commit 91a4a6b0ce
20 changed files with 780 additions and 82 deletions

View File

@ -44,14 +44,14 @@ import java.io.IOException;
* instance of a subclass of {@code ContentHandler}, and its
* {@code getContent} method is called to create the object.
* <p>
* If no content handler could be found, URLConnection will
* look for a content handler in a user-defineable set of places.
* If no content handler could be {@linkplain URLConnection#getContent() found},
* URLConnection will look for a content handler in a user-definable set of places.
* Users can define a vertical-bar delimited set of class prefixes
* to search through by defining the <i>java.content.handler.pkgs</i>
* to search through by defining the <i>{@value java.net.URLConnection#contentPathProp}</i>
* property. The class name must be of the form:
* <blockquote>
* <i>{package-prefix}.{major}.{minor}</i>
* <P>
* <p>
* where <i>{major}.{minor}</i> is formed by taking the
* content-type string, replacing all slash characters with a
* {@code period} ('.'), and all other non-alphanumeric characters
@ -82,6 +82,7 @@ import java.io.IOException;
* @since 1.0
*/
abstract public class ContentHandler {
/**
* Given a URL connect stream positioned at the beginning of the
* representation of an object, this method reads that stream and
@ -104,8 +105,8 @@ abstract public class ContentHandler {
* @param urlc a URL connection.
* @param classes an array of types requested
* @return the object read by the {@code ContentHandler} that is
* the first match of the suggested types.
* null if none of the requested are supported.
* the first match of the suggested types or
* {@code null} if none of the requested are supported.
* @exception IOException if an I/O error occurs while reading the object.
* @since 1.3
*/
@ -113,12 +114,11 @@ abstract public class ContentHandler {
public Object getContent(URLConnection urlc, Class[] classes) throws IOException {
Object obj = getContent(urlc);
for (int i = 0; i < classes.length; i++) {
if (classes[i].isInstance(obj)) {
for (Class<?> c : classes) {
if (c.isInstance(obj)) {
return obj;
}
}
}
return null;
}
}

View File

@ -39,12 +39,13 @@ package java.net;
* @since 1.0
*/
public interface ContentHandlerFactory {
/**
* Creates a new {@code ContentHandler} to read an object from
* a {@code URLStreamHandler}.
*
* @param mimetype the MIME type for which a content handler is desired.
*
* @return a new {@code ContentHandler} to read an object from a
* {@code URLStreamHandler}.
* @see java.net.ContentHandler

View File

@ -28,8 +28,12 @@ package java.net;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.PrivilegedAction;
import java.util.Hashtable;
import java.util.Date;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.StringTokenizer;
import java.util.Collections;
import java.util.Map;
@ -107,7 +111,7 @@ import sun.net.www.MessageHeader;
* <li>{@code getContentType}
* <li>{@code getDate}
* <li>{@code getExpiration}
* <li>{@code getLastModifed}
* <li>{@code getLastModified}
* </ul>
* <p>
* provide convenient access to these fields. The
@ -695,16 +699,30 @@ public abstract class URLConnection {
* This method first determines the content type of the object by
* calling the {@code getContentType} method. If this is
* the first time that the application has seen that specific content
* type, a content handler for that content type is created:
* type, a content handler for that content type is created.
* <p> This is done as follows:
* <ol>
* <li>If the application has set up a content handler factory instance
* using the {@code setContentHandlerFactory} method, the
* {@code createContentHandler} method of that instance is called
* with the content type as an argument; the result is a content
* handler for that content type.
* <li>If no content handler factory has yet been set up, or if the
* factory's {@code createContentHandler} method returns
* {@code null}, then this method tries to load a content handler
* <li>If no {@code ContentHandlerFactory} has yet been set up,
* or if the factory's {@code createContentHandler} method
* returns {@code null}, then the {@linkplain java.util.ServiceLoader
* ServiceLoader} mechanism is used to locate {@linkplain
* java.net.ContentHandlerFactory ContentHandlerFactory}
* implementations using the system class
* loader. The order that factories are located is implementation
* specific, and an implementation is free to cache the located
* factories. A {@linkplain java.util.ServiceConfigurationError
* ServiceConfigurationError}, {@code Error} or {@code RuntimeException}
* thrown from the {@code createContentHandler}, if encountered, will
* be propagated to the calling thread. The {@code
* createContentHandler} method of each factory, if instantiated, is
* invoked, with the content type, until a factory returns non-null,
* or all factories have been exhausted.
* <li>Failing that, this method tries to load a content handler
* class as defined by {@link java.net.ContentHandler ContentHandler}.
* If the class does not exist, or is not a subclass of {@code
* ContentHandler}, then an {@code UnknownServiceException} is thrown.
@ -855,8 +873,7 @@ public abstract class URLConnection {
* @see #getDoInput()
*/
public void setDoInput(boolean doinput) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
doInput = doinput;
}
@ -885,8 +902,7 @@ public abstract class URLConnection {
* @see #getDoOutput()
*/
public void setDoOutput(boolean dooutput) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
doOutput = dooutput;
}
@ -911,8 +927,7 @@ public abstract class URLConnection {
* @see #getAllowUserInteraction()
*/
public void setAllowUserInteraction(boolean allowuserinteraction) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
allowUserInteraction = allowuserinteraction;
}
@ -974,8 +989,7 @@ public abstract class URLConnection {
* @see #getUseCaches()
*/
public void setUseCaches(boolean usecaches) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
useCaches = usecaches;
}
@ -1000,8 +1014,7 @@ public abstract class URLConnection {
* @see #getIfModifiedSince()
*/
public void setIfModifiedSince(long ifmodifiedsince) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
ifModifiedSince = ifmodifiedsince;
}
@ -1055,12 +1068,11 @@ public abstract class URLConnection {
* (e.g., "{@code Accept}").
* @param value the value associated with it.
* @throws IllegalStateException if already connected
* @throws NullPointerException if key is <CODE>null</CODE>
* @throws NullPointerException if key is {@code null}
* @see #getRequestProperty(java.lang.String)
*/
public void setRequestProperty(String key, String value) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
if (key == null)
throw new NullPointerException ("key is null");
@ -1084,8 +1096,7 @@ public abstract class URLConnection {
* @since 1.4
*/
public void addRequestProperty(String key, String value) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
if (key == null)
throw new NullPointerException ("key is null");
@ -1107,8 +1118,7 @@ public abstract class URLConnection {
* @see #setRequestProperty(java.lang.String, java.lang.String)
*/
public String getRequestProperty(String key) {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
if (requests == null)
return null;
@ -1129,8 +1139,7 @@ public abstract class URLConnection {
* @since 1.4
*/
public Map<String,List<String>> getRequestProperties() {
if (connected)
throw new IllegalStateException("Already connected");
checkConnected();
if (requests == null)
return Collections.emptyMap();
@ -1183,7 +1192,7 @@ public abstract class URLConnection {
/**
* The ContentHandler factory.
*/
static ContentHandlerFactory factory;
private static volatile ContentHandlerFactory factory;
/**
* Sets the {@code ContentHandlerFactory} of an
@ -1216,37 +1225,45 @@ public abstract class URLConnection {
factory = fac;
}
private static Hashtable<String, ContentHandler> handlers = new Hashtable<>();
private static final Hashtable<String, ContentHandler> handlers = new Hashtable<>();
/**
* Gets the Content Handler appropriate for this connection.
*/
synchronized ContentHandler getContentHandler()
throws UnknownServiceException
{
private ContentHandler getContentHandler() throws UnknownServiceException {
String contentType = stripOffParameters(getContentType());
ContentHandler handler = null;
if (contentType == null)
if (contentType == null) {
throw new UnknownServiceException("no content-type");
try {
handler = handlers.get(contentType);
if (handler != null)
return handler;
} catch(Exception e) {
}
if (factory != null)
ContentHandler handler = handlers.get(contentType);
if (handler != null)
return handler;
if (factory != null) {
handler = factory.createContentHandler(contentType);
if (handler == null) {
try {
handler = lookupContentHandlerClassFor(contentType);
} catch(Exception e) {
e.printStackTrace();
handler = UnknownContentHandler.INSTANCE;
}
handlers.put(contentType, handler);
if (handler != null)
return handler;
}
return handler;
handler = lookupContentHandlerViaProvider(contentType);
if (handler != null) {
ContentHandler h = handlers.putIfAbsent(contentType, handler);
return h != null ? h : handler;
}
try {
handler = lookupContentHandlerClassFor(contentType);
} catch (Exception e) {
e.printStackTrace();
handler = UnknownContentHandler.INSTANCE;
}
assert handler != null;
ContentHandler h = handlers.putIfAbsent(contentType, handler);
return h != null ? h : handler;
}
/*
@ -1270,10 +1287,10 @@ public abstract class URLConnection {
private static final String contentPathProp = "java.content.handler.pkgs";
/**
* Looks for a content handler in a user-defineable set of places.
* By default it looks in sun.net.www.content, but users can define a
* vertical-bar delimited set of class prefixes to search through in
* addition by defining the java.content.handler.pkgs property.
* Looks for a content handler in a user-definable set of places.
* By default it looks in {@value #contentClassPrefix}, but users can define
* a vertical-bar delimited set of class prefixes to search through in
* addition by defining the {@value #contentPathProp} property.
* The class name must be of the form:
* <pre>
* {package-prefix}.{major}.{minor}
@ -1281,11 +1298,10 @@ public abstract class URLConnection {
* YoyoDyne.experimental.text.plain
* </pre>
*/
private ContentHandler lookupContentHandlerClassFor(String contentType)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
private ContentHandler lookupContentHandlerClassFor(String contentType) {
String contentHandlerClassName = typeToPackageName(contentType);
String contentHandlerPkgPrefixes =getContentHandlerPkgPrefixes();
String contentHandlerPkgPrefixes = getContentHandlerPkgPrefixes();
StringTokenizer packagePrefixIter =
new StringTokenizer(contentHandlerPkgPrefixes, "|");
@ -1305,17 +1321,46 @@ public abstract class URLConnection {
}
}
if (cls != null) {
ContentHandler handler =
(ContentHandler)cls.newInstance();
return handler;
return (ContentHandler) cls.newInstance();
}
} catch(Exception e) {
}
} catch(Exception ignored) { }
}
return UnknownContentHandler.INSTANCE;
}
private ContentHandler lookupContentHandlerViaProvider(String contentType) {
return AccessController.doPrivileged(
new PrivilegedAction<>() {
@Override
public ContentHandler run() {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ServiceLoader<ContentHandlerFactory> sl =
ServiceLoader.load(ContentHandlerFactory.class, cl);
Iterator<ContentHandlerFactory> iterator = sl.iterator();
ContentHandler handler = null;
while (iterator.hasNext()) {
ContentHandlerFactory f;
try {
f = iterator.next();
} catch (ServiceConfigurationError e) {
if (e.getCause() instanceof SecurityException) {
continue;
}
throw e;
}
handler = f.createContentHandler(contentType);
if (handler != null) {
break;
}
}
return handler;
}
});
}
/**
* Utility function to map a MIME content type into an equivalent
* pair of class name components. For example: "text/html" would
@ -1345,8 +1390,8 @@ public abstract class URLConnection {
* Returns a vertical bar separated list of package prefixes for potential
* content handlers. Tries to get the java.content.handler.pkgs property
* to use as a set of package prefixes to search. Whether or not
* that property has been defined, the sun.net.www.content is always
* the last one on the returned package list.
* that property has been defined, the {@value #contentClassPrefix}
* is always the last one on the returned package list.
*/
private String getContentHandlerPkgPrefixes() {
String packagePrefixList = AccessController.doPrivileged(
@ -1764,9 +1809,12 @@ public abstract class URLConnection {
return skipped;
}
private void checkConnected() {
if (connected)
throw new IllegalStateException("Already connected");
}
}
class UnknownContentHandler extends ContentHandler {
static final ContentHandler INSTANCE = new UnknownContentHandler();

View File

@ -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. 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.
#
# Provider for content handlers
sun.awt.www.content.MultimediaContentHandlers

View File

@ -0,0 +1,59 @@
/*
* 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 sun.awt.www.content;
import sun.awt.www.content.audio.wav;
import sun.awt.www.content.audio.x_aiff;
import sun.awt.www.content.image.gif;
import sun.awt.www.content.audio.aiff;
import sun.awt.www.content.audio.basic;
import sun.awt.www.content.audio.x_wav;
import sun.awt.www.content.image.jpeg;
import sun.awt.www.content.image.png;
import sun.awt.www.content.image.x_xbitmap;
import sun.awt.www.content.image.x_xpixmap;
import java.net.ContentHandler;
import java.net.ContentHandlerFactory;
public final class MultimediaContentHandlers implements ContentHandlerFactory {
@Override
public ContentHandler createContentHandler(String mimetype) {
switch (mimetype) {
case "audio/aiff": return new aiff();
case "audio/basic": return new basic();
case "audio/wav": return new wav();
case "audio/x-aiff": return new x_aiff();
case "audio/x-wav": return new x_wav();
case "image/gif": return new gif();
case "image/jpeg": return new jpeg();
case "image/png": return new png();
case "image/x-xbitmap": return new x_xbitmap();
case "image/x-xpixmap": return new x_xpixmap();
default: return null;
}
}
}

View File

@ -27,7 +27,7 @@
* Basic .aiff audio handler.
* @author Jeff Nisewanger
*/
package sun.net.www.content.audio;
package sun.awt.www.content.audio;
import java.net.*;
import java.io.IOException;

View File

@ -27,7 +27,7 @@
* Basic .au and .snd audio handler.
* @author Jeff Nisewanger
*/
package sun.net.www.content.audio;
package sun.awt.www.content.audio;
import java.net.*;
import java.io.IOException;

View File

@ -27,7 +27,7 @@
* Basic .wav audio handler.
* @author Jeff Nisewanger
*/
package sun.net.www.content.audio;
package sun.awt.www.content.audio;
import java.net.*;
import java.io.IOException;

View File

@ -27,7 +27,7 @@
* Basic .aiff audio handler.
* @author Jeff Nisewanger
*/
package sun.net.www.content.audio;
package sun.awt.www.content.audio;
import java.net.*;
import java.io.IOException;

View File

@ -27,7 +27,7 @@
* Basic .wav audio handler.
* @author Jeff Nisewanger
*/
package sun.net.www.content.audio;
package sun.awt.www.content.audio;
import java.net.*;
import java.io.IOException;

View File

@ -23,7 +23,7 @@
* questions.
*/
package sun.net.www.content.image;
package sun.awt.www.content.image;
import java.net.*;
import sun.awt.image.*;

View File

@ -23,7 +23,7 @@
* questions.
*/
package sun.net.www.content.image;
package sun.awt.www.content.image;
import java.net.*;
import sun.awt.image.*;

View File

@ -23,7 +23,7 @@
* questions.
*/
package sun.net.www.content.image;
package sun.awt.www.content.image;
import java.net.*;
import java.io.IOException;

View File

@ -23,7 +23,7 @@
* questions.
*/
package sun.net.www.content.image;
package sun.awt.www.content.image;
import java.net.*;
import sun.awt.image.*;

View File

@ -23,7 +23,7 @@
* questions.
*/
package sun.net.www.content.image;
package sun.awt.www.content.image;
import java.net.*;
import sun.awt.image.*;

View File

@ -0,0 +1,371 @@
/*
* 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.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.SequenceInputStream;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonMap;
/*
* @test
* @bug 8064925
* @summary Basic test for ContentHandler. Ensures discovery paths for content
* handlers follow a particular order.
*/
public class ContentHandlersTest {
public static void main(String[] args) throws Throwable {
step1_ContentHandlerFactory();
step2_ServiceLoader();
step3_UserDefined();
step4_BuiltIn();
}
private static void step1_ContentHandlerFactory() throws IOException {
String factoryClassFqn = "net.java.openjdk.test.TestContentHandlerFactory";
Path tmp = Files.createDirectory(Paths.get("ContentHandlersTest-1"));
Path src = templatesHome().resolve("test.template");
Path dst = tmp.resolve("Test.java");
Files.copy(src, dst);
Path build = Files.createDirectory(tmp.resolve("build"));
Path dst1 = fromTemplate(templatesHome().resolve("broken_factory.template"),
factoryClassFqn, tmp);
javac(build, dst, dst1);
Result r = java(emptyMap(), singleton(build), "Test", factoryClassFqn);
if (r.exitValue == 0 || !r.output.startsWith(
stackTraceStringForBrokenFactory(factoryClassFqn))) {
throw new RuntimeException(
"Expected a different kind of failure: " + r.output);
}
}
private static void step2_ServiceLoader() throws IOException {
String factoryClassFqn = "net.java.openjdk.test.TestContentHandlerFactory";
Path tmp = Files.createDirectory(Paths.get("ContentHandlersTest-2"));
Path src = templatesHome().resolve("test.template");
Path dst = tmp.resolve("Test.java");
Files.copy(src, dst);
Path dst1 = fromTemplate(templatesHome().resolve("broken_constructor_factory.template"),
factoryClassFqn, tmp);
Path build = Files.createDirectory(tmp.resolve("build"));
javac(build, dst);
Path explodedJar = Files.createDirectory(tmp.resolve("exploded-jar"));
Path services = Files.createDirectories(explodedJar.resolve("META-INF")
.resolve("services"));
Path s = services.resolve("java.net.ContentHandlerFactory");
try (FileWriter fw = new FileWriter(s.toFile())) {
fw.write(factoryClassFqn);
}
javac(explodedJar, dst1);
jar(tmp.resolve("test.jar"), explodedJar);
Files.copy(tmp.resolve("test.jar"), build.resolve("test.jar"));
Result r = java(emptyMap(), asList(build.resolve("test.jar"), build), "Test");
if (r.exitValue == 0 || !verifyOutput(r.output, factoryClassFqn))
throw new RuntimeException(r.output);
}
private static void step3_UserDefined() throws IOException {
String packagePrefix = "net.java.openjdk.test";
String fqn = packagePrefix + ".text.plain";
Path tmp = Files.createDirectory(Paths.get("ContentHandlersTest-3"));
Path src = templatesHome().resolve("test.template");
Path dst = tmp.resolve("Test.java");
Files.copy(src, dst);
Path dst1 = fromTemplate(templatesHome().resolve("plain.template"),
fqn, tmp);
Path build = Files.createDirectory(tmp.resolve("build"));
javac(build, dst);
Path classes = Files.createDirectory(tmp.resolve("classes"));
javac(classes, dst1);
Map<String, String> m = singletonMap("java.content.handler.pkgs", packagePrefix);
Result r = java(m, asList(build, classes), "Test");
if (r.exitValue != 0 || !r.output.contains(fqn))
throw new RuntimeException(r.output);
}
private static void step4_BuiltIn() throws IOException {
Path tmp = Files.createDirectory(Paths.get("ContentHandlersTest-4"));
Path src = templatesHome().resolve("test.template");
Path dst = tmp.resolve("Test.java");
Files.copy(src, dst);
Path build = Files.createDirectory(tmp.resolve("build"));
javac(build, dst);
Result r = java(emptyMap(), singleton(build), "Test");
if (r.exitValue != 0 || !r.output.contains("sun.net.www.content.text.PlainTextInputStream"))
throw new RuntimeException(r.output);
}
private static String stackTraceStringForBrokenFactory(String fqn) {
return "Exception in thread \"main\" java.lang.RuntimeException: " +
"This is a broken factory. It is supposed to throw this exception.";
}
private static Path fromTemplate(Path srcTemplate,
String factoryFqn,
Path dstFolder) throws IOException {
String factorySimpleName, packageName;
int i = factoryFqn.lastIndexOf('.');
if (i < 0) {
packageName = "";
factorySimpleName = factoryFqn;
} else {
packageName = factoryFqn.substring(0, i);
factorySimpleName = factoryFqn.substring(i + 1);
}
Path result = dstFolder.resolve(factorySimpleName + ".java");
File dst = result.toFile();
File src = srcTemplate.toFile();
try (BufferedReader r = new BufferedReader(new FileReader(src));
BufferedWriter w = new BufferedWriter(new FileWriter(dst))) {
List<String> lines = processTemplate(packageName, factorySimpleName,
r.lines()).collect(Collectors.toList());
Iterator<String> it = lines.iterator();
if (it.hasNext())
w.write(it.next());
while (it.hasNext()) {
w.newLine();
w.write(it.next());
}
}
return result;
}
private static Stream<String> processTemplate(String packageName,
String factorySimpleName,
Stream<String> lines) {
Function<String, String> pckg;
if (packageName.isEmpty()) {
pckg = s -> s.contains("$package") ? "" : s;
} else {
pckg = s -> s.replaceAll("\\$package", packageName);
}
Function<String, String> factory
= s -> s.replaceAll("\\$className", factorySimpleName);
return lines.map(pckg).map(factory);
}
// IMO, that's the easiest way that gives you a fair amount of confidence in
// that j.u.ServiceLoader is loading a factory rather than Class.forName
private static boolean verifyOutput(String output, String fqn) {
String s1 = String.format("java.util.ServiceConfigurationError: " +
"java.net.ContentHandlerFactory: " +
"Provider %s could not be instantiated", fqn);
return output.contains(s1);
}
private 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));
}
private static void javac(Path compilationOutput, Path... sourceFiles) {
String javac = getJDKTool("javac");
List<String> commands = new ArrayList<>();
commands.addAll(asList(javac, "-d", compilationOutput.toString()));
List<Path> paths = asList(sourceFiles);
commands.addAll(paths.stream()
.map(Path::toString)
.collect(Collectors.toList()));
quickFail(run(new ProcessBuilder(commands)));
}
private static void quickFail(Result r) {
if (r.exitValue != 0)
throw new RuntimeException(r.output);
}
private static Result java(Map<String, String> properties,
Collection<Path> classpath,
String classname, String... args) {
String java = getJDKTool("java");
List<String> commands = new ArrayList<>();
commands.add(java);
commands.addAll(properties.entrySet()
.stream()
.map(e -> "-D" + e.getKey() + "=" + e.getValue())
.collect(Collectors.toList()));
String cp = classpath.stream()
.map(Path::toString)
.collect(Collectors.joining(File.pathSeparator));
commands.add("-cp");
commands.add(cp);
commands.add(classname);
commands.addAll(Arrays.asList(args));
return run(new ProcessBuilder(commands));
}
private static Result run(ProcessBuilder b) {
Process p;
try {
p = b.start();
} catch (IOException e) {
throw new RuntimeException(
format("Couldn't start process '%s'", b.command()), e);
}
String output;
try {
output = toString(p.getInputStream(), p.getErrorStream());
} catch (IOException e) {
throw new RuntimeException(
format("Couldn't read process output '%s'", b.command()), e);
}
try {
p.waitFor();
} catch (InterruptedException e) {
throw new RuntimeException(
format("Process hasn't finished '%s'", b.command()), e);
}
return new Result(p.exitValue(), output);
}
private static String getJDKTool(String name) {
String testJdk = System.getProperty("test.jdk");
if (testJdk == null)
throw new RuntimeException("Please provide test.jdk property at a startup");
return testJdk + File.separator + "bin" + File.separator + name;
}
private static Path templatesHome() {
String testSrc = System.getProperty("test.src");
if (testSrc == null)
throw new RuntimeException("Please provide test.src property at a startup");
return Paths.get(testSrc);
}
private 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();
}
private 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) {
}
}
}
}
private static class Result {
final int exitValue;
final String output;
private Result(int exitValue, String output) {
this.exitValue = exitValue;
this.output = output;
}
}
}

View File

@ -0,0 +1,40 @@
/*
* 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.ContentHandler;
import java.net.ContentHandlerFactory;
public class $className implements ContentHandlerFactory {
public $className() {
throw new RuntimeException(
"This is a broken factory. It is supposed to throw this exception.");
}
@Override
public ContentHandler createContentHandler(String mimetype) {
throw new RuntimeException( "This method is not supposed to be called.");
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.ContentHandler;
import java.net.ContentHandlerFactory;
public class $className implements ContentHandlerFactory {
@Override
public ContentHandler createContentHandler(String mimetype) {
throw new RuntimeException(
"This is a broken factory. It is supposed to throw this exception.");
}
}

View File

@ -0,0 +1,37 @@
/*
* 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 $package;
import java.io.IOException;
import java.net.ContentHandler;
import java.net.URLConnection;
public final class $className extends ContentHandler {
@Override
public Object getContent(URLConnection urlc) throws IOException {
return this;
}
}

View File

@ -0,0 +1,79 @@
/*
* 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.ByteArrayInputStream;
import java.io.InputStream;
import java.net.ContentHandlerFactory;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
public class Test {
public static void main(String[] args) throws Exception {
if (args.length > 0) {
String fqn = args[0];
@SuppressWarnings("unchecked")
Class<? extends ContentHandlerFactory> c =
(Class<? extends ContentHandlerFactory>) Class.forName(fqn);
ContentHandlerFactory f = c.newInstance();
URLConnection.setContentHandlerFactory(f);
}
// One does not simply use a ContentHandler...
// From an end user perspective ContentHandler is used indirectly
// and it's more like SPI rather than API. So there's a certain amount
// of preparations needs to be done beforehand.
URLStreamHandlerFactory streamHandlerFactory =
(protocol) ->
new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL u) {
return newUrlConnection(u);
}
};
URL.setURLStreamHandlerFactory(streamHandlerFactory);
// Finally
Object content = new URL("whatever:").getContent();
System.out.println("Content class is: " + content.getClass());
}
private static URLConnection newUrlConnection(URL u) {
return new URLConnection(u) {
@Override public void connect() { }
@Override
public InputStream getInputStream() { return null; }
@Override public String getContentType() { return "text/plain"; }
};
}
}