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 }