1 /*
   2  * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.istack.internal.tools;
  27 
  28 import java.io.InputStream;
  29 import java.io.ByteArrayOutputStream;
  30 import java.io.Closeable;
  31 import java.io.File;
  32 import java.io.IOException;
  33 import java.net.JarURLConnection;
  34 import java.net.URISyntaxException;
  35 import java.net.URL;
  36 import java.net.MalformedURLException;
  37 import java.net.URLConnection;
  38 import java.util.Collections;
  39 import java.util.Enumeration;
  40 import java.util.HashSet;
  41 import java.util.Set;
  42 import java.util.jar.JarFile;
  43 import java.util.logging.Level;
  44 import java.util.logging.Logger;
  45 
  46 /**
  47  * Load classes/resources from a side folder, so that
  48  * classes of the same package can live in a single jar file.
  49  *
  50  * <p>
  51  * For example, with the following jar file:
  52  * <pre>
  53  *  /
  54  *  +- foo
  55  *     +- X.class
  56  *  +- bar
  57  *     +- X.class
  58  * </pre>
  59  * <p>
  60  * {@link ParallelWorldClassLoader}("foo/") would load {@code X.class} from
  61  * {@code /foo/X.class} (note that X is defined in the root package, not
  62  * {@code foo.X}.
  63  *
  64  * <p>
  65  * This can be combined with  {@link MaskingClassLoader} to mask classes which are loaded by the parent
  66  * class loader so that the child class loader
  67  * classes living in different folders are loaded
  68  * before the parent class loader loads classes living the jar file publicly
  69  * visible
  70  * For example, with the following jar file:
  71  * <pre>
  72  *  /
  73  *  +- foo
  74  *     +- X.class
  75  *  +- bar
  76  *     +-foo
  77  *        +- X.class
  78  * </pre>
  79  * <p>
  80  * {@link ParallelWorldClassLoader}(MaskingClassLoader.class.getClassLoader())
  81  * would load {@code foo.X.class}  from
  82  * {@code /bar/foo.X.class} not the {@code foo.X.class}
  83  * in the publicly visible place in the jar file, thus
  84  * masking the parent classLoader from loading the class from {@code foo.X.class}
  85  * (note that X is defined in the  package foo, not
  86  * {@code bar.foo.X}.
  87  *
  88  * @author Kohsuke Kawaguchi
  89  */
  90 public class ParallelWorldClassLoader extends ClassLoader implements Closeable {
  91 
  92     /**
  93      * Strings like "prefix/", "abc/", or "" to indicate
  94      * classes should be loaded normally.
  95      */
  96     private final String prefix;
  97     private final Set<JarFile> jars;
  98 
  99     public ParallelWorldClassLoader(ClassLoader parent,String prefix) {
 100         super(parent);
 101         this.prefix = prefix;
 102         jars = Collections.synchronizedSet(new HashSet<JarFile>());
 103     }
 104 
 105     protected Class findClass(String name) throws ClassNotFoundException {
 106 
 107         StringBuffer sb = new StringBuffer(name.length()+prefix.length()+6);
 108         sb.append(prefix).append(name.replace('.','/')).append(".class");
 109 
 110         URL u  = getParent().getResource(sb.toString());
 111         if (u == null) {
 112             throw new ClassNotFoundException(name);
 113         }
 114 
 115         InputStream is = null;
 116         URLConnection con = null;
 117 
 118         try {
 119             con = u.openConnection();
 120             is = con.getInputStream();
 121         } catch (IOException ioe) {
 122             throw new ClassNotFoundException(name);
 123         }
 124 
 125         if (is==null)
 126             throw new ClassNotFoundException(name);
 127 
 128         try {
 129             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 130             byte[] buf = new byte[1024];
 131             int len;
 132             while((len=is.read(buf))>=0)
 133                 baos.write(buf,0,len);
 134 
 135             buf = baos.toByteArray();
 136             int packIndex = name.lastIndexOf('.');
 137             if (packIndex != -1) {
 138                 String pkgname = name.substring(0, packIndex);
 139                 // Check if package already loaded.
 140                 Package pkg = getPackage(pkgname);
 141                 if (pkg == null) {
 142                     definePackage(pkgname, null, null, null, null, null, null, null);
 143                 }
 144             }
 145             return defineClass(name,buf,0,buf.length);
 146         } catch (IOException e) {
 147             throw new ClassNotFoundException(name,e);
 148         } finally {
 149             try {
 150                 if (con != null && con instanceof JarURLConnection) {
 151                     jars.add(((JarURLConnection) con).getJarFile());
 152                 }
 153             } catch (IOException ioe) {
 154                 //ignore
 155             }
 156             if (is != null) {
 157                 try {
 158                     is.close();
 159                 } catch (IOException ioe) {
 160                     //ignore
 161                 }
 162             }
 163         }
 164     }
 165 
 166     @Override
 167     protected URL findResource(String name) {
 168         URL u = getParent().getResource(prefix + name);
 169         if (u != null) {
 170             try {
 171                 jars.add(new JarFile(new File(toJarUrl(u).toURI())));
 172             } catch (URISyntaxException ex) {
 173                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 174             } catch (IOException ex) {
 175                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 176             } catch (ClassNotFoundException ex) {
 177                 //ignore - not a jar
 178             }
 179         }
 180         return u;
 181     }
 182 
 183     @Override
 184     protected Enumeration<URL> findResources(String name) throws IOException {
 185         Enumeration<URL> en = getParent().getResources(prefix + name);
 186         while (en.hasMoreElements()) {
 187             try {
 188                 jars.add(new JarFile(new File(toJarUrl(en.nextElement()).toURI())));
 189             } catch (URISyntaxException ex) {
 190                 //should not happen
 191                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 192             } catch (IOException ex) {
 193                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 194             } catch (ClassNotFoundException ex) {
 195                 //ignore - not a jar
 196             }
 197         }
 198         return en;
 199     }
 200 
 201     public synchronized void close() throws IOException {
 202         for (JarFile jar : jars) {
 203             jar.close();
 204         }
 205     }
 206 
 207     /**
 208      * Given the URL inside jar, returns the URL to the jar itself.
 209      */
 210     public static URL toJarUrl(URL res) throws ClassNotFoundException, MalformedURLException {
 211         String url = res.toExternalForm();
 212         if(!url.startsWith("jar:"))
 213             throw new ClassNotFoundException("Loaded outside a jar "+url);
 214         url = url.substring(4); // cut off jar:
 215         url = url.substring(0,url.lastIndexOf('!'));    // cut off everything after '!'
 216         url = url.replaceAll(" ", "%20"); // support white spaces in path
 217         return new URL(url);
 218     }
 219 }