You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by pe...@apache.org on 2013/04/01 09:47:45 UTC
svn commit: r1463106 [2/4] - in /river/jtsk/skunk/qa_refactor/trunk: ./ qa/
qa/jtreg/net/jini/loader/pref/PreferredClassProvider/registryRetainCodebase/
qa/jtreg/net/jini/loader/pref/PreferredResources/correctInterpretation/
qa/src/com/sun/jini/test/im...
Added: river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/URLClassLoader.java
URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/URLClassLoader.java?rev=1463106&view=auto
==============================================================================
--- river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/URLClassLoader.java (added)
+++ river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/URLClassLoader.java Mon Apr 1 07:47:44 2013
@@ -0,0 +1,1330 @@
+/*
+ * 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.river.api.net;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.ObjectStreamException;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.JarURLConnection;
+import java.net.MalformedURLException;
+import java.net.SocketPermission;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.net.URLStreamHandlerFactory;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.PrivilegedAction;
+import java.security.SecureClassLoader;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.Manifest;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.river.impl.Messages;
+
+/**
+ * This class loader is responsible for loading classes and resources from a
+ * list of URLs which can refer to either directories or JAR files. Classes
+ * loaded by this {@code URLClassLoader} are granted permission to access the
+ * URLs contained in the URL search list.
+ * <p>
+ * Unlike java.net.URLClassLoader, CodeSource equality is based on Certificate
+ * and Uri location equality, not URL.equals().
+ * <p>
+ * This allows implementors of {@link java.rmi.Remote} to do two things:
+ * <ol>
+ * <li>Utilise replication of codebase servers or mirrors.</li>
+ * <li>Use different domain names to ensure separation of proxy classes that
+ * otherwise utilise identical jar files</li>
+ * </ol>
+ *
+ */
+public class URLClassLoader extends java.net.URLClassLoader {
+
+ private final List<URL> originalUrls; // Copy on Write
+
+ private final List<URL> searchList; // Synchronized
+
+ /* synchronize on handlerList for all access to handlerList and handlerMap */
+ private final List<URLHandler> handlerList;
+ private final Map<Uri, URLHandler> handlerMap = new HashMap<Uri, URLHandler>();
+
+ private final URLStreamHandlerFactory factory;
+
+ private volatile AccessControlContext currentContext;
+
+ static class SubURLClassLoader extends URLClassLoader {
+ // The subclass that overwrites the loadClass() method
+ private boolean checkingPackageAccess = false;
+
+ SubURLClassLoader(URL[] urls) {
+ super(urls, ClassLoader.getSystemClassLoader());
+ }
+
+ SubURLClassLoader(URL[] urls, ClassLoader parent) {
+ super(urls, parent);
+ }
+
+ /**
+ * Overrides the {@code loadClass()} of {@code ClassLoader}. It calls
+ * the security manager's {@code checkPackageAccess()} before
+ * attempting to load the class.
+ *
+ * @return the Class object.
+ * @param className
+ * String the name of the class to search for.
+ * @param resolveClass
+ * boolean indicates if class should be resolved after
+ * loading.
+ * @throws ClassNotFoundException
+ * If the class could not be found.
+ */
+ @Override
+ protected synchronized Class<?> loadClass(String className,
+ boolean resolveClass) throws ClassNotFoundException {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null && !checkingPackageAccess) {
+ int index = className.lastIndexOf('.');
+ if (index > 0) { // skip if class is from a default package
+ try {
+ checkingPackageAccess = true;
+ sm.checkPackageAccess(className.substring(0, index));
+ } finally {
+ checkingPackageAccess = false;
+ }
+ }
+ }
+ return super.loadClass(className, resolveClass);
+ }
+ }
+
+ static class IndexFile {
+
+ private final HashMap<String, ArrayList<URL>> map;
+ //private URLClassLoader host;
+
+
+ static IndexFile readIndexFile(JarFile jf, JarEntry indexEntry, URL url) {
+ BufferedReader in = null;
+ InputStream is = null;
+ try {
+ // Add mappings from resource to jar file
+ String parentURLString = getParentURL(url).toExternalForm();
+ String prefix = "jar:" //$NON-NLS-1$
+ + parentURLString + "/"; //$NON-NLS-1$
+ is = jf.getInputStream(indexEntry);
+ in = new BufferedReader(new InputStreamReader(is, "UTF8"));
+ HashMap<String, ArrayList<URL>> pre_map = new HashMap<String, ArrayList<URL>>();
+ // Ignore the 2 first lines (index version)
+ if (in.readLine() == null) return null;
+ if (in.readLine() == null) return null;
+ TOP_CYCLE:
+ while (true) {
+ String line = in.readLine();
+ if (line == null) {
+ break;
+ }
+ URL jar = new URL(prefix + line + "!/"); //$NON-NLS-1$
+ while (true) {
+ line = in.readLine();
+ if (line == null) {
+ break TOP_CYCLE;
+ }
+ if ("".equals(line)) {
+ break;
+ }
+ ArrayList<URL> list;
+ if (pre_map.containsKey(line)) {
+ list = pre_map.get(line);
+ } else {
+ list = new ArrayList<URL>();
+ pre_map.put(line, list);
+ }
+ list.add(jar);
+ }
+ }
+ if (!pre_map.isEmpty()) {
+ return new IndexFile(pre_map);
+ }
+ } catch (MalformedURLException e) {
+ // Ignore this jar's index
+ } catch (IOException e) {
+ // Ignore this jar's index
+ }
+ finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException e) {
+ }
+ }
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ return null;
+ }
+
+ private static URL getParentURL(URL url) throws IOException {
+ URL fileURL = ((JarURLConnection) url.openConnection()).getJarFileURL();
+ String file = fileURL.getFile();
+ String parentFile = new File(file).getParent();
+ parentFile = parentFile.replace(File.separatorChar, '/');
+ if (parentFile.charAt(0) != '/') {
+ parentFile = "/" + parentFile; //$NON-NLS-1$
+ }
+ URL parentURL = new URL(fileURL.getProtocol(), fileURL
+ .getHost(), fileURL.getPort(), parentFile);
+ return parentURL;
+ }
+
+ public IndexFile(HashMap<String, ArrayList<URL>> map) {
+ // Don't need to defensively copy map, it's created for and only
+ // used here.
+ this.map = map;
+ }
+
+ ArrayList<URL> get(String name) {
+ synchronized (map){
+ return map.get(name);
+ }
+ }
+ }
+
+ class URLHandler {
+ final URL url;
+ final URL codeSourceUrl;
+
+ public URLHandler(URL url) {
+ this.url = url;
+ this.codeSourceUrl = url;
+ }
+
+ public URLHandler(URL url, URL codeSourceUrl){
+ this.url = url;
+ this.codeSourceUrl = codeSourceUrl;
+ }
+
+ void findResources(String name, ArrayList<URL> resources) {
+ URL res = findResource(name);
+ if (res != null && !resources.contains(res)) {
+ resources.add(res);
+ }
+ }
+
+ Class<?> findClass(String packageName, String name, String origName) {
+ URL resURL = targetURL(url, name);
+ if (resURL != null) {
+ try {
+ InputStream is = resURL.openStream();
+ return createClass(is, packageName, origName);
+ } catch (IOException e) {
+ }
+ }
+ return null;
+ }
+
+
+ Class<?> createClass(InputStream is, String packageName, String origName) {
+ if (is == null) {
+ return null;
+ }
+ byte[] clBuf = null;
+ try {
+ clBuf = getBytes(is);
+ } catch (IOException e) {
+ return null;
+ } finally {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ if (packageName != null) {
+ String packageDotName = packageName.replace('/', '.');
+ Package packageObj = getPackage(packageDotName);
+ if (packageObj == null) {
+ definePackage(packageDotName, null, null,
+ null, null, null, null, null);
+ } else {
+ if (packageObj.isSealed()) {
+ throw new SecurityException(Messages
+ .getString("luni.A1")); //$NON-NLS-1$
+ }
+ }
+ }
+ return defineClass(origName, clBuf, 0, clBuf.length, new UriCodeSource(codeSourceUrl, (Certificate[]) null, null));
+ }
+
+ URL findResource(String name) {
+ URL resURL = targetURL(url, name);
+ if (resURL != null) {
+ try {
+ URLConnection uc = resURL.openConnection();
+ uc.getInputStream().close();
+ // HTTP can return a stream on a non-existent file
+ // So check for the return code;
+ if (!resURL.getProtocol().equals("http")) { //$NON-NLS-1$
+ return resURL;
+ }
+ int code;
+ if ((code = ((HttpURLConnection) uc).getResponseCode()) >= 200
+ && code < 300) {
+ return resURL;
+ }
+ } catch (SecurityException e) {
+ return null;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ return null;
+ }
+
+ URL targetURL(URL base, String name) {
+ try {
+ String file = base.getFile() + URIEncoderDecoder.quoteIllegal(name,
+ "/@" + Uri.someLegal);
+
+ return new URL(base.getProtocol(), base.getHost(), base.getPort(),
+ file, null);
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ } catch (MalformedURLException e) {
+ return null;
+ }
+ }
+
+ }
+
+ class URLJarHandler extends URLHandler {
+ private final JarFile jf;
+ private final String prefixName;
+ private final IndexFile index;
+ private final Map<Uri, URLHandler> subHandlers = new HashMap<Uri, URLHandler>();
+
+ public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName) {
+ super(url, jarURL);
+ this.jf = jf;
+ this.prefixName = prefixName;
+ final JarEntry je = jf.getJarEntry("META-INF/INDEX.LIST"); //$NON-NLS-1$
+ this.index = (je == null ? null : IndexFile.readIndexFile(jf, je, url));
+ }
+
+ public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName, IndexFile index) {
+ super(url, jarURL);
+ this.jf = jf;
+ this.prefixName = prefixName;
+ this.index = index;
+ }
+
+ IndexFile getIndex() {
+ return index;
+ }
+
+ @Override
+ void findResources(String name, ArrayList<URL> resources) {
+ URL res = findResourceInOwn(name);
+ if (res != null && !resources.contains(res)) {
+ resources.add(res);
+ }
+ if (index != null) {
+ int pos = name.lastIndexOf("/"); //$NON-NLS-1$
+ // only keep the directory part of the resource
+ // as index.list only keeps track of directories and root files
+ String indexedName = (pos > 0) ? name.substring(0, pos) : name;
+ ArrayList<URL> urls = index.get(indexedName);
+ if (urls != null) {
+ synchronized (urls){
+ urls.remove(url);
+ urls = (ArrayList<URL>) urls.clone(); // Defensive copy to avoid sync
+ }
+ for (URL url : urls) {
+ URLHandler h = getSubHandler(url);
+ if (h != null) {
+ h.findResources(name, resources);
+ }
+ }
+ }
+ }
+
+ }
+
+ @Override
+ Class<?> findClass(String packageName, String name, String origName) {
+ String entryName = prefixName + name;
+ JarEntry entry = jf.getJarEntry(entryName);
+ if (entry != null) {
+ /**
+ * Avoid recursive load class, especially the class
+ * is an implementation class of security provider
+ * and the jar is signed.
+ */
+ try {
+ Manifest manifest = jf.getManifest();
+ return createClass(entry, manifest, packageName, origName);
+ } catch (IOException e) {
+ }
+ }
+ if (index != null) {
+ ArrayList<URL> urls;
+ if (packageName == null) {
+ urls = index.get(name);
+ } else {
+ urls = index.get(packageName);
+ }
+ if (urls != null) {
+ synchronized (urls){
+ urls.remove(url);
+ urls = (ArrayList<URL>) urls.clone(); // Defensive copy.
+ }
+ for (URL url : urls) {
+ URLHandler h = getSubHandler(url);
+ if (h != null) {
+ Class<?> res = h.findClass(packageName, name, origName);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) {
+ InputStream is = null;
+ byte[] clBuf = null;
+ try {
+ is = jf.getInputStream(entry);
+ clBuf = getBytes(is);
+ } catch (IOException e) {
+ return null;
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException e) {
+ }
+ }
+ }
+ if (packageName != null) {
+ String packageDotName = packageName.replace('/', '.');
+ Package packageObj = getPackage(packageDotName);
+ if (packageObj == null) {
+ if (manifest != null) {
+ definePackage(packageDotName, manifest,
+ codeSourceUrl);
+ } else {
+ definePackage(packageDotName, null, null,
+ null, null, null, null, null);
+ }
+ } else {
+ boolean exception = packageObj.isSealed();
+ if (manifest != null) {
+ if (isSealed(manifest, packageName + "/")) {
+ exception = !packageObj
+ .isSealed(codeSourceUrl);
+ }
+ }
+ if (exception) {
+ throw new SecurityException(Messages
+ .getString("luni.A1", packageName)); //$NON-NLS-1$
+ }
+ }
+ }
+ CodeSource codeS = new UriCodeSource(codeSourceUrl, entry.getCertificates(),null);
+ return defineClass(origName, clBuf, 0, clBuf.length, codeS);
+ }
+
+ URL findResourceInOwn(String name) {
+ String entryName = prefixName + name;
+ if (jf.getEntry(entryName) != null) {
+ return targetURL(url, name);
+ }
+ return null;
+ }
+
+ @Override
+ URL findResource(String name) {
+ URL res = findResourceInOwn(name);
+ if (res != null) {
+ return res;
+ }
+ if (index != null) {
+ int pos = name.lastIndexOf("/"); //$NON-NLS-1$
+ // only keep the directory part of the resource
+ // as index.list only keeps track of directories and root files
+ String indexedName = (pos > 0) ? name.substring(0, pos) : name;
+ ArrayList<URL> urls = index.get(indexedName);
+ if (urls != null) {
+ synchronized (urls){
+ urls.remove(url);
+ urls = (ArrayList<URL>) urls.clone(); // Defensive copy.
+ }
+ for (URL url : urls) {
+ URLHandler h = getSubHandler(url);
+ if (h != null) {
+ res = h.findResource(name);
+ if (res != null) {
+ return res;
+ }
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private URLHandler getSubHandler(URL url) {
+ Uri key = null;
+ try {
+ key = Uri.urlToUri(url);
+ } catch (URISyntaxException ex) {
+ Logger.getLogger(URLClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ synchronized (subHandlers){
+ URLHandler sub = subHandlers.get(key);
+ if (sub != null) {
+ return sub;
+ }
+ String protocol = url.getProtocol();
+ if (protocol.equals("jar")) { //$NON-NLS-1$
+ sub = createURLJarHandler(url);
+ } else if (protocol.equals("file")) { //$NON-NLS-1$
+ sub = createURLSubJarHandler(url);
+ } else {
+ sub = createURLHandler(url);
+ }
+ if (sub != null && key != null) {
+ subHandlers.put(key, sub);
+ }
+ return sub;
+ }
+ }
+
+ private URLHandler createURLSubJarHandler(URL url) {
+ String prefixName;
+ String file = url.getFile();
+ if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
+ prefixName = "";
+ } else {
+ int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$
+ if (sepIdx == -1) {
+ // Invalid URL, don't look here again
+ return null;
+ }
+ sepIdx += 2;
+ prefixName = file.substring(sepIdx);
+ }
+ try {
+ URL jarURL = ((JarURLConnection) url
+ .openConnection()).getJarFileURL();
+ JarURLConnection juc = (JarURLConnection) new URL(
+ "jar", "", //$NON-NLS-1$ //$NON-NLS-2$
+ jarURL.toExternalForm() + "!/").openConnection(); //$NON-NLS-1$
+ JarFile jf = juc.getJarFile();
+ URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName, null);
+ // TODO : to think what we should do with indexes & manifest.class file here
+ return jarH;
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ }
+
+ class URLFileHandler extends URLHandler {
+ private final String prefix;
+
+ public URLFileHandler(URL url) {
+ super(url);
+ String baseFile = url.getFile();
+ String host = url.getHost();
+ int hostLength = 0;
+ if (host != null) {
+ hostLength = host.length();
+ }
+ StringBuilder buf = new StringBuilder(2 + hostLength
+ + baseFile.length());
+ if (hostLength > 0) {
+ buf.append("//").append(host); //$NON-NLS-1$
+ }
+ // baseFile always ends with '/'
+ buf.append(baseFile);
+ prefix = buf.toString();
+ }
+
+ @Override
+ Class<?> findClass(String packageName, String name, String origName) {
+ String filename = prefix + name;
+ try {
+ filename = URLDecoder.decode(filename, "UTF-8"); //$NON-NLS-1$
+ } catch (IllegalArgumentException e) {
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+
+ File file = new File(filename);
+ if (file.exists()) {
+ try {
+ InputStream is = new FileInputStream(file);
+ return createClass(is, packageName, origName);
+ } catch (FileNotFoundException e) {
+ }
+ }
+ return null;
+ }
+
+ @Override
+ URL findResource(String name) {
+ int idx = 0;
+ String filename;
+
+ // Do not create a UNC path, i.e. \\host
+ while (idx < name.length() &&
+ ((name.charAt(idx) == '/') || (name.charAt(idx) == '\\'))) {
+ idx++;
+ }
+
+ if (idx > 0) {
+ name = name.substring(idx);
+ }
+
+ try {
+ filename = URLDecoder.decode(prefix, "UTF-8") + name; //$NON-NLS-1$
+
+ if (new File(filename).exists()) {
+ return targetURL(url, name);
+ }
+ return null;
+ } catch (IllegalArgumentException e) {
+ return null;
+ } catch (UnsupportedEncodingException e) {
+ // must not happen
+ throw new AssertionError(e);
+ }
+ }
+
+ }
+
+ /**
+ * To avoid CodeSource equals and hashCode methods in SecureClassLoader keys.
+ *
+ * CodeSource uses DNS lookup calls to check location IP addresses are
+ * equal.
+ *
+ * This class must not be serialized.
+ */
+ private static class UriCodeSource extends CodeSource {
+ private static final long serialVersionUID = 1L;
+ private final Uri uri;
+ private final int hashCode;
+
+ UriCodeSource(URL url, Certificate [] certs, Collection<Permission> perms){
+ super(url, certs);
+ Uri uri = null;
+ try {
+ uri = Uri.urlToUri(url);
+ } catch (URISyntaxException ex) { }//Ignore
+ this.uri = uri;
+ int hash = 7;
+ hash = 23 * hash + (this.uri != null ? this.uri.hashCode() : 0);
+ hash = 23 * hash + (certs != null ? certs.hashCode() : 0);
+ hashCode = hash;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean equals(Object o){
+ if (!(o instanceof UriCodeSource)) return false;
+ if (uri == null) return super.equals(o);
+ UriCodeSource that = (UriCodeSource) o;
+ if ( !uri.equals(that.uri)) return false;
+ Certificate [] mine = getCertificates();
+ Certificate [] theirs = that.getCertificates();
+ if ( mine == null && theirs == null) return true;
+ if ( mine == null && theirs != null) return false;
+ if ( mine != null && theirs == null) return false;
+ if (Arrays.asList(getCertificates()).equals(Arrays.asList(that.getCertificates()))) return true;
+ return false;
+ }
+
+ Object writeReplace() throws ObjectStreamException {
+ return new CodeSource(getLocation(), getCertificates());
+ }
+
+ }
+
+
+ /**
+ * Constructs a new {@code URLClassLoader} instance. The newly created
+ * instance will have the system ClassLoader as its parent. URLs that end
+ * with "/" are assumed to be directories, otherwise they are assumed to be
+ * JAR files.
+ *
+ * @param urls
+ * the list of URLs where a specific class or file could be
+ * found.
+ * @throws SecurityException
+ * if a security manager exists and its {@code
+ * checkCreateClassLoader()} method doesn't allow creation of
+ * new ClassLoaders.
+ */
+ public URLClassLoader(URL[] urls) {
+ this(urls, ClassLoader.getSystemClassLoader(), null);
+ }
+
+ /**
+ * Constructs a new URLClassLoader instance. The newly created instance will
+ * have the system ClassLoader as its parent. URLs that end with "/" are
+ * assumed to be directories, otherwise they are assumed to be JAR files.
+ *
+ * @param urls
+ * the list of URLs where a specific class or file could be
+ * found.
+ * @param parent
+ * the class loader to assign as this loader's parent.
+ * @throws SecurityException
+ * if a security manager exists and its {@code
+ * checkCreateClassLoader()} method doesn't allow creation of
+ * new class loaders.
+ */
+ public URLClassLoader(URL[] urls, ClassLoader parent) {
+ this(urls, parent, null);
+ }
+
+ /**
+ * Adds the specified URL to the search list.
+ *
+ * @param url
+ * the URL which is to add.
+ */
+ protected void addURL(URL url) {
+ try {
+ originalUrls.add(url);
+ searchList.add(createSearchURL(url));
+ } catch (MalformedURLException e) {
+ }
+ }
+
+ /**
+ * Returns all known URLs which point to the specified resource.
+ *
+ * @param name
+ * the name of the requested resource.
+ * @return the enumeration of URLs which point to the specified resource.
+ * @throws IOException
+ * if an I/O error occurs while attempting to connect.
+ */
+ @Override
+ public Enumeration<URL> findResources(final String name) throws IOException {
+ ArrayList<URL> result = AccessController.doPrivileged(
+ new PrivilegedAction<ArrayList<URL>>() {
+ public ArrayList<URL> run() {
+ ArrayList<URL> results = new ArrayList<URL>();
+ findResourcesImpl(name, results);
+ return results;
+ }
+ }, currentContext);
+ SecurityManager sm;
+ int length = result.size();
+ if (length > 0 && (sm = System.getSecurityManager()) != null) {
+ ArrayList<URL> reduced = new ArrayList<URL>(length);
+ for (int i = 0; i < length; i++) {
+ URL url = result.get(i);
+ try {
+ sm.checkPermission(url.openConnection().getPermission());
+ reduced.add(url);
+ } catch (IOException e) {
+ } catch (SecurityException e) {
+ }
+ }
+ result = reduced;
+ }
+ return Collections.enumeration(result);
+ }
+
+ void findResourcesImpl(String name, ArrayList<URL> result) {
+ if (name == null) {
+ return;
+ }
+ int n = 0;
+ while (true) {
+ URLHandler handler = getHandler(n++);
+ if (handler == null) {
+ break;
+ }
+ handler.findResources(name, result);
+ }
+ }
+
+
+ /**
+ * Converts an input stream into a byte array.
+ *
+ * @param is
+ * the input stream
+ * @return byte[] the byte array
+ */
+ private static byte[] getBytes(InputStream is)
+ throws IOException {
+ byte[] buf = new byte[4096];
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(4096);
+ int count;
+ while ((count = is.read(buf)) > 0) {
+ bos.write(buf, 0, count);
+ }
+ return bos.toByteArray();
+ }
+
+ /**
+ * Gets all permissions for the specified {@code codesource}. First, this
+ * method retrieves the permissions from the system policy. If the protocol
+ * is "file:/" then a new permission, {@code FilePermission}, granting the
+ * read permission to the file is added to the permission collection.
+ * Otherwise, connecting to and accepting connections from the URL is
+ * granted.
+ *
+ * @param codesource
+ * the code source object whose permissions have to be known.
+ * @return the list of permissions according to the code source object.
+ */
+ @Override
+ protected PermissionCollection getPermissions(final CodeSource codesource) {
+ PermissionCollection pc = super.getPermissions(codesource);
+ URL u = codesource.getLocation();
+ if (u.getProtocol().equals("jar")) { //$NON-NLS-1$
+ try {
+ // Create a URL for the resource the jar refers to
+ u = ((JarURLConnection) u.openConnection()).getJarFileURL();
+ } catch (IOException e) {
+ // This should never occur. If it does continue using the jar
+ // URL
+ }
+ }
+ if (u.getProtocol().equals("file")) { //$NON-NLS-1$
+ String path = u.getFile();
+ String host = u.getHost();
+ if (host != null && host.length() > 0) {
+ path = "//" + host + path; //$NON-NLS-1$
+ }
+
+ if (File.separatorChar != '/') {
+ path = path.replace('/', File.separatorChar);
+ }
+ if (isDirectory(u)) {
+ pc.add(new FilePermission(path + "-", "read")); //$NON-NLS-1$ //$NON-NLS-2$
+ } else {
+ pc.add(new FilePermission(path, "read")); //$NON-NLS-1$
+ }
+ } else {
+ String host = u.getHost();
+ if (host.length() == 0) {
+ host = "localhost"; //$NON-NLS-1$
+ }
+ pc.add(new SocketPermission(host, "connect, accept")); //$NON-NLS-1$
+ }
+ return pc;
+ }
+
+ /**
+ * Returns the search list of this {@code URLClassLoader}.
+ *
+ * @return the list of all known URLs of this instance.
+ */
+ public URL[] getURLs() {
+ return originalUrls.toArray(new URL[originalUrls.size()]);
+ }
+
+ /**
+ * Determines if the URL is pointing to a directory.
+ */
+ private static boolean isDirectory(URL url) {
+ String file = url.getFile();
+ return (file.length() > 0 && file.charAt(file.length() - 1) == '/');
+ }
+
+ /**
+ * Returns a new {@code URLClassLoader} instance for the given URLs and the
+ * system {@code ClassLoader} as its parent. The method {@code loadClass()}
+ * of the new instance will call {@code
+ * SecurityManager.checkPackageAccess()} before loading a class.
+ *
+ * @param urls
+ * the list of URLs that is passed to the new {@code
+ * URLClassloader}.
+ * @return the created {@code URLClassLoader} instance.
+ */
+ public static URLClassLoader newInstance(final URL[] urls) {
+ URLClassLoader sub = AccessController
+ .doPrivileged(new PrivilegedAction<URLClassLoader>() {
+ public URLClassLoader run() {
+ return new SubURLClassLoader(urls);
+ }
+ });
+ sub.currentContext = AccessController.getContext();
+ return sub;
+ }
+
+ /**
+ * Returns a new {@code URLClassLoader} instance for the given URLs and the
+ * specified {@code ClassLoader} as its parent. The method {@code
+ * loadClass()} of the new instance will call the SecurityManager's {@code
+ * checkPackageAccess()} before loading a class.
+ *
+ * @param urls
+ * the list of URLs that is passed to the new URLClassloader.
+ * @param parentCl
+ * the parent class loader that is passed to the new
+ * URLClassloader.
+ * @return the created {@code URLClassLoader} instance.
+ */
+ public static URLClassLoader newInstance(final URL[] urls,
+ final ClassLoader parentCl) {
+ URLClassLoader sub = AccessController
+ .doPrivileged(new PrivilegedAction<URLClassLoader>() {
+ public URLClassLoader run() {
+ return new SubURLClassLoader(urls, parentCl);
+ }
+ });
+ sub.currentContext = AccessController.getContext();
+ return sub;
+ }
+
+ /**
+ * Constructs a new {@code URLClassLoader} instance. The newly created
+ * instance will have the specified {@code ClassLoader} as its parent and
+ * use the specified factory to create stream handlers. URLs that end with
+ * "/" are assumed to be directories, otherwise they are assumed to be JAR
+ * files.
+ *
+ * @param searchUrls
+ * the list of URLs where a specific class or file could be
+ * found.
+ * @param parent
+ * the {@code ClassLoader} to assign as this loader's parent.
+ * @param factory
+ * the factory that will be used to create protocol-specific
+ * stream handlers.
+ * @throws SecurityException
+ * if a security manager exists and its {@code
+ * checkCreateClassLoader()} method doesn't allow creation of
+ * new {@code ClassLoader}s.
+ */
+ public URLClassLoader(URL[] searchUrls, ClassLoader parent,
+ URLStreamHandlerFactory factory) {
+ super(searchUrls, parent, factory); // ClassLoader protectes against finalizer attack.
+ this.factory = factory;
+ // capture the context of the thread that creates this URLClassLoader
+ currentContext = AccessController.getContext();
+ int nbUrls = searchUrls.length;
+ List<URL> originalUrls = new ArrayList<URL>(nbUrls);
+ handlerList = new ArrayList<URLHandler>(nbUrls);
+ searchList = Collections.synchronizedList(new LinkedList<URL>());
+ for (int i = 0; i < nbUrls; i++) {
+ originalUrls.add(searchUrls[i]);
+ try {
+ searchList.add(createSearchURL(searchUrls[i]));
+ } catch (MalformedURLException e) {
+ }
+ }
+ this.originalUrls = new CopyOnWriteArrayList<URL>(originalUrls);
+ }
+
+ /**
+ * Tries to locate and load the specified class using the known URLs. If the
+ * class could be found, a class object representing the loaded class will
+ * be returned.
+ *
+ * @param clsName
+ * the name of the class which has to be found.
+ * @return the class that has been loaded.
+ * @throws ClassNotFoundException
+ * if the specified class cannot be loaded.
+ */
+ @Override
+ protected Class<?> findClass(final String clsName)
+ throws ClassNotFoundException {
+ Class<?> cls = AccessController.doPrivileged(
+ new PrivilegedAction<Class<?>>() {
+ public Class<?> run() {
+ return findClassImpl(clsName);
+ }
+ }, currentContext);
+ if (cls != null) {
+ return cls;
+ }
+ throw new ClassNotFoundException(clsName);
+ }
+
+ /**
+ * Returns an URL that will be checked if it contains the class or resource.
+ * If the file component of the URL is not a directory, a Jar URL will be
+ * created.
+ *
+ * @return java.net.URL a test URL
+ */
+ private URL createSearchURL(URL url) throws MalformedURLException {
+ if (url == null) {
+ return url;
+ }
+
+ String protocol = url.getProtocol();
+
+ if (isDirectory(url) || protocol.equals("jar")) { //$NON-NLS-1$
+ return url;
+ }
+ if (factory == null) {
+ return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$
+ -1, url.toString() + "!/"); //$NON-NLS-1$
+ }
+ // use jar protocol as the stream handler protocol
+ return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$
+ -1, url.toString() + "!/", //$NON-NLS-1$
+ factory.createURLStreamHandler("jar"));//$NON-NLS-1$
+ }
+
+ /**
+ * Returns an URL referencing the specified resource or {@code null} if the
+ * resource could not be found.
+ *
+ * @param name
+ * the name of the requested resource.
+ * @return the URL which points to the given resource.
+ */
+ @Override
+ public URL findResource(final String name) {
+ if (name == null) {
+ return null;
+ }
+ URL result = AccessController.doPrivileged(new PrivilegedAction<URL>() {
+ public URL run() {
+ return findResourceImpl(name);
+ }
+ }, currentContext);
+ SecurityManager sm;
+ if (result != null && (sm = System.getSecurityManager()) != null) {
+ try {
+ sm.checkPermission(result.openConnection().getPermission());
+ } catch (IOException e) {
+ return null;
+ } catch (SecurityException e) {
+ return null;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Returns a URL among the given ones referencing the specified resource or
+ * null if no resource could be found.
+ *
+ * @param resName java.lang.String the name of the requested resource
+ * @return URL URL for the resource.
+ */
+ URL findResourceImpl(String resName) {
+ int n = 0;
+
+ while (true) {
+ URLHandler handler = getHandler(n++);
+ if (handler == null) {
+ break;
+ }
+ URL res = handler.findResource(resName);
+ if (res != null) {
+ return res;
+ }
+ }
+ return null;
+ }
+
+ URLHandler getHandler(int num) {
+ synchronized (handlerList){
+ if (num < handlerList.size()) {
+ return handlerList.get(num);
+ }
+
+ makeNewHandler();
+ if (num < handlerList.size()) {
+ return handlerList.get(num);
+ }
+ return null;
+ }
+ }
+
+ // synchronize on handlerList.
+ private void makeNewHandler() {
+ while (!searchList.isEmpty()) {
+ URL nextCandidate = searchList.remove(0);
+ if (nextCandidate == null) { // luni.94=One of urls is null
+ throw new NullPointerException(Messages.getString("luni.94")); //$NON-NLS-1$
+ }
+ Uri candidateKey = null;
+ try {
+ candidateKey = Uri.urlToUri(nextCandidate);
+ } catch (URISyntaxException ex) {
+ Logger.getLogger(URLClassLoader.class.getName()).log(Level.SEVERE, null, ex);
+ }
+ if (!handlerMap.containsKey(candidateKey)) {
+ URLHandler result;
+ String protocol = nextCandidate.getProtocol();
+ if (protocol.equals("jar")) { //$NON-NLS-1$
+ result = createURLJarHandler(nextCandidate);
+ } else if (protocol.equals("file")) { //$NON-NLS-1$
+ result = createURLFileHandler(nextCandidate);
+ } else {
+ result = createURLHandler(nextCandidate);
+ }
+ if (result != null) {
+ handlerMap.put(candidateKey, result);
+ handlerList.add(result);
+ return;
+ }
+ }
+ }
+ }
+
+ private URLHandler createURLHandler(URL url) {
+ return new URLHandler(url);
+ }
+
+ private URLHandler createURLFileHandler(URL url) {
+ return new URLFileHandler(url);
+ }
+
+ private URLHandler createURLJarHandler(URL url) {
+ String prefixName;
+ String file = url.getFile();
+ if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
+ prefixName = "";
+ } else {
+ int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$
+ if (sepIdx == -1) {
+ // Invalid URL, don't look here again
+ return null;
+ }
+ sepIdx += 2;
+ prefixName = file.substring(sepIdx);
+ }
+ try {
+ URL jarURL = ((JarURLConnection) url
+ .openConnection()).getJarFileURL();
+ JarURLConnection juc = (JarURLConnection) new URL(
+ "jar", "", //$NON-NLS-1$ //$NON-NLS-2$
+ jarURL.toExternalForm() + "!/").openConnection(); //$NON-NLS-1$
+ JarFile jf = juc.getJarFile();
+ URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName);
+
+ if (jarH.getIndex() == null) {
+ try {
+ Manifest manifest = jf.getManifest();
+ if (manifest != null) {
+ String classpath = manifest.getMainAttributes().getValue(
+ Attributes.Name.CLASS_PATH);
+ if (classpath != null) {
+ searchList.addAll(0, getInternalURLs(url, classpath));
+ }
+ }
+ } catch (IOException e) {
+ }
+ }
+ return jarH;
+ } catch (IOException e) {
+ }
+ return null;
+ }
+
+ /**
+ * Defines a new package using the information extracted from the specified
+ * manifest.
+ *
+ * @param packageName
+ * the name of the new package.
+ * @param manifest
+ * the manifest containing additional information for the new
+ * package.
+ * @param url
+ * the URL to the code source for the new package.
+ * @return the created package.
+ * @throws IllegalArgumentException
+ * if a package with the given name already exists.
+ */
+ protected Package definePackage(String packageName, Manifest manifest,
+ URL url) throws IllegalArgumentException {
+ Attributes mainAttributes = manifest.getMainAttributes();
+ String dirName = packageName.replace('.', '/') + "/"; //$NON-NLS-1$
+ Attributes packageAttributes = manifest.getAttributes(dirName);
+ boolean noEntry = false;
+ if (packageAttributes == null) {
+ noEntry = true;
+ packageAttributes = mainAttributes;
+ }
+ String specificationTitle = packageAttributes
+ .getValue(Attributes.Name.SPECIFICATION_TITLE);
+ if (specificationTitle == null && !noEntry) {
+ specificationTitle = mainAttributes
+ .getValue(Attributes.Name.SPECIFICATION_TITLE);
+ }
+ String specificationVersion = packageAttributes
+ .getValue(Attributes.Name.SPECIFICATION_VERSION);
+ if (specificationVersion == null && !noEntry) {
+ specificationVersion = mainAttributes
+ .getValue(Attributes.Name.SPECIFICATION_VERSION);
+ }
+ String specificationVendor = packageAttributes
+ .getValue(Attributes.Name.SPECIFICATION_VENDOR);
+ if (specificationVendor == null && !noEntry) {
+ specificationVendor = mainAttributes
+ .getValue(Attributes.Name.SPECIFICATION_VENDOR);
+ }
+ String implementationTitle = packageAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
+ if (implementationTitle == null && !noEntry) {
+ implementationTitle = mainAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
+ }
+ String implementationVersion = packageAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ if (implementationVersion == null && !noEntry) {
+ implementationVersion = mainAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
+ }
+ String implementationVendor = packageAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
+ if (implementationVendor == null && !noEntry) {
+ implementationVendor = mainAttributes
+ .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
+ }
+
+ return definePackage(packageName, specificationTitle,
+ specificationVersion, specificationVendor, implementationTitle,
+ implementationVersion, implementationVendor, isSealed(manifest,
+ dirName) ? url : null);
+ }
+
+ private boolean isSealed(Manifest manifest, String dirName) {
+ Attributes mainAttributes = manifest.getMainAttributes();
+ String value = mainAttributes.getValue(Attributes.Name.SEALED);
+ boolean sealed = value != null && value.toLowerCase(Locale.getDefault()).equals("true"); //$NON-NLS-1$
+ Attributes attributes = manifest.getAttributes(dirName);
+ if (attributes != null) {
+ value = attributes.getValue(Attributes.Name.SEALED);
+ if (value != null) {
+ sealed = value.toLowerCase(Locale.getDefault()).equals("true"); //$NON-NLS-1$
+ }
+ }
+ return sealed;
+ }
+
+ /**
+ * returns URLs referenced in the string classpath.
+ *
+ * @param root
+ * the jar URL that classpath is related to
+ * @param classpath
+ * the relative URLs separated by spaces
+ * @return URL[] the URLs contained in the string classpath.
+ */
+ private ArrayList<URL> getInternalURLs(URL root, String classpath) {
+ // Class-path attribute is composed of space-separated values.
+ StringTokenizer tokenizer = new StringTokenizer(classpath);
+ ArrayList<URL> addedURLs = new ArrayList<URL>();
+ String file = root.getFile();
+ int jarIndex = file.lastIndexOf("!/") - 1; //$NON-NLS-1$
+ int index = file.lastIndexOf("/", jarIndex) + 1; //$NON-NLS-1$
+ if (index == 0) {
+ index = file.lastIndexOf(
+ System.getProperty("file.separator"), jarIndex) + 1; //$NON-NLS-1$
+ }
+ file = file.substring(0, index);
+ while (tokenizer.hasMoreElements()) {
+ String element = tokenizer.nextToken();
+ if (!element.equals("")) { //$NON-NLS-1$
+ try {
+ // Take absolute path case into consideration
+ URL url = new URL(new URL(file), element);
+ addedURLs.add(createSearchURL(url));
+ } catch (MalformedURLException e) {
+ // Nothing is added
+ }
+ }
+ }
+ return addedURLs;
+ }
+
+ Class<?> findClassImpl(String className) {
+ String partialName = className.replace('.', '/');
+ final String classFileName = new StringBuilder(partialName).append(".class").toString(); //$NON-NLS-1$
+ String packageName = null;
+ int position = partialName.lastIndexOf('/');
+ if ((position = partialName.lastIndexOf('/')) != -1) {
+ packageName = partialName.substring(0, position);
+ }
+ int n = 0;
+ while (true) {
+ URLHandler handler = getHandler(n++);
+ if (handler == null) {
+ break;
+ }
+ Class<?> res = handler.findClass(packageName, classFileName, className);
+ if (res != null) {
+ return res;
+ }
+ }
+ return null;
+
+ }
+
+}
Propchange: river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/URLClassLoader.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/Uri.java
URL: http://svn.apache.org/viewvc/river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/Uri.java?rev=1463106&r1=1463105&r2=1463106&view=diff
==============================================================================
--- river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/Uri.java (original)
+++ river/jtsk/skunk/qa_refactor/trunk/src/org/apache/river/api/net/Uri.java Mon Apr 1 07:47:44 2013
@@ -17,44 +17,277 @@
package org.apache.river.api.net;
+import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
+import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import org.apache.river.impl.Messages;
/**
* This class represents an immutable instance of a URI as defined by RFC 3986.
*
- * This class behaves similarly to java.net.URI and is a drop in replacement,
- * however all instances are normalised during construction, to comply with
- * RFC 3986.
+ * This class replaces java.net.URI functionality.
*
- * Normalisation of java.net.URI was limited to the path, the scheme
- * and host are also normalised in accordance with RFC 3986.
+ * Unlike java.net.URI this class is not Serializable and hashCode and
+ * equality is governed by strict RFC3986 normalisation. In addition "other"
+ * characters allowed in java.net.URI as specified by javadoc, not specifically
+ * allowed by RFC3986 are illegal and must be escaped. This strict adherence
+ * is essential to eliminate false negative or positive matches.
*
- * It also has some additional useful static methods that deal with common
- * scenario's.
- *
- * Unlike java.net.URI this class is not Serializable.
+ * In addition to RFC3896 normalisation, on OS platforms with a \ file separator
+ * the path is converted to UPPER CASE for comparison for file: schema, during
+ * equals and hashCode calls.
*
+ * IPv6 and IPvFuture host addresses must be enclosed in square brackets as per
+ * RFC3986.
*/
public final class Uri implements Comparable<Uri> {
- private static final long serialVersionUID = -6052424284110960213l;
-
+ /* Class Implementation */
+
+ /* Legacy java.net.URI RFC 2396 syntax*/
static final String unreserved = "_-!.~\'()*"; //$NON-NLS-1$
-
static final String punct = ",;:$&+="; //$NON-NLS-1$
-
static final String reserved = punct + "?/[]@"; //$NON-NLS-1$
-
+ // String someLegal = unreserved + punct;
+ // String queryLegal = unreserved + reserved + "\\\""; //$NON-NLS-1$
+ // String allLegal = unreserved + reserved;
+
static final String someLegal = unreserved + punct;
-
+
static final String queryLegal = unreserved + reserved + "\\\""; //$NON-NLS-1$
- static final String allLegal = unreserved + reserved;
+// static final String allLegal = unreserved + reserved;
+
+ /* RFC 3986 */
+// private static final char [] latin = new char[256];
+// private static final String [] latinEsc = new String[256];
+
+ /* 2.1. Percent-Encoding
+ *
+ * A percent-encoding mechanism is used to represent a data octet in a
+ * component when that octet's corresponding character is outside the
+ * allowed set or is being used as a delimiter of, or within, the
+ * component. A percent-encoded octet is encoded as a character
+ * triplet, consisting of the percent character "%" followed by the two
+ * hexadecimal digits representing that octet's numeric value. For
+ * example, "%20" is the percent-encoding for the binary octet
+ * "00100000" (ABNF: %x20), which in US-ASCII corresponds to the space
+ * character (SP). Section 2.4 describes when percent-encoding and
+ * decoding is applied.
+ *
+ * pct-encoded = "%" HEXDIG HEXDIG
+ *
+ * The uppercase hexadecimal digits 'A' through 'F' are equivalent to
+ * the lowercase digits 'a' through 'f', respectively. If two URIs
+ * differ only in the case of hexadecimal digits used in percent-encoded
+ * octets, they are equivalent. For consistency, URI producers and
+ * normalizers should use uppercase hexadecimal digits for all percent-
+ * encodings.
+ */
+ // Any character that is not part of the reserved and unreserved sets must
+ // be encoded.
+ // Section 2.1 Percent encoding must be converted to upper case during normalisation.
+ private static final char escape = '%';
+ /* RFC3986 obsoletes RFC2396 and RFC2732
+ *
+ * reserved = gen-delims / sub-delims
+ *
+ * gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@"
+ *
+ * sub-delims = "!" / "$" / "&" / "'" / "(" / ")"
+ * / "*" / "+" / "," / ";" / "="
+ */
+ // Section 2.2 Reserved set is protected from normalisation.
+// private static final char [] gen_delims = {':', '/', '?', '#', '[', ']', '@'};
+// private static final char [] sub_delims = {'!', '$', '&', '\'', '(', ')', '*',
+// '+', ',', ';', '='};
+ /*
+ * For consistency, percent-encoded octets in the ranges of ALPHA
+ * (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), period (%2E),
+ * underscore (%5F), or tilde (%7E) should not be created by URI
+ * producers and, when found in a URI, should be decoded to their
+ * corresponding unreserved characters by URI normalizers.
+ */
+ // Section 2.3 Unreserved characters (Allowed) must be decoded during normalisation if % encoded.
+// private static final char [] lowalpha = "abcdefghijklmnopqrstuvwxyz".toCharArray();
+// private static final char [] upalpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
+// private static final char [] numeric = "0123456789".toCharArray();
+// private static final char [] unres_punct = {'-' , '.' , '_' , '~'};
+
+ // Section 3.1 Scheme
+// private static final char [] schemeEx = "+-.".toCharArray(); // + ALPHA and numeric.
+
+ // To be unescaped during normalisation, unmodifiable and safely published.
+// final static Map<String, Character> unReserved;
+// final static Map<String, Character> schemeUnreserved;
+
+ /* Explicit legal String fields follow, ALPHA and DIGIT are implicitly legal */
+
+ /* All characters that are legal URI syntax */
+ static final String allLegalUnescaped = ":/?#[]@!$&'()*+,;=-._~";
+ static final String allLegal = "%:/?#[]@!$&'()*+,;=-._~";
+ /*
+ * Syntax Summary
+ *
+ * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
+ *
+ * hier-part = "//" authority path-abempty
+ * / path-absolute
+ * / path-rootless
+ * / path-empty
+ *
+ * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
+ */
+ static final String schemeLegal = "+-.";
+ /*
+ * authority = [ userinfo "@" ] host [ ":" port ]
+ * userinfo = *( unreserved / pct-encoded / sub-delims / ":" )
+ */
+ static final String userinfoLegal = "-._~!$&'()*+,;=:";
+ static final String authorityLegal = userinfoLegal + "@[]";
+ /* host = IP-literal / IPv4address / reg-name
+ * IP-literal = "[" ( IPv6address / IPvFuture ) "]"
+ * IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
+ */
+ static final String iPvFuture = "-._~!$&'()*+,;=:";
+ /* IPv6address = 6( h16 ":" ) ls32
+ * / "::" 5( h16 ":" ) ls32
+ * / [ h16 ] "::" 4( h16 ":" ) ls32
+ * / [ *1( h16 ":" ) h16 ] "::" 3( h16 ":" ) ls32
+ * / [ *2( h16 ":" ) h16 ] "::" 2( h16 ":" ) ls32
+ * / [ *3( h16 ":" ) h16 ] "::" h16 ":" ls32
+ * / [ *4( h16 ":" ) h16 ] "::" ls32
+ * / [ *5( h16 ":" ) h16 ] "::" h16
+ * / [ *6( h16 ":" ) h16 ] "::"
+ *
+ * ls32 = ( h16 ":" h16 ) / IPv4address
+ * ; least-significant 32 bits of address
+ *
+ * h16 = 1*4HEXDIG
+ * ; 16 bits of address represented in hexadecimal
+ *
+ * IPv4address = dec-octet "." dec-octet "." dec-octet "." dec-octet
+ *
+ * dec-octet = DIGIT ; 0-9
+ * / %x31-39 DIGIT ; 10-99
+ * / "1" 2DIGIT ; 100-199
+ * / "2" %x30-34 DIGIT ; 200-249
+ * / "25" %x30-35 ; 250-255
+ * reg-name = *( unreserved / pct-encoded / sub-delims )
+ */
+ static final String hostRegNameLegal = "-._~!$&'()*+,;=";
+ /* port = *DIGIT
+ *
+ * path = path-abempty ; begins with "/" or is empty
+ * / path-absolute ; begins with "/" but not "//"
+ * / path-noscheme ; begins with a non-colon segment
+ * / path-rootless ; begins with a segment
+ * / path-empty ; zero characters
+ *
+ * path-abempty = *( "/" segment )
+ * path-absolute = "/" [ segment-nz *( "/" segment ) ]
+ * path-noscheme = segment-nz-nc *( "/" segment )
+ * path-rootless = segment-nz *( "/" segment )
+ * path-empty = 0<pchar>
+ *
+ * segment = *pchar
+ * segment-nz = 1*pchar
+ * segment-nz-nc = 1*( unreserved / pct-encoded / sub-delims / "@" ) ; non-zero-length segment without any colon ":"
+ *
+ * pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
+ */
+ static final String pcharLegal = "-._~!$&'()*+,;=:@";
+ static final String segmentNzNcLegal = "-._~!$&'()*+,;=@";
+ static final String segmentLegal = pcharLegal;
+ static final String pathLegal = segmentLegal + "/";
+
+ /* query = *( pchar / "/" / "?" )
+ *
+ * fragment = *( pchar / "/" / "?" )
+ */
+ static final String queryFragLegal = pcharLegal + "/?";
+
+ /** Fixes windows file URI string by converting back slashes to forward
+ * slashes and inserting a forward slash before the drive letter if it is
+ * missing. No normalisation or modification of case is performed.
+ */
+ public static String fixWindowsURI(String uri) {
+ if (uri == null) return null;
+ if ( uri.startsWith("file:") || uri.startsWith("FILE:")){
+ char [] u = uri.toCharArray();
+ int l = u.length;
+ StringBuilder sb = new StringBuilder();
+ for (int i=0; i<l; i++){
+ // Ensure we use forward slashes
+ if (u[i] == File.separatorChar) {
+ sb.append('/');
+ continue;
+ }
+ if (i == 5 && uri.startsWith(":", 6 )) {
+ // Windows drive letter without leading slashes doesn't comply
+ // with URI spec, fix it here
+ sb.append("/");
+ }
+ sb.append(u[i]);
+ }
+ return sb.toString();
+ }
+ return uri;
+ }
+
+ public static URI uriToURI(Uri uri){
+ return URI.create(uri.toString());
+ }
+
+ public static Uri urlToUri(URL url) throws URISyntaxException{
+ return Uri.parseAndCreate(url.toString());
+ }
+
+ public static File uriToFile(Uri uri){
+ return new File(uriToURI(uri));
+ }
+
+ public static Uri fileToUri(File file) throws URISyntaxException{
+ String path = file.getAbsolutePath();
+ if (File.separatorChar == '\\') {
+ path = path.replace(File.separatorChar, '/');
+ }
+ return new Uri("file", null, path, null, null); //$NON-NLS-1$
+ }
+
+ public static Uri filePathToUri(String path) throws URISyntaxException{
+ String forwardSlash = "/";
+ if (path == null || path.length() == 0) {
+ // codebase is "file:"
+ path = "*";
+ }
+ // Ensure compatibility with URLClassLoader, when directory
+ // character is dropped by File.
+ boolean directory = false;
+ if (path.endsWith(forwardSlash)) directory = true;
+ path = new File(path).getAbsolutePath();
+ if (directory) {
+ if (!(path.endsWith(File.separator))){
+ path = path + File.separator;
+ }
+ }
+ if (File.separatorChar == '\\') {
+ path = path.replace(File.separatorChar, '/');
+ }
+ return new Uri("file", null, path, null, null); //$NON-NLS-1$
+ }
+
+ /* Begin Object Implementation */
private final String string;
private final String scheme;
@@ -71,6 +304,7 @@ public final class Uri implements Compar
private final boolean serverAuthority;
private final String hashString;
private final int hash;
+ private final boolean fileSchemeCaseInsensitiveOS;
/**
*
@@ -102,7 +336,8 @@ public final class Uri implements Compar
boolean opaque,
boolean absolute,
boolean serverAuthority,
- int hash)
+ int hash,
+ boolean fileSchemeCaseInsensitiveOS)
{
super();
this.scheme = scheme;
@@ -152,7 +387,7 @@ public final class Uri implements Compar
}
this.hashString = getHashString();
this.hash = hash == -1 ? hashString.hashCode(): hash;
-
+ this.fileSchemeCaseInsensitiveOS = fileSchemeCaseInsensitiveOS;
}
/**
@@ -176,23 +411,31 @@ public final class Uri implements Compar
p.opaque,
p.absolute,
p.serverAuthority,
- p.hash);
+ p.hash,
+ p.fileSchemeCaseInsensitiveOS);
}
/**
* Creates a new URI instance according to the given string {@code uri}.
*
+ * The URI must strictly conform to RFC3986, it doesn't support extended
+ * characters sets like java.net.URI, instead all non ASCII characters
+ * must be escaped.
+ *
+ * Any encoded unreserved characters are decoded.
+ *
* @param uri
* the textual URI representation to be parsed into a URI object.
* @throws URISyntaxException
* if the given string {@code uri} doesn't fit to the
- * specification RFC2396 or could not be parsed correctly.
+ * specification RF3986 or could not be parsed correctly.
*/
public Uri(String uri) throws URISyntaxException {
this(constructor1(uri));
}
private static UriParser constructor1(String uri) throws URISyntaxException {
+ uri = URIEncoderDecoder.decodeUnreserved(uri);
UriParser p = new UriParser();
p.parseURI(uri, false);
return p;
@@ -227,12 +470,12 @@ public final class Uri implements Compar
}
if (ssp != null) {
// QUOTE ILLEGAL CHARACTERS
- uri.append(quoteComponent(ssp, allLegal));
+ uri.append(quoteComponent(ssp, allLegalUnescaped));
}
if (frag != null) {
uri.append('#');
// QUOTE ILLEGAL CHARACTERS
- uri.append(quoteComponent(frag, allLegal));
+ uri.append(quoteComponent(frag, Uri.queryFragLegal));
}
UriParser p = new UriParser();
@@ -299,7 +542,7 @@ public final class Uri implements Compar
if (userinfo != null) {
// QUOTE ILLEGAL CHARACTERS in userinfo
- uri.append(quoteComponent(userinfo, someLegal));
+ uri.append(quoteComponent(userinfo, Uri.userinfoLegal));
uri.append('@');
}
@@ -320,19 +563,19 @@ public final class Uri implements Compar
if (path != null) {
// QUOTE ILLEGAL CHARS
- uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$
+ uri.append(quoteComponent(path, "/@" + Uri.pathLegal)); //$NON-NLS-1$
}
if (query != null) {
uri.append('?');
// QUOTE ILLEGAL CHARS
- uri.append(quoteComponent(query, allLegal));
+ uri.append(quoteComponent(query, Uri.queryFragLegal));
}
if (fragment != null) {
// QUOTE ILLEGAL CHARS
uri.append('#');
- uri.append(quoteComponent(fragment, allLegal));
+ uri.append(quoteComponent(fragment, Uri.queryFragLegal));
}
UriParser p = new UriParser();
@@ -406,22 +649,22 @@ public final class Uri implements Compar
if (authority != null) {
uri.append("//"); //$NON-NLS-1$
// QUOTE ILLEGAL CHARS
- uri.append(quoteComponent(authority, "@[]" + someLegal)); //$NON-NLS-1$
+ uri.append(quoteComponent(authority, "@[]" + Uri.authorityLegal)); //$NON-NLS-1$
}
if (path != null) {
// QUOTE ILLEGAL CHARS
- uri.append(quoteComponent(path, "/@" + someLegal)); //$NON-NLS-1$
+ uri.append(quoteComponent(path, "/@" + Uri.pathLegal)); //$NON-NLS-1$
}
if (query != null) {
// QUOTE ILLEGAL CHARS
uri.append('?');
- uri.append(quoteComponent(query, allLegal));
+ uri.append(quoteComponent(query, Uri.queryFragLegal));
}
if (fragment != null) {
// QUOTE ILLEGAL CHARS
uri.append('#');
- uri.append(quoteComponent(fragment, allLegal));
+ uri.append(quoteComponent(fragment, Uri.queryFragLegal));
}
UriParser p = new UriParser();
@@ -534,7 +777,12 @@ public final class Uri implements Compar
// authorities are the same
// compare paths
- ret = path.compareTo(uri.path);
+
+ if (fileSchemeCaseInsensitiveOS){
+ ret = path.toUpperCase(Locale.ENGLISH).compareTo(uri.path.toUpperCase(Locale.ENGLISH));
+ } else {
+ ret = path.compareTo(uri.path);
+ }
if (ret != 0) {
return ret;
}
@@ -592,15 +840,16 @@ public final class Uri implements Compar
/**
* The parameter string doesn't contain any existing escape sequences, any
- * escape character % found is encoded as %25.
+ * escape character % found is encoded as %25. Illegal characters are
+ * escaped if possible.
*
* The Uri is normalised according to RFC3986.
*
* @param unescapedString
* @return
*/
- public static Uri escapeAndCreate(String unescapedString){
- throw new UnsupportedOperationException("not supported");
+ public static Uri escapeAndCreate(String unescapedString) throws URISyntaxException{
+ return new Uri(quoteComponent(unescapedString, allLegalUnescaped));
}
/**
@@ -611,33 +860,16 @@ public final class Uri implements Compar
* @param nonCompliantEscapedString
* @return
*/
- public static Uri parseAndCreate(String nonCompliantEscapedString){
- throw new UnsupportedOperationException("not supported");
+ public static Uri parseAndCreate(String nonCompliantEscapedString) throws URISyntaxException{
+ return new Uri(quoteComponent(nonCompliantEscapedString, allLegal));
}
- // No point cloning an immutable object.
-// public Uri clone() {
-// return new Uri( string,
-// scheme,
-// schemespecificpart,
-// authority,
-// userinfo,
-// host,
-// port,
-// path,
-// query,
-// fragment,
-// opaque,
-// absolute,
-// serverAuthority,
-// hash);
-// }
/*
* Takes a string that may contain hex sequences like %F1 or %2b and
* converts the hex values following the '%' to lowercase
*/
- private String convertHexToLowerCase(String s) {
+ private String convertHexToUpperCase(String s) {
StringBuilder result = new StringBuilder(""); //$NON-NLS-1$
if (s.indexOf('%') == -1) {
return s;
@@ -646,7 +878,7 @@ public final class Uri implements Compar
int index = 0, previndex = 0;
while ((index = s.indexOf('%', previndex)) != -1) {
result.append(s.substring(previndex, index + 1));
- result.append(s.substring(index + 1, index + 3).toLowerCase());
+ result.append(s.substring(index + 1, index + 3).toUpperCase(Locale.ENGLISH));
index += 3;
previndex = index;
}
@@ -659,29 +891,33 @@ public final class Uri implements Compar
* occur in pairs as above
*/
private boolean equalsHexCaseInsensitive(String first, String second) {
- if (first.indexOf('%') != second.indexOf('%')) {
- return first.equals(second);
- }
-
- int index = 0, previndex = 0;
- while ((index = first.indexOf('%', previndex)) != -1
- && second.indexOf('%', previndex) == index) {
- boolean match = first.substring(previndex, index).equals(
- second.substring(previndex, index));
- if (!match) {
- return false;
- }
-
- match = first.substring(index + 1, index + 3).equalsIgnoreCase(
- second.substring(index + 1, index + 3));
- if (!match) {
- return false;
- }
-
- index += 3;
- previndex = index;
- }
- return first.substring(previndex).equals(second.substring(previndex));
+ //Hex will always be upper case.
+ if (first != null) return first.equals(second);
+ if (second != null) return false;
+ return true;
+// if (first.indexOf('%') != second.indexOf('%')) {
+// return first.equals(second);
+// }
+//
+// int index = 0, previndex = 0;
+// while ((index = first.indexOf('%', previndex)) != -1
+// && second.indexOf('%', previndex) == index) {
+// boolean match = first.substring(previndex, index).equals(
+// second.substring(previndex, index));
+// if (!match) {
+// return false;
+// }
+//
+// match = first.substring(index + 1, index + 3).equalsIgnoreCase(
+// second.substring(index + 1, index + 3));
+// if (!match) {
+// return false;
+// }
+//
+// index += 3;
+// previndex = index;
+// }
+// return first.substring(previndex).equals(second.substring(previndex));
}
/**
@@ -723,7 +959,13 @@ public final class Uri implements Compar
return equalsHexCaseInsensitive(uri.schemespecificpart,
schemespecificpart);
} else if (!uri.opaque && !opaque) {
- if (!equalsHexCaseInsensitive(path, uri.path)) {
+ if ( !(path != null && (path.equals(uri.path)
+ || fileSchemeCaseInsensitiveOS
+ // Upper case comparison required for Windows & VMS.
+ && path.toUpperCase(Locale.ENGLISH).equals(
+ uri.path.toUpperCase(Locale.ENGLISH)
+ ))))
+ {
return false;
}
@@ -972,7 +1214,8 @@ public final class Uri implements Compar
opaque,
absolute,
serverAuthority,
- hash);
+ hash,
+ fileSchemeCaseInsensitiveOS);
}
@@ -1147,7 +1390,8 @@ public final class Uri implements Compar
false,
false,
false,
- -1);
+ -1,
+ fileSchemeCaseInsensitiveOS);
}
/**
@@ -1183,7 +1427,8 @@ public final class Uri implements Compar
opaque,
absolute,
serverAuthority,
- hash);
+ hash,
+ fileSchemeCaseInsensitiveOS);
// no need to re-calculate the scheme specific part,
// since fragment is not part of scheme specific part.
@@ -1206,7 +1451,8 @@ public final class Uri implements Compar
relative.opaque,
absolute,
relative.serverAuthority,
- relative.hash);
+ relative.hash,
+ fileSchemeCaseInsensitiveOS);
} else {
// since relative URI has no authority,
// the resolved URI is very similar to this URI,
@@ -1230,7 +1476,8 @@ public final class Uri implements Compar
opaque,
absolute,
serverAuthority,
- hash);
+ hash,
+ fileSchemeCaseInsensitiveOS);
}
}
@@ -1329,7 +1576,7 @@ public final class Uri implements Compar
/*
* Form a string from the components of this URI, similarly to the
* toString() method. But this method converts scheme and host to lowercase,
- * and converts escaped octets to lowercase.
+ * and converts escaped octets to uppercase.
*
* Should convert octets to uppercase and follow platform specific
* normalization rules for file: uri.
@@ -1359,7 +1606,11 @@ public final class Uri implements Compar
}
if (path != null) {
- result.append(path);
+ if (fileSchemeCaseInsensitiveOS){
+ result.append(path.toUpperCase(Locale.ENGLISH));
+ } else {
+ result.append(path);
+ }
}
if (query != null) {
@@ -1373,7 +1624,7 @@ public final class Uri implements Compar
result.append(fragment);
}
- return convertHexToLowerCase(result.toString());
+ return convertHexToUpperCase(result.toString());
}
/**