| /* |
| * 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 |
| |
| } |
| |
| } |