package com.oracle.svm.core.jdk;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import org.graalvm.nativeimage.ImageSingletons;
import org.graalvm.nativeimage.Platform;
import org.graalvm.nativeimage.Platforms;
import org.graalvm.nativeimage.hosted.Feature;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.annotate.AutomaticFeature;
import com.oracle.svm.core.annotate.Delete;
import com.oracle.svm.core.annotate.Substitute;
import com.oracle.svm.core.annotate.TargetClass;
import com.oracle.svm.core.option.OptionUtils;
import com.oracle.svm.core.option.SubstrateOptionsParser;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.util.ReflectionUtil;
@TargetClass(java.net.URL.class)
final class Target_java_net_URL {
@Delete private static Hashtable<?, ?> handlers;
@Substitute
private static URLStreamHandler getURLStreamHandler(String protocol) throws MalformedURLException {
return JavaNetSubstitutions.getURLStreamHandler(protocol);
}
@Substitute
@SuppressWarnings("unused")
public static void setURLStreamHandlerFactory(URLStreamHandlerFactory fac) {
VMError.unsupportedFeature("Setting a custom URLStreamHandlerFactory.");
}
}
@AutomaticFeature
class JavaNetFeature implements Feature {
@Override
public void afterRegistration(AfterRegistrationAccess access) {
ImageSingletons.add(URLProtocolsSupport.class, new URLProtocolsSupport());
}
@Override
public void duringSetup(DuringSetupAccess access) {
JavaNetSubstitutions.defaultProtocols.forEach(protocol -> {
boolean registered = JavaNetSubstitutions.addURLStreamHandler(protocol);
VMError.guarantee(registered, "The URL protocol " + protocol + " is not available.");
});
for (String protocol : OptionUtils.flatten(",", SubstrateOptions.EnableURLProtocols.getValue())) {
if (JavaNetSubstitutions.defaultProtocols.contains(protocol)) {
printWarning("The URL protocol " + protocol + " is enabled by default. " +
"The option " + JavaNetSubstitutions.enableProtocolsOption + protocol + " is not needed.");
} else if (JavaNetSubstitutions.onDemandProtocols.contains(protocol)) {
boolean registered = JavaNetSubstitutions.addURLStreamHandler(protocol);
VMError.guarantee(registered, "The URL protocol " + protocol + " is not available.");
} else {
printWarning("The URL protocol " + protocol + " is not tested and might not work as expected." +
System.lineSeparator() + JavaNetSubstitutions.supportedProtocols());
boolean registered = JavaNetSubstitutions.addURLStreamHandler(protocol);
if (!registered) {
printWarning("Registering the " + protocol + " URL protocol failed. " +
"It will not be available at runtime." + System.lineSeparator());
}
}
}
}
private static void printWarning(String warningMessage) {
System.out.println(warningMessage);
}
}
class URLProtocolsSupport {
@Platforms(Platform.HOSTED_ONLY.class)
static void put(String protocol, URLStreamHandler urlStreamHandler) {
ImageSingletons.lookup(URLProtocolsSupport.class).imageHandlers.put(protocol, urlStreamHandler);
}
static URLStreamHandler get(String protocol) {
return ImageSingletons.lookup(URLProtocolsSupport.class).imageHandlers.get(protocol);
}
private final HashMap<String, URLStreamHandler> imageHandlers = new HashMap<>();
}
public final class JavaNetSubstitutions {
public static final String FILE_PROTOCOL = "file";
public static final String RESOURCE_PROTOCOL = "resource";
public static final String HTTP_PROTOCOL = "http";
public static final String HTTPS_PROTOCOL = "https";
static final List<String> defaultProtocols = Arrays.asList(FILE_PROTOCOL, RESOURCE_PROTOCOL);
static final List<String> onDemandProtocols = Arrays.asList(HTTP_PROTOCOL, HTTPS_PROTOCOL);
static final String enableProtocolsOption = SubstrateOptionsParser.commandArgument(SubstrateOptions.EnableURLProtocols, "");
@Platforms(Platform.HOSTED_ONLY.class)
static boolean addURLStreamHandler(String protocol) {
if (RESOURCE_PROTOCOL.equals(protocol)) {
final URLStreamHandler resourcesURLStreamHandler = createResourcesURLStreamHandler();
URLProtocolsSupport.put(RESOURCE_PROTOCOL, resourcesURLStreamHandler);
return true;
}
try {
URLStreamHandler handler = (URLStreamHandler) ReflectionUtil.lookupMethod(URL.class, "getURLStreamHandler", String.class).invoke(null, protocol);
if (handler != null) {
URLProtocolsSupport.put(protocol, handler);
return true;
}
return false;
} catch (ReflectiveOperationException ex) {
throw VMError.shouldNotReachHere(ex);
}
}
static URLStreamHandler getURLStreamHandler(String protocol) throws MalformedURLException {
URLStreamHandler result = URLProtocolsSupport.get(protocol);
if (result == null) {
if (onDemandProtocols.contains(protocol)) {
unsupported("Accessing an URL protocol that was not enabled. The URL protocol " + protocol +
" is supported but not enabled by default. It must be enabled by adding the " + enableProtocolsOption + protocol +
" option to the native-image command.");
} else {
unsupported("Accessing an URL protocol that was not enabled. The URL protocol " + protocol +
" is not tested and might not work as expected. It can be enabled by adding the " + enableProtocolsOption + protocol +
" option to the native-image command.");
}
}
return result;
}
static URLStreamHandler createResourcesURLStreamHandler() {
return new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new URLConnection(url) {
@Override
public void connect() throws IOException {
}
@Override
public InputStream getInputStream() throws IOException {
Resources.ResourcesSupport support = ImageSingletons.lookup(Resources.ResourcesSupport.class);
String resName = url.toString().substring(1 + JavaNetSubstitutions.RESOURCE_PROTOCOL.length());
final List<byte[]> bytes = support.resources.get(resName);
if (bytes == null || bytes.size() < 1) {
return null;
} else {
return new ByteArrayInputStream(bytes.get(0));
}
}
};
}
};
}
private static void unsupported(String message) throws MalformedURLException {
throw new MalformedURLException(message);
}
static String supportedProtocols() {
return "Supported URL protocols enabled by default: " + String.join(",", JavaNetSubstitutions.defaultProtocols) +
". Supported URL protocols available on demand: " + String.join(",", JavaNetSubstitutions.onDemandProtocols) + ".";
}
}