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 <tt>X.class<tt> from
  61  * <tt>/foo/X.class</tt> (note that X is defined in the root package, not
  62  * <tt>foo.X</tt>.
  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()) would load <tt>foo.X.class<tt> from
  81  * <tt>/bar/foo.X.class</tt> not the <tt>foo.X.class<tt> in the publicly visible place in the jar file, thus
  82  * masking the parent classLoader from loading the class from  <tt>foo.X.class<tt>
  83  * (note that X is defined in the  package foo, not
  84  * <tt>bar.foo.X</tt>.
  85  *
  86  * @author Kohsuke Kawaguchi
  87  */
  88 public class ParallelWorldClassLoader extends ClassLoader implements Closeable {
  89 
  90     /**
  91      * Strings like "prefix/", "abc/", or "" to indicate
  92      * classes should be loaded normally.
  93      */
  94     private final String prefix;
  95     private final Set<JarFile> jars;
  96 
  97     public ParallelWorldClassLoader(ClassLoader parent,String prefix) {
  98         super(parent);
  99         this.prefix = prefix;
 100         jars = Collections.synchronizedSet(new HashSet<JarFile>());
 101     }
 102 
 103     protected Class findClass(String name) throws ClassNotFoundException {
 104 
 105         StringBuffer sb = new StringBuffer(name.length()+prefix.length()+6);
 106         sb.append(prefix).append(name.replace('.','/')).append(".class");
 107 
 108         URL u  = getParent().getResource(sb.toString());
 109         if (u == null) {
 110             throw new ClassNotFoundException(name);
 111         }
 112 
 113         InputStream is = null;
 114         URLConnection con = null;
 115 
 116         try {
 117             con = u.openConnection();
 118             is = con.getInputStream();
 119         } catch (IOException ioe) {
 120             throw new ClassNotFoundException(name);
 121         }
 122 
 123         if (is==null)
 124             throw new ClassNotFoundException(name);
 125 
 126         try {
 127             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 128             byte[] buf = new byte[1024];
 129             int len;
 130             while((len=is.read(buf))>=0)
 131                 baos.write(buf,0,len);
 132 
 133             buf = baos.toByteArray();
 134             int packIndex = name.lastIndexOf('.');
 135             if (packIndex != -1) {
 136                 String pkgname = name.substring(0, packIndex);
 137                 // Check if package already loaded.
 138                 Package pkg = getPackage(pkgname);
 139                 if (pkg == null) {
 140                     definePackage(pkgname, null, null, null, null, null, null, null);
 141                 }
 142             }
 143             return defineClass(name,buf,0,buf.length);
 144         } catch (IOException e) {
 145             throw new ClassNotFoundException(name,e);
 146         } finally {
 147             try {
 148                 if (con != null && con instanceof JarURLConnection) {
 149                     jars.add(((JarURLConnection) con).getJarFile());
 150                 }
 151             } catch (IOException ioe) {
 152                 //ignore
 153             }
 154             if (is != null) {
 155                 try {
 156                     is.close();
 157                 } catch (IOException ioe) {
 158                     //ignore
 159                 }
 160             }
 161         }
 162     }
 163 
 164     @Override
 165     protected URL findResource(String name) {
 166         URL u = getParent().getResource(prefix + name);
 167         if (u != null) {
 168             try {
 169                 jars.add(new JarFile(new File(toJarUrl(u).toURI())));
 170             } catch (URISyntaxException ex) {
 171                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 172             } catch (IOException ex) {
 173                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 174             } catch (ClassNotFoundException ex) {
 175                 //ignore - not a jar
 176             }
 177         }
 178         return u;
 179     }
 180 
 181     @Override
 182     protected Enumeration<URL> findResources(String name) throws IOException {
 183         Enumeration<URL> en = getParent().getResources(prefix + name);
 184         while (en.hasMoreElements()) {
 185             try {
 186                 jars.add(new JarFile(new File(toJarUrl(en.nextElement()).toURI())));
 187             } catch (URISyntaxException ex) {
 188                 //should not happen
 189                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 190             } catch (IOException ex) {
 191                 Logger.getLogger(ParallelWorldClassLoader.class.getName()).log(Level.WARNING, null, ex);
 192             } catch (ClassNotFoundException ex) {
 193                 //ignore - not a jar
 194             }
 195         }
 196         return en;
 197     }
 198 
 199     public synchronized void close() throws IOException {
 200         for (JarFile jar : jars) {
 201             jar.close();
 202         }
 203     }
 204 
 205     /**
 206      * Given the URL inside jar, returns the URL to the jar itself.
 207      */
 208     public static URL toJarUrl(URL res) throws ClassNotFoundException, MalformedURLException {
 209         String url = res.toExternalForm();
 210         if(!url.startsWith("jar:"))
 211             throw new ClassNotFoundException("Loaded outside a jar "+url);
 212         url = url.substring(4); // cut off jar:
 213         url = url.substring(0,url.lastIndexOf('!'));    // cut off everything after '!'
 214         url = url.replaceAll(" ", "%20"); // support white spaces in path
 215         return new URL(url);
 216     }
 217 }