| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| |
| package org.netbeans; |
| |
| import java.io.IOException; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.openide.util.Enumerations; |
| import org.openide.util.Lookup; |
| |
| /** |
| * A class loader that has multiple parents and uses them for loading |
| * classes and resources. It is optimized for working in the enviroment |
| * of a deeply nested classloader hierarchy. It uses shared knowledge |
| * about package population to route the loading request directly |
| * to the correct classloader. |
| * It doesn't load classes or resources itself, but allows subclasses |
| * to add such functionality. |
| * |
| * @author Petr Nejedly, Jesse Glick |
| */ |
| public class ProxyClassLoader extends ClassLoader { |
| |
| private static final Logger LOGGER = Logger.getLogger(ProxyClassLoader.class.getName()); |
| private static final boolean LOG_LOADING; |
| private static final ClassLoader TOP_CL = ProxyClassLoader.class.getClassLoader(); |
| |
| static { |
| boolean prop1 = System.getProperty("org.netbeans.ProxyClassLoader.level") != null; |
| LOG_LOADING = prop1 || LOGGER.isLoggable(Level.FINE); |
| } |
| |
| /** All known packages |
| * @GuardedBy("packages") |
| */ |
| private final Map<String, Package> packages = new HashMap<String, Package>(); |
| |
| /** keeps information about parent classloaders, system classloader, etc.*/ |
| volatile ProxyClassParents parents; |
| |
| /** Create a multi-parented classloader. |
| * @param parents all direct parents of this classloader, except system one. |
| * @param transitive whether other PCLs depending on this one will |
| * automatically search through its parent list |
| */ |
| public ProxyClassLoader(ClassLoader[] parents, boolean transitive) { |
| super(TOP_CL); |
| this.parents = ProxyClassParents.coalesceParents(this, parents, TOP_CL, transitive); |
| } |
| |
| protected final void addCoveredPackages(Iterable<String> coveredPackages) { |
| ProxyClassPackages.addCoveredPackages(this, coveredPackages); |
| } |
| |
| // this is used only by system classloader, maybe we can redesign it a bit |
| // to live without this functionality, then destroy may also go away |
| /** Add new parents dynamically. |
| * @param nueparents the new parents to add (append to list) |
| * @throws IllegalArgumentException in case of a null or cyclic parent (duplicate OK) |
| */ |
| public void append(ClassLoader[] nueparents) throws IllegalArgumentException { |
| if (nueparents == null) throw new IllegalArgumentException("null parents array"); // NOI18N |
| |
| for (ClassLoader cl : nueparents) { |
| if (cl == null) throw new IllegalArgumentException("null parent: " + Arrays.asList(nueparents)); // NOI18N |
| } |
| |
| ProxyClassLoader[] resParents = null; |
| ModuleFactory moduleFactory = Lookup.getDefault().lookup(ModuleFactory.class); |
| if (moduleFactory != null && moduleFactory.removeBaseClassLoader()) { |
| // this hack is here to prevent having the application classloader |
| // as parent to all module classloaders. |
| parents = ProxyClassParents.coalesceParents(this, nueparents, ClassLoader.getSystemClassLoader(), parents.isTransitive()); |
| } else { |
| parents = parents.append(this, nueparents); |
| } |
| } |
| |
| /** |
| * Loads the class with the specified name. The implementation of |
| * this method searches for classes in the following order:<p> |
| * <ol> |
| * <li> Looks for a known package and pass the loading to the ClassLoader |
| for that package. |
| * <li> For unknown packages passes the call directly |
| * already been loaded. |
| * </ol> |
| * |
| * @param name the name of the class |
| * @param resolve if <code>true</code> then resolve the class |
| * @return the resulting <code>Class</code> object |
| * @exception ClassNotFoundException if the class could not be found |
| */ |
| @Override |
| protected synchronized Class<?> loadClass(String name, boolean resolve) |
| throws ClassNotFoundException { |
| final Class<?> cls = doFindClass(name); |
| if (resolve) resolveClass(cls); |
| return cls; |
| } |
| |
| @Override |
| protected Class<?> findClass(String name) throws ClassNotFoundException { |
| LOGGER.log(Level.FINEST, "{0} finding class {1}", new Object[] {this, name}); |
| return doFindClass(name); |
| } |
| |
| private Class<?> doFindClass(String name) throws ClassNotFoundException { |
| if (LOG_LOADING && !name.startsWith("java.")) { |
| LOGGER.log(Level.FINEST, "{0} initiated loading of {1}", |
| new Object[] {this, name}); |
| } |
| |
| Class<?> cls = null; |
| |
| int last = name.lastIndexOf('.'); |
| if (last == -1) { |
| throw new ClassNotFoundException("Will not load classes from default package (" + name + ")"); // NOI18N |
| } |
| |
| // Strip+intern or use from package coverage |
| String pkg = (last >= 0) ? name.substring(0, last) : ""; |
| |
| final String path = pkg.replace('.', '/') + "/"; |
| |
| Set<ProxyClassLoader> del = ProxyClassPackages.findCoveredPkg(pkg); |
| |
| Boolean boo = isSystemPackage(pkg); |
| if ((boo == null || boo.booleanValue()) && shouldDelegateResource(path, null)) { |
| try { |
| cls = parents.systemCL().loadClass(name); |
| if (boo == null) registerSystemPackage(pkg, true); |
| return cls; // try SCL first |
| } catch (ClassNotFoundException e) { |
| // No dissaster, try other loaders |
| } |
| } |
| |
| if (del == null) { |
| // uncovered package, go directly to SCL (may throw the CNFE for us) |
| //if (shouldDelegateResource(path, null)) cls = par.systemCL().loadClass(name); |
| } else if (del.size() == 1) { |
| // simple package coverage |
| ProxyClassLoader pcl = del.iterator().next(); |
| if (pcl == this || (parents.contains(pcl) && shouldDelegateResource(path, pcl))) { |
| cls = pcl.selfLoadClass(pkg, name); |
| if (cls != null) registerSystemPackage(pkg, false); |
| }/* else { // maybe it is also covered by SCL |
| if (shouldDelegateResource(path, null)) cls = par.systemCL().loadClass(name); |
| }*/ |
| } else { |
| // multicovered package, search in order |
| for (ProxyClassLoader pcl : parents.loaders()) { // all our accessible parents |
| if (del.contains(pcl) && shouldDelegateResource(path, pcl)) { // that cover given package |
| Class<?> _cls = pcl.selfLoadClass(pkg, name); |
| if (_cls != null) { |
| if (cls == null) { |
| cls = _cls; |
| } else if (cls != _cls) { |
| String message = "Will not load class " + name + " arbitrarily from one of " + |
| cls.getClassLoader() + " and " + pcl + " starting from " + this + |
| "; see http://wiki.netbeans.org/DevFaqModuleCCE"; |
| ClassNotFoundException cnfe = new ClassNotFoundException(message); |
| if (arbitraryLoadWarnings.add(message)) { |
| if (LOGGER.isLoggable(Level.FINE)) { |
| LOGGER.log(Level.FINE, null, cnfe); |
| } else { |
| LOGGER.warning(message); |
| } |
| } |
| throw cnfe; |
| } |
| } |
| } |
| } |
| if (cls == null && del.contains(this)) cls = selfLoadClass(pkg, name); |
| if (cls != null) registerSystemPackage(pkg, false); |
| } |
| if (cls == null && shouldDelegateResource(path, null)) { |
| try { |
| cls = parents.systemCL().loadClass(name); |
| } catch (ClassNotFoundException e) { |
| throw new ClassNotFoundException(diagnosticCNFEMessage(e.getMessage(), del), e); |
| } |
| } |
| if (cls == null) { |
| throw new ClassNotFoundException(diagnosticCNFEMessage(name, del)); |
| } |
| return cls; |
| } |
| |
| private String diagnosticCNFEMessage(String base, Set<ProxyClassLoader> del) { |
| String parentSetS; |
| int size = parents.size(); |
| // Too big to show in its entirety - overwhelms the log file. |
| StringBuilder b = new StringBuilder(); |
| b.append(base).append(" starting from ").append(this) |
| .append(" with possible defining loaders ").append(del) |
| .append(" and declared parents "); |
| Iterator<ProxyClassLoader> parentSetI = parents.loaders().iterator(); |
| for (int i = 0; i < 10 && parentSetI.hasNext(); i++) { |
| b.append(i == 0 ? "[" : ", "); |
| b.append(parentSetI.next()); |
| } |
| if (parentSetI.hasNext()) { |
| b.append(", ...").append(size - 10).append(" more"); |
| } |
| b.append(']'); |
| return b.toString(); |
| } |
| private static final Set<String> arbitraryLoadWarnings = Collections.synchronizedSet(new HashSet<String>()); |
| |
| /** May return null */ |
| private synchronized Class<?> selfLoadClass(String pkg, String name) { |
| Class<?> cls = findLoadedClass(name); |
| if (cls == null) { |
| try { |
| cls = doLoadClass(pkg, name); |
| } catch (NoClassDefFoundError e) { |
| // #145503: we can make a guess as to what triggered this error (since the JRE does not inform you). |
| // XXX Exceptions.attachMessage does not seem to work here |
| throw (NoClassDefFoundError) new NoClassDefFoundError(e.getMessage() + " while loading " + name + |
| "; see http://wiki.netbeans.org/DevFaqTroubleshootClassNotFound").initCause(e); // NOI18N |
| } |
| if (LOG_LOADING && !name.startsWith("java.")) LOGGER.log(Level.FINEST, "{0} loaded {1}", |
| new Object[] {this, name}); |
| } |
| return cls; |
| } |
| |
| |
| /** This ClassLoader can't load anything itself. Subclasses |
| * may override this method to do some class loading themselves. The |
| * implementation should not throw any exception, just return |
| * <CODE>null</CODE> if it can't load required class. |
| * |
| * @param name the name of the class |
| * @return the resulting <code>Class</code> object or <code>null</code> |
| */ |
| protected Class<?> doLoadClass(String pkg, String name) { |
| return null; |
| } |
| |
| private String stripInitialSlash(String resource) { // #90310 |
| if (resource.startsWith("/")) { |
| LOGGER.log(Level.WARNING, "Should not use initial '/' in calls to ClassLoader.getResource(s): {0}", resource); |
| return resource.substring(1); |
| } else { |
| return resource; |
| } |
| } |
| |
| /** |
| * Finds the resource with the given name. |
| * @param name a "/"-separated path name that identifies the resource. |
| * @return a URL for reading the resource, or <code>null</code> if |
| * the resource could not be found. |
| * @see #findResource(String) |
| */ |
| @Override |
| public final URL getResource(String name) { |
| return getResourceImpl(name); |
| } |
| |
| URL getResourceImpl(String name) { |
| URL url = null; |
| name = stripInitialSlash(name); |
| |
| int last = name.lastIndexOf('/'); |
| String pkg; |
| String fallDef = null; |
| if (last >= 0) { |
| if (name.startsWith("META-INF/")) { |
| pkg = name.substring(8); |
| fallDef = name.substring(0, last).replace('/', '.'); |
| } else { |
| pkg = name.substring(0, last).replace('/', '.'); |
| } |
| } else { |
| pkg = "default/" + name; |
| fallDef = ""; |
| } |
| String path = name.substring(0, last+1); |
| |
| Boolean systemPackage = isSystemPackage(pkg); |
| if ((systemPackage == null || systemPackage) && shouldDelegateResource(path, null)) { |
| URL u = parents.systemCL().getResource(name); |
| if (u != null) { |
| if (systemPackage == null) { |
| registerSystemPackage(pkg, true); |
| } |
| return u; |
| } |
| // else try other loaders |
| } |
| |
| Set<ProxyClassLoader> del = ProxyClassPackages.findCoveredPkg(pkg); |
| if (fallDef != null) { |
| Set<ProxyClassLoader> snd = ProxyClassPackages.findCoveredPkg(fallDef); |
| if (snd != null) { |
| if (del != null) { |
| del = new HashSet<ProxyClassLoader>(del); |
| del.addAll(snd); |
| } else { |
| del = snd; |
| } |
| } |
| } |
| |
| if (del == null) { |
| // uncovered package, go directly to SCL |
| if (shouldDelegateResource(path, null)) url = parents.systemCL().getResource(name); |
| } else if (del.size() == 1) { |
| // simple package coverage |
| ProxyClassLoader pcl = del.iterator().next(); |
| if (pcl == this || (parents.contains(pcl) && shouldDelegateResource(path, pcl))) |
| url = pcl.findResource(name); |
| } else { |
| // multicovered package, search in order |
| for (ProxyClassLoader pcl : parents.loaders()) { // all our accessible parents |
| if (del.contains(pcl) && shouldDelegateResource(path, pcl)) { // that cover given package |
| url = pcl.findResource(name); |
| if (url != null) break; |
| } |
| } |
| if (url == null && del.contains(this)) url = findResource(name); |
| } |
| |
| // uncovered package, go directly to SCL |
| if (url == null && shouldDelegateResource(path, null)) url = parents.systemCL().getResource(name); |
| |
| return url; |
| } |
| |
| /** This ClassLoader can't load anything itself. Subclasses |
| * may override this method to do some resource loading themselves. |
| * |
| * @param name the resource name |
| * @return a URL for reading the resource, or <code>null</code> |
| * if the resource could not be found. |
| */ |
| @Override |
| public URL findResource(String name) { |
| return super.findResource(name); |
| } |
| |
| @Override |
| public final Enumeration<URL> getResources(String name) throws IOException { |
| return getResourcesImpl(name); |
| } |
| |
| synchronized Enumeration<URL> getResourcesImpl(String name) throws IOException { |
| name = stripInitialSlash(name); |
| final int slashIdx = name.lastIndexOf('/'); |
| final String path = name.substring(0, slashIdx + 1); |
| String pkg; |
| String fallDef = null; |
| if (slashIdx >= 0) { |
| if (name.startsWith("META-INF/")) { |
| pkg = name.substring(8); |
| fallDef = name.substring(0, slashIdx).replace('/', '.'); |
| } else { |
| pkg = name.substring(0, slashIdx).replace('/', '.'); |
| } |
| } else { |
| pkg = "default/" + name; |
| fallDef = ""; |
| } |
| List<Enumeration<URL>> sub = new ArrayList<Enumeration<URL>>(); |
| |
| // always consult SCL first |
| if (shouldDelegateResource(path, null)) sub.add(parents.systemCL().getResources(name)); |
| |
| Set<ProxyClassLoader> del = ProxyClassPackages.findCoveredPkg(pkg); |
| if (fallDef != null) { |
| Set<ProxyClassLoader> snd = ProxyClassPackages.findCoveredPkg(fallDef); |
| if (snd != null) { |
| if (del != null) { |
| del = new HashSet<ProxyClassLoader>(del); |
| del.addAll(snd); |
| } else { |
| del = snd; |
| } |
| } |
| } |
| |
| if (del != null) { |
| for (ProxyClassLoader pcl : parents.loaders()) { // all our accessible parents |
| if (del.contains(pcl) && shouldDelegateResource(path, pcl)) { // that cover given package |
| sub.add(pcl.findResources(name)); |
| } |
| } |
| if (del.contains(this)) { |
| sub.add(findResources(name)); |
| } |
| } |
| // Should not be duplicates, assuming the parent loaders are properly distinct |
| // from one another and do not overlap in JAR usage, which they ought not. |
| // Anyway MetaInfServicesLookup, the most important client of this method, does |
| // its own duplicate filtering already. |
| return Enumerations.concat(Collections.enumeration(sub)); |
| } |
| |
| @Override |
| public Enumeration<URL> findResources(String name) throws IOException { |
| return super.findResources(name); |
| } |
| |
| |
| /** |
| * Returns a Package that has been defined by this class loader or any |
| * of its parents. |
| * |
| * @param name the package name |
| * @return the Package corresponding to the given name, or null if not found |
| */ |
| @Override |
| protected Package getPackage(String name) { |
| return getPackageFast(name, true); |
| } |
| |
| /** |
| * Faster way to find a package. |
| * @param name package name in org.netbeans.modules.foo format |
| * @param sname package name in org/netbeans/modules/foo/ format |
| * @param recurse whether to also ask parents |
| * @return located package, or null |
| */ |
| protected Package getPackageFast(String name, boolean recurse) { |
| synchronized (packages) { |
| Package pkg = packages.get(name); |
| if (pkg != null) { |
| return pkg; |
| } |
| if (!recurse) { |
| return null; |
| } |
| String path = name.replace('.', '/'); |
| for (ProxyClassLoader par : this.parents.loaders()) { |
| if (!shouldDelegateResource(path, par)) |
| continue; |
| pkg = par.getPackageFast(name, false); |
| if (pkg != null) break; |
| } |
| // pretend the resource ends with "/". This works better with hidden package and |
| // prefix-based checks. |
| if (pkg == null && shouldDelegateResource(path + "/", null)) { |
| // Cannot access either Package.getSystemPackages nor ClassLoader.getPackage |
| // from here, so do the best we can though it will cause unnecessary |
| // duplication of the package cache (PCL.packages vs. CL.packages): |
| pkg = super.getPackage(name); |
| } |
| if (pkg != null) { |
| packages.put(name, pkg); |
| } |
| return pkg; |
| } |
| } |
| |
| /** This is here just for locking serialization purposes. |
| * Delegates to super.definePackage with proper locking. |
| * Also tracks the package in our private cache, since |
| * getPackageFast(...,...,false) will not call super.getPackage. |
| */ |
| @Override |
| protected Package definePackage(String name, String specTitle, |
| String specVersion, String specVendor, String implTitle, |
| String implVersion, String implVendor, URL sealBase ) |
| throws IllegalArgumentException { |
| synchronized (packages) { |
| Package pkg = super.definePackage(name, specTitle, specVersion, specVendor, implTitle, |
| implVersion, implVendor, sealBase); |
| packages.put(name, pkg); |
| return pkg; |
| } |
| } |
| |
| /** |
| * Returns all of the Packages defined by this class loader and its parents. |
| * |
| * @return the array of <code>Package</code> objects defined by this |
| * <code>ClassLoader</code> |
| */ |
| @Override |
| protected synchronized Package[] getPackages() { |
| return getPackages(new HashSet<ClassLoader>()); |
| } |
| |
| /** |
| * Returns all of the Packages defined by this class loader and its parents. |
| * Do not recurse to parents in addedParents set. It speeds up execution |
| * time significantly. |
| * @return the array of <code>Package</code> objects defined by this |
| * <code>ClassLoader</code> |
| */ |
| private Package[] getPackages(Set<ClassLoader> addedParents) { |
| Map<String,Package> all = new HashMap<String, Package>(); |
| // XXX call shouldDelegateResource on each? |
| addPackages(all, super.getPackages()); |
| for (ClassLoader par : this.parents.loaders()) { |
| if (par instanceof ProxyClassLoader && addedParents.add(par)) { |
| // XXX should ideally use shouldDelegateResource here... |
| addPackages(all, ((ProxyClassLoader)par).getPackages(addedParents)); |
| } |
| } |
| synchronized (packages) { |
| all.keySet().removeAll(packages.keySet()); |
| packages.putAll(all); |
| return packages.values().toArray(new Package[packages.size()]); |
| } |
| } |
| |
| private void addPackages(Map<String,Package> all, Package[] pkgs) { |
| // Would be easier if Package.equals() was just defined sensibly... |
| for (int i = 0; i < pkgs.length; i++) { |
| all.put(pkgs[i].getName(), pkgs[i]); |
| } |
| } |
| |
| protected final void setSystemClassLoader(ClassLoader s) { |
| parents = parents.changeSystemClassLoader(s); |
| } |
| |
| protected boolean shouldDelegateResource(String pkg, ClassLoader parent) { |
| return true; |
| } |
| |
| /** Called before releasing the classloader so it can itself unregister |
| * from the global ClassLoader pool */ |
| public void destroy() { |
| ProxyClassPackages.removeCoveredPakcages(this); |
| } |
| |
| final ClassLoader firstParent() { |
| Iterator<ProxyClassLoader> it = parents.loaders().iterator(); |
| return it.hasNext() ? it.next() : null; |
| } |
| |
| // |
| // System Class Loader Packages Support |
| // |
| |
| private static Map<String,Boolean> sclPackages = Collections.synchronizedMap(new HashMap<String,Boolean>()); |
| private static Boolean isSystemPackage(String pkg) { |
| return sclPackages.get(pkg); |
| } |
| private static void registerSystemPackage(String pkg, boolean isSystemPkg) { |
| sclPackages.put(pkg, isSystemPkg); |
| } |
| } |