river-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From peter_firmst...@apache.org
Subject 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...
Date Mon, 01 Apr 2013 07:47:45 GMT
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());
     }
 
     /**



Mime
View raw message