| /* |
| |
| 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.apache.batik.util; |
| |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.batik.Version; |
| |
| /** |
| * A {@link java.net.URL}-like class that supports custom URI schemes |
| * and GZIP encoding. |
| * <p> |
| * This class is used as a replacement for {@link java.net.URL}. |
| * This is done for several reasons. First, unlike {@link java.net.URL} |
| * this class will accept and parse as much of a URL as possible, without |
| * throwing a {@link java.net.MalformedURLException}. This makes it useful |
| * for simply parsing a URL string (hence its name). |
| * </p> |
| * <p> |
| * Second, it allows for extension of the URI schemes supported by the |
| * parser. Batik uses this to support the |
| * <a href='http://www.ietf.org/rfc/rfc2397'><code>data:</code> URL scheme (RFC2397)</a>. |
| * </p> |
| * <p> |
| * Third, by default it checks the streams that it opens to see if they |
| * are GZIP compressed, and if so it automatically uncompresses them |
| * (avoiding opening the stream twice in the process). |
| * </p> |
| * <p> |
| * It is worth noting that most real work is defered to the |
| * {@link ParsedURLData} class to which most methods are forwarded. |
| * This is done because it allows a constructor interface to {@link ParsedURL} |
| * (mostly for compatability with core {@link URL}), in spite of the fact that |
| * the real implemenation uses the protocol handlers as factories for protocol |
| * specific instances of the {@link ParsedURLData} class. |
| * </p> |
| * |
| * @author <a href="mailto:deweese@apache.org">Thomas DeWeese</a> |
| * @version $Id$ |
| */ |
| public class ParsedURL { |
| |
| /** |
| * The data class we defer most things to. |
| */ |
| ParsedURLData data; |
| |
| /** |
| * The user agent to associate with this URL |
| */ |
| String userAgent; |
| |
| /** |
| * This maps between protocol names and ParsedURLProtocolHandler instances. |
| */ |
| private static Map handlersMap = null; |
| |
| /** |
| * The default protocol handler. This handler is used when |
| * other handlers fail or no match for a protocol can be |
| * found. |
| */ |
| private static ParsedURLProtocolHandler defaultHandler |
| = new ParsedURLDefaultProtocolHandler(); |
| |
| private static String globalUserAgent = "Batik/"+Version.getVersion(); |
| |
| public static String getGlobalUserAgent() { return globalUserAgent; } |
| |
| public static void setGlobalUserAgent(String userAgent) { |
| globalUserAgent = userAgent; |
| } |
| |
| /** |
| * Returns the shared instance of HandlersMap. This method is |
| * also responsible for initializing the handler map if this is |
| * the first time it has been requested since the class was |
| * loaded. |
| */ |
| private static synchronized Map getHandlersMap() { |
| if (handlersMap != null) return handlersMap; |
| |
| handlersMap = new HashMap(); |
| registerHandler(new ParsedURLDataProtocolHandler()); |
| registerHandler(new ParsedURLJarProtocolHandler()); |
| |
| Iterator iter = Service.providers(ParsedURLProtocolHandler.class); |
| while (iter.hasNext()) { |
| ParsedURLProtocolHandler handler; |
| handler = (ParsedURLProtocolHandler)iter.next(); |
| |
| // System.out.println("Handler: " + handler); |
| registerHandler(handler); |
| } |
| |
| |
| return handlersMap; |
| |
| } |
| |
| /** |
| * Returns the handler for a particular protocol. If protocol is |
| * <tt>null</tt> or no match is found in the handlers map it |
| * returns the default protocol handler. |
| * @param protocol The protocol to get a handler for. |
| */ |
| public static synchronized ParsedURLProtocolHandler getHandler |
| (String protocol) { |
| if (protocol == null) |
| return defaultHandler; |
| |
| Map handlers = getHandlersMap(); |
| ParsedURLProtocolHandler ret; |
| ret = (ParsedURLProtocolHandler)handlers.get(protocol); |
| if (ret == null) |
| ret = defaultHandler; |
| return ret; |
| } |
| |
| /** |
| * Registers a Protocol handler by adding it to the handlers map. |
| * If the given protocol handler returns <tt>null</tt> as it's |
| * supported protocol then it is registered as the default |
| * protocol handler. |
| * @param handler the new Protocol Handler to register |
| */ |
| public static synchronized void registerHandler |
| (ParsedURLProtocolHandler handler) { |
| if (handler.getProtocolHandled() == null) { |
| defaultHandler = handler; |
| return; |
| } |
| |
| Map handlers = getHandlersMap(); |
| handlers.put(handler.getProtocolHandled(), handler); |
| } |
| |
| /** |
| * This is a utility function others can call that checks if |
| * is is a GZIP stream if so it returns a GZIPInputStream that |
| * will decode the contents, otherwise it returns (or a |
| * buffered version of is) untouched. |
| * @param is Stream that may potentially be a GZIP stream. |
| */ |
| public static InputStream checkGZIP(InputStream is) |
| throws IOException { |
| return ParsedURLData.checkGZIP(is); |
| } |
| |
| /** |
| * Construct a ParsedURL from the given url string. |
| * @param urlStr The string to try and parse as a URL |
| */ |
| public ParsedURL(String urlStr) { |
| userAgent = getGlobalUserAgent(); |
| data = parseURL(urlStr); |
| } |
| |
| /** |
| * Construct a ParsedURL from the given java.net.URL instance. |
| * This is useful if you already have a valid java.net.URL |
| * instance. This bypasses most of the parsing and hence is |
| * quicker and less prone to reinterpretation than converting the |
| * URL to a string before construction. |
| * |
| * @param url The URL to "mimic". |
| */ |
| public ParsedURL(URL url) { |
| userAgent = getGlobalUserAgent(); |
| data = new ParsedURLData(url); |
| } |
| |
| /** |
| * Construct a sub URL from two strings. |
| * @param baseStr The 'parent' URL. Should be complete. |
| * @param urlStr The 'sub' URL may be complete or partial. |
| * the missing pieces will be taken from the baseStr. |
| */ |
| public ParsedURL(String baseStr, String urlStr) { |
| userAgent = getGlobalUserAgent(); |
| if (baseStr != null) |
| data = parseURL(baseStr, urlStr); |
| else |
| data = parseURL(urlStr); |
| } |
| |
| /** |
| * Construct a sub URL from a base URL and a string for the sub url. |
| * @param baseURL The 'parent' URL. |
| * @param urlStr The 'sub' URL may be complete or partial. |
| * the missing pieces will be taken from the baseURL. |
| */ |
| public ParsedURL(URL baseURL, String urlStr) { |
| userAgent = getGlobalUserAgent(); |
| |
| if (baseURL != null) |
| data = parseURL(new ParsedURL(baseURL), urlStr); |
| else |
| data = parseURL(urlStr); |
| } |
| |
| /** |
| * Construct a sub URL from a base ParsedURL and a string for the sub url. |
| * @param baseURL The 'parent' URL. |
| * @param urlStr The 'sub' URL may be complete or partial. |
| * the missing pieces will be taken from the baseURL. |
| */ |
| public ParsedURL(ParsedURL baseURL, String urlStr) { |
| if (baseURL != null) { |
| userAgent = baseURL.getUserAgent(); |
| data = parseURL(baseURL, urlStr); |
| } else { |
| data = parseURL(urlStr); |
| } |
| } |
| |
| /** |
| * Return a string rep of the URL (can be passed back into the |
| * constructor if desired). |
| */ |
| public String toString() { |
| return data.toString(); |
| } |
| |
| /** |
| * Implement Object.equals. |
| * Relies heavily on the contained ParsedURLData's implementation |
| * of equals. |
| */ |
| public boolean equals(Object obj) { |
| if (obj == null) return false; |
| if (! (obj instanceof ParsedURL)) |
| return false; |
| ParsedURL purl = (ParsedURL)obj; |
| return data.equals(purl.data); |
| } |
| |
| /** |
| * Implement Object.hashCode. |
| * Relies on the contained ParsedURLData's implementation |
| * of hashCode. |
| */ |
| public int hashCode() { |
| return data.hashCode(); |
| } |
| |
| /** |
| * Returns true if the URL looks well formed and complete. |
| * This does not garuntee that the stream can be opened but |
| * is a good indication that things aren't totally messed up. |
| */ |
| public boolean complete() { |
| return data.complete(); |
| } |
| |
| /** |
| * Return the user agent current associated with this url (or |
| * null if none). |
| */ |
| public String getUserAgent() { |
| return userAgent; |
| } |
| /** |
| * Sets the user agent associated with this url (null clears |
| * any associated user agent). |
| */ |
| public void setUserAgent(String userAgent) { |
| this.userAgent = userAgent; |
| } |
| |
| /** |
| * Returns the protocol for this URL. |
| * The protocol is everything upto the first ':'. |
| */ |
| public String getProtocol() { |
| if (data.protocol == null) return null; |
| return data.protocol; |
| } |
| |
| /** |
| * Returns the host for this URL, if any, <tt>null</tt> if there isn't |
| * one or it doesn't make sense for the protocol. |
| */ |
| public String getHost() { |
| if (data.host == null) return null; |
| return data.host; |
| } |
| |
| /** |
| * Returns the port on the host to connect to, if it was specified |
| * in the url that was parsed, otherwise returns -1. |
| */ |
| public int getPort() { return data.port; } |
| |
| /** |
| * Returns the path for this URL, if any (where appropriate for |
| * the protocol this also includes the file, not just directory). |
| * Note that getPath appears in JDK 1.3 as a synonym for getFile |
| * from JDK 1.2. |
| */ |
| public String getPath() { |
| if (data.path == null) return null; |
| return data.path; |
| } |
| |
| /** |
| * Returns the 'fragment' reference in the URL. |
| */ |
| public String getRef() { |
| if (data.ref == null) return null; |
| return data.ref; |
| } |
| |
| |
| /** |
| * Returns the URL up to and include the port number on |
| * the host. Does not include the path or fragment pieces. |
| */ |
| public String getPortStr() { |
| return data.getPortStr(); |
| } |
| |
| /** |
| * Returns the content type if available. This is only available |
| * for some protocols. |
| */ |
| public String getContentType() { |
| return data.getContentType(userAgent); |
| } |
| |
| /** |
| * Returns the content type's type/subtype, if available. This is |
| * only available for some protocols. |
| */ |
| public String getContentTypeMediaType() { |
| return data.getContentTypeMediaType(userAgent); |
| } |
| |
| /** |
| * Returns the content type's charset parameter, if available. This is |
| * only available for some protocols. |
| */ |
| public String getContentTypeCharset() { |
| return data.getContentTypeCharset(userAgent); |
| } |
| |
| /** |
| * Returns whether the Content-Type header has the given parameter. |
| */ |
| public boolean hasContentTypeParameter(String param) { |
| return data.hasContentTypeParameter(userAgent, param); |
| } |
| |
| /** |
| * Returns the content encoding if available. This is only available |
| * for some protocols. |
| */ |
| public String getContentEncoding() { |
| return data.getContentEncoding(userAgent); |
| } |
| |
| /** |
| * Attempt to open the stream checking for common compression |
| * types, and automatically decompressing them if found. |
| */ |
| public InputStream openStream() throws IOException { |
| return data.openStream(userAgent, null); |
| } |
| |
| /** |
| * Attempt to open the stream checking for common compression |
| * types, and automatically decompressing them if found. |
| * @param mimeType The expected mime type of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). |
| */ |
| public InputStream openStream(String mimeType) throws IOException { |
| List mt = new ArrayList(1); |
| mt.add(mimeType); |
| return data.openStream(userAgent, mt.iterator()); |
| } |
| |
| /** |
| * Attempt to open the stream checking for common compression |
| * types, and automatically decompressing them if found. |
| * @param mimeTypes The expected mime types of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). |
| */ |
| public InputStream openStream(String [] mimeTypes) throws IOException { |
| List mt = new ArrayList(mimeTypes.length); |
| for (int i=0; i<mimeTypes.length; i++) |
| mt.add(mimeTypes[i]); |
| return data.openStream(userAgent, mt.iterator()); |
| } |
| |
| /** |
| * Attempt to open the stream checking for common compression |
| * types, and automatically decompressing them if found. |
| * @param mimeTypes The expected mime types of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). The elements of |
| * the iterator must be strings. |
| */ |
| public InputStream openStream(Iterator mimeTypes) throws IOException { |
| return data.openStream(userAgent, mimeTypes); |
| } |
| |
| /** |
| * Attempt to open the stream, does no checking for compression |
| * types. |
| */ |
| public InputStream openStreamRaw() throws IOException { |
| return data.openStreamRaw(userAgent, null); |
| } |
| |
| /** |
| * Attempt to open the stream, does no checking for compression |
| * types. |
| * @param mimeType The expected mime type of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). |
| */ |
| public InputStream openStreamRaw(String mimeType) throws IOException { |
| List mt = new ArrayList(1); |
| mt.add(mimeType); |
| return data.openStreamRaw(userAgent, mt.iterator()); |
| } |
| |
| /** |
| * Attempt to open the stream, does no checking for comression |
| * types. |
| * @param mimeTypes The expected mime types of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). |
| */ |
| public InputStream openStreamRaw(String [] mimeTypes) throws IOException { |
| List mt = new ArrayList(mimeTypes.length); |
| for (int i=0; i<mimeTypes.length; i++) |
| mt.add(mimeTypes[i]); |
| return data.openStreamRaw(userAgent, mt.iterator()); |
| } |
| |
| /** |
| * Attempt to open the stream, does no checking for comression |
| * types. |
| * @param mimeTypes The expected mime types of the content |
| * in the returned InputStream (mapped to Http accept |
| * header among other possabilities). The elements of |
| * the iterator must be strings. |
| */ |
| public InputStream openStreamRaw(Iterator mimeTypes) throws IOException { |
| return data.openStreamRaw(userAgent, mimeTypes); |
| } |
| |
| public boolean sameFile(ParsedURL other) { |
| return data.sameFile(other.data); |
| } |
| |
| |
| /** |
| * Parse out the protocol from a url string. Used internally to |
| * select the proper handler, all other parsing is done by |
| * the selected protocol handler. |
| */ |
| protected static String getProtocol(String urlStr) { |
| if (urlStr == null) return null; |
| int idx = 0, len = urlStr.length(); |
| |
| if (len == 0) return null; |
| |
| // Protocol is only allowed to include -+.a-zA-Z |
| // So as soon as we hit something else we know we |
| // are done (if it is a ':' then we have protocol otherwise |
| // we don't. |
| char ch = urlStr.charAt(idx); |
| while ((ch == '-') || // todo this might be more efficient with a long mask |
| (ch == '+') || // which has a bit set for each valid char. |
| (ch == '.') || // check feasability |
| ((ch >= 'a') && (ch <= 'z')) || |
| ((ch >= 'A') && (ch <= 'Z'))) { |
| idx++; |
| if (idx == len) { |
| ch=0; |
| break; |
| } |
| ch = urlStr.charAt(idx); |
| } |
| if (ch == ':') { |
| // Has a protocol spec... |
| return urlStr.substring(0, idx).toLowerCase(); |
| } |
| return null; |
| } |
| |
| /** |
| * Factory method to construct an appropriate subclass of ParsedURLData |
| * @param urlStr the string to parse. |
| */ |
| public static ParsedURLData parseURL(String urlStr) { |
| ParsedURLProtocolHandler handler = getHandler(getProtocol(urlStr)); |
| return handler.parseURL(urlStr); |
| } |
| |
| /** |
| * Factory method to construct an appropriate subclass of ParsedURLData, |
| * for a sub url. |
| * @param baseStr The base URL string to parse. |
| * @param urlStr the sub URL string to parse. |
| */ |
| public static ParsedURLData parseURL(String baseStr, String urlStr) { |
| if (baseStr == null) |
| return parseURL(urlStr); |
| |
| ParsedURL purl = new ParsedURL(baseStr); |
| return parseURL(purl, urlStr); |
| } |
| |
| /** |
| * Factory method to construct an appropriate subclass of ParsedURLData, |
| * for a sub url. |
| * @param baseURL The base ParsedURL to parse. |
| * @param urlStr the sub URL string to parse. |
| */ |
| public static ParsedURLData parseURL(ParsedURL baseURL, String urlStr) { |
| if (baseURL == null) |
| return parseURL(urlStr); |
| |
| String protocol = getProtocol(urlStr); |
| if (protocol == null) |
| protocol = baseURL.getProtocol(); |
| ParsedURLProtocolHandler handler = getHandler(protocol); |
| return handler.parseURL(baseURL, urlStr); |
| } |
| } |