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 }