001/** 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.xbean.classloader; 018 019import java.io.IOException; 020import java.lang.ref.SoftReference; 021import java.net.URL; 022import java.net.URLStreamHandlerFactory; 023import java.util.ArrayList; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Collections; 027import java.util.Enumeration; 028import java.util.List; 029import java.util.Map; 030import java.util.concurrent.ConcurrentHashMap; 031 032/** 033 * A MultiParentClassLoader is a simple extension of the URLClassLoader that simply changes the single parent class 034 * loader model to support a list of parent class loaders. Each operation that accesses a parent, has been replaced 035 * with a operation that checks each parent in order. This getParent method of this class will always return null, 036 * which may be interperated by the calling code to mean that this class loader is a direct child of the system class 037 * loader. 038 * 039 * @author Dain Sundstrom 040 * @version $Id: MultiParentClassLoader.java 1347594 2012-06-07 12:56:03Z gnodet $ 041 * @since 2.0 042 */ 043public class MultiParentClassLoader extends NamedClassLoader { 044 private final ClassLoader[] parents; 045 private final boolean inverseClassLoading; 046 private final String[] hiddenClasses; 047 private final String[] nonOverridableClasses; 048 private final String[] hiddenResources; 049 private final String[] nonOverridableResources; 050 private final Map<String, SoftReference<Class>> cache = new ConcurrentHashMap<String, SoftReference<Class>>(); 051 052 /** 053 * Creates a named class loader with no parents. 054 * @param name the name of this class loader 055 * @param urls the urls from which this class loader will classes and resources 056 */ 057 public MultiParentClassLoader(String name, URL[] urls) { 058 this(name, urls, ClassLoader.getSystemClassLoader()); 059 } 060 061 /** 062 * Creates a named class loader as a child of the specified parent. 063 * @param name the name of this class loader 064 * @param urls the urls from which this class loader will classes and resources 065 * @param parent the parent of this class loader 066 */ 067 public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent) { 068 this(name, urls, new ClassLoader[] {parent}); 069 } 070 071 /** 072 * Creates a named class loader as a child of the specified parent and using the specified URLStreamHandlerFactory 073 * for accessing the urls.. 074 * @param name the name of this class loader 075 * @param urls the urls from which this class loader will classes and resources 076 * @param parent the parent of this class loader 077 * @param factory the URLStreamHandlerFactory used to access the urls 078 */ 079 public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { 080 this(name, urls, new ClassLoader[] {parent}, factory); 081 } 082 083 /** 084 * Creates a named class loader as a child of the specified parents. 085 * @param name the name of this class loader 086 * @param urls the urls from which this class loader will classes and resources 087 * @param parents the parents of this class loader 088 */ 089 public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents) { 090 this(name, urls, parents, false, new String[0], new String[0]); 091 } 092 093 public MultiParentClassLoader(String name, URL[] urls, ClassLoader parent, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 094 this(name, urls, new ClassLoader[]{parent}, inverseClassLoading, hiddenClasses, nonOverridableClasses); 095 } 096 097 /** 098 * Creates a named class loader as a child of the specified parents and using the specified URLStreamHandlerFactory 099 * for accessing the urls.. 100 * @param name the name of this class loader 101 * @param urls the urls from which this class loader will classes and resources 102 * @param parents the parents of this class loader 103 * @param factory the URLStreamHandlerFactory used to access the urls 104 */ 105 public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, URLStreamHandlerFactory factory) { 106 super(name, urls, null, factory); 107 this.parents = copyParents(parents); 108 this.inverseClassLoading = false; 109 this.hiddenClasses = new String[0]; 110 this.nonOverridableClasses = new String[0]; 111 this.hiddenResources = new String[0]; 112 this.nonOverridableResources = new String[0]; 113 } 114 115 public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, Collection hiddenClasses, Collection nonOverridableClasses) { 116 this(name, urls, parents, inverseClassLoading, (String[]) hiddenClasses.toArray(new String[hiddenClasses.size()]), (String[]) nonOverridableClasses.toArray(new String[nonOverridableClasses.size()])); 117 } 118 119 public MultiParentClassLoader(String name, URL[] urls, ClassLoader[] parents, boolean inverseClassLoading, String[] hiddenClasses, String[] nonOverridableClasses) { 120 super(name, urls); 121 this.parents = copyParents(parents); 122 this.inverseClassLoading = inverseClassLoading; 123 this.hiddenClasses = hiddenClasses; 124 this.nonOverridableClasses = nonOverridableClasses; 125 hiddenResources = toResources(hiddenClasses); 126 nonOverridableResources = toResources(nonOverridableClasses); 127 } 128 129 private static String[] toResources(String[] classes) { 130 String[] resources = new String[classes.length]; 131 for (int i = 0; i < classes.length; i++) { 132 String className = classes[i]; 133 resources[i] = className.replace('.', '/'); 134 } 135 return resources; 136 } 137 138 private static ClassLoader[] copyParents(ClassLoader[] parents) { 139 ClassLoader[] newParentsArray = new ClassLoader[parents.length]; 140 for (int i = 0; i < parents.length; i++) { 141 ClassLoader parent = parents[i]; 142 if (parent == null) { 143 throw new NullPointerException("parent[" + i + "] is null"); 144 } 145 newParentsArray[i] = parent; 146 } 147 return newParentsArray; 148 } 149 150 /** 151 * Gets the parents of this class loader. 152 * @return the parents of this class loader 153 */ 154 public ClassLoader[] getParents() { 155 return parents; 156 } 157 158 /** 159 * {@inheritDoc} 160 */ 161 protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { 162 Class result = null; 163 164 // 165 // check if the class is already in the local cache 166 // 167 SoftReference<Class> reference = cache.get(name); 168 if (reference != null) { 169 result = reference.get(); 170 } 171 if (result == null) { 172 result = doLoadClass(name, resolve); 173 cache.put(name, new SoftReference<Class>(result)); 174 } 175 176 return result; 177 } 178 179 private synchronized Class doLoadClass(String name, boolean resolve) throws ClassNotFoundException { 180 // 181 // Check if class is in the loaded classes cache 182 // 183 Class cachedClass = findLoadedClass(name); 184 if (cachedClass != null) { 185 return resolveClass(cachedClass, resolve); 186 } 187 188 // 189 // if we are using inverse class loading, check local urls first 190 // 191 if (inverseClassLoading && !isDestroyed() && !isNonOverridableClass(name)) { 192 try { 193 Class clazz = findClass(name); 194 return resolveClass(clazz, resolve); 195 } catch (ClassNotFoundException ignored) { 196 } 197 } 198 199 // 200 // Check parent class loaders 201 // 202 if (!isHiddenClass(name)) { 203 for (int i = 0; i < parents.length; i++) { 204 ClassLoader parent = parents[i]; 205 try { 206 Class clazz = parent.loadClass(name); 207 return resolveClass(clazz, resolve); 208 } catch (ClassNotFoundException ignored) { 209 // this parent didn't have the class; try the next one 210 } 211 } 212 } 213 214 // 215 // if we are not using inverse class loading, check local urls now 216 // 217 // don't worry about excluding non-overridable classes here... we 218 // have alredy checked he parent and the parent didn't have the 219 // class, so we can override now 220 if (!isDestroyed()) { 221 try { 222 Class clazz = findClass(name); 223 return resolveClass(clazz, resolve); 224 } catch (ClassNotFoundException ignored) { 225 } 226 } 227 228 throw new ClassNotFoundException(name + " in classloader " + getName()); 229 } 230 231 private boolean isNonOverridableClass(String name) { 232 for (int i = 0; i < nonOverridableClasses.length; i++) { 233 if (name.startsWith(nonOverridableClasses[i])) { 234 return true; 235 } 236 } 237 return false; 238 } 239 240 private boolean isHiddenClass(String name) { 241 for (int i = 0; i < hiddenClasses.length; i++) { 242 if (name.startsWith(hiddenClasses[i])) { 243 return true; 244 } 245 } 246 return false; 247 } 248 249 private Class resolveClass(Class clazz, boolean resolve) { 250 if (resolve) { 251 resolveClass(clazz); 252 } 253 return clazz; 254 } 255 256 /** 257 * {@inheritDoc} 258 */ 259 public URL getResource(String name) { 260 if (isDestroyed()) { 261 return null; 262 } 263 264 // 265 // if we are using inverse class loading, check local urls first 266 // 267 if (inverseClassLoading && !isDestroyed() && !isNonOverridableResource(name)) { 268 URL url = findResource(name); 269 if (url != null) { 270 return url; 271 } 272 } 273 274 // 275 // Check parent class loaders 276 // 277 if (!isHiddenResource(name)) { 278 for (int i = 0; i < parents.length; i++) { 279 ClassLoader parent = parents[i]; 280 URL url = parent.getResource(name); 281 if (url != null) { 282 return url; 283 } 284 } 285 } 286 287 // 288 // if we are not using inverse class loading, check local urls now 289 // 290 // don't worry about excluding non-overridable resources here... we 291 // have alredy checked he parent and the parent didn't have the 292 // resource, so we can override now 293 if (!isDestroyed()) { 294 // parents didn't have the resource; attempt to load it from my urls 295 return findResource(name); 296 } 297 298 return null; 299 } 300 301 /** 302 * {@inheritDoc} 303 */ 304 public Enumeration findResources(String name) throws IOException { 305 if (isDestroyed()) { 306 return Collections.enumeration(Collections.EMPTY_SET); 307 } 308 309 List resources = new ArrayList(); 310 311 // 312 // if we are using inverse class loading, add the resources from local urls first 313 // 314 if (inverseClassLoading && !isDestroyed()) { 315 List myResources = Collections.list(super.findResources(name)); 316 resources.addAll(myResources); 317 } 318 319 // 320 // Add parent resources 321 // 322 for (int i = 0; i < parents.length; i++) { 323 ClassLoader parent = parents[i]; 324 List parentResources = Collections.list(parent.getResources(name)); 325 resources.addAll(parentResources); 326 } 327 328 // 329 // if we are not using inverse class loading, add the resources from local urls now 330 // 331 if (!inverseClassLoading && !isDestroyed()) { 332 List myResources = Collections.list(super.findResources(name)); 333 resources.addAll(myResources); 334 } 335 336 return Collections.enumeration(resources); 337 } 338 339 private boolean isNonOverridableResource(String name) { 340 for (int i = 0; i < nonOverridableResources.length; i++) { 341 if (name.startsWith(nonOverridableResources[i])) { 342 return true; 343 } 344 } 345 return false; 346 } 347 348 private boolean isHiddenResource(String name) { 349 for (int i = 0; i < hiddenResources.length; i++) { 350 if (name.startsWith(hiddenResources[i])) { 351 return true; 352 } 353 } 354 return false; 355 } 356 357 /** 358 * {@inheritDoc} 359 */ 360 public String toString() { 361 return "[" + getClass().getName() + ":" + 362 " name=" + getName() + 363 " urls=" + Arrays.asList(getURLs()) + 364 " parents=" + Arrays.asList(parents) + 365 "]"; 366 } 367}