blob: 02c6a3a43ff517b35c87b4eb09682ddcfcaa6bdc [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.core.startup;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.URLStreamHandlerRegistration;
/** Stream handler for internal resource-based URLs.
* @author Jesse Glick
*/
@URLStreamHandlerRegistration(protocol={NbResourceStreamHandler.PROTOCOL_SYSTEM_RESOURCE, NbResourceStreamHandler.PROTOCOL_LOCALIZED_SYSTEM_RESOURCE})
public final class NbResourceStreamHandler extends URLStreamHandler {
public static final String PROTOCOL_SYSTEM_RESOURCE = "nbres"; // NOI18N
public static final String PROTOCOL_LOCALIZED_SYSTEM_RESOURCE = "nbresloc"; // NOI18N
public URLConnection openConnection(URL u) throws IOException {
if (u.getProtocol().equals(PROTOCOL_SYSTEM_RESOURCE)) {
return new Connection(u, false);
} else if (u.getProtocol().equals(PROTOCOL_LOCALIZED_SYSTEM_RESOURCE)) {
return new Connection(u, true);
} else {
throw new IOException("Bad protocol: " + u.getProtocol()); // NOI18N
}
}
private static class Connection extends URLConnection {
private final boolean localized;
// A real connection to delegate to. Non-null if successfully connected.
private URLConnection real;
private IOException exception = null;
public Connection(URL u, boolean localized) {
super(u);
this.localized = localized;
}
/** Tries to get a URL from this resource from the proper classloader,
* localizing first if requested.
* Also opens the URL to make a connection; this connection, <code>real</code>,
* will be delegated to for all operations.
*/
public synchronized void connect() throws IOException {
if (exception != null) {
// See tryToConnect().
IOException e = exception;
exception = null;
throw e;
}
if (! connected) {
String resource = url.getPath();
if (resource.length() > 0 && resource.charAt(0) == '/') { // NOI18N
resource = resource.substring(1);
} else {
Logger.getLogger(NbResourceStreamHandler.class.getName()).log(Level.WARNING, "URL path should begin with a slash: " + url);
}
ClassLoader loader = Lookup.getDefault().lookup(ClassLoader.class);
URL target;
URL t1 = loader.getResource(resource);
if (localized) {
// Find the suffix insertion point.
// XXX #29580: should have a shared API for this
int dotIndex = resource.lastIndexOf('.');
if (dotIndex < resource.lastIndexOf('/')) {
dotIndex = -1;
}
String base, ext;
if (dotIndex != -1) {
base = resource.substring(0, dotIndex);
ext = resource.substring(dotIndex);
} else {
base = resource;
ext = "";
}
target = null;
Iterator<String> suffixes = NbBundle.getLocalizingSuffixes();
while (suffixes.hasNext()) {
String suffix = suffixes.next();
target = "".equals(suffix)? t1: loader.getResource(base + suffix + ext);
if (target != null) {
break;
}
}
} else {
target = t1;
}
if (target == null) {
throw new IOException(NbBundle.getMessage(NbResourceStreamHandler.class, "EXC_nbres_cannot_connect", url));
}
real = target.openConnection();
real.connect();
connected = true;
}
}
/** Try to connect; but if it does not work, oh well.
* Ideally this would be quite unnecessary.
* Unfortunately much code, inclduing the Swing editor kits,
* gets header fields and so on without ever calling connect().
* These methods cannot even throw exceptions so it is a mess.
* E.g. if you display a nbres: URL in the ICE browser, it is fine:
* it calls connect() according to the specification, then
* getContentType() produces text/html as expected.
* But using the SwingBrowser default implementation, it goes
* ahead and calls getContentType() immediately. So we have
* to try to connect and get the right content type then too.
* This complicated the timing of error reporting.
*/
private void tryToConnect() {
if (connected || exception != null) return;
try {
connect();
} catch (IOException ioe) {
exception = ioe;
}
}
@Override
public String getHeaderField(int n) {
tryToConnect();
if (connected)
return real.getHeaderField(n);
else
return null;
}
@Override
public String getHeaderFieldKey(int n) {
tryToConnect();
if (connected)
return real.getHeaderFieldKey(n);
else
return null;
}
@Override
public String getHeaderField(String key) {
tryToConnect();
if (connected) {
return real.getHeaderField(key);
}
return null;
}
@Override
public InputStream getInputStream() throws IOException {
connect();
return real.getInputStream();
}
@Override
public OutputStream getOutputStream() throws IOException {
connect();
return real.getOutputStream();
}
// Should not be required, but they are:
@Override
public String getContentType() {
tryToConnect();
if (connected)
return real.getContentType();
else
return "application/octet-stream"; // NOI18N
}
@Override
public int getContentLength() {
tryToConnect();
if (connected)
return real.getContentLength();
else
return 0;
}
public @Override long getLastModified() {
if (connected && /* #96928 */ !real.getURL().toExternalForm().contains("http:")) { // NOI18N
return real.getLastModified();
}
return 0L;
}
// [PENDING] might be some more methods it would be useful to delegate, possibly
}
}