All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.ProxyClassLoader Maven / Gradle / Ivy

There is a newer version: RELEASE230
Show newest version
/*
 * 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 packages = new HashMap();

    /** 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 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:

*

    *
  1. Looks for a known package and pass the loading to the ClassLoader for that package. *
  2. For unknown packages passes the call directly * already been loaded. *
* * @param name the name of the class * @param resolve if true then resolve the class * @return the resulting Class object * @exception ClassNotFoundException if the class could not be found */ @Override protected synchronized Class loadClass(String name, boolean resolve) 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 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)); } if (resolve) resolveClass(cls); return cls; } private String diagnosticCNFEMessage(String base, Set 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 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 arbitraryLoadWarnings = Collections.synchronizedSet(new HashSet()); /** 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 * null if it can't load required class. * * @param name the name of the class * @return the resulting Class object or null */ 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 null 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 del = ProxyClassPackages.findCoveredPkg(pkg); if (fallDef != null) { Set snd = ProxyClassPackages.findCoveredPkg(fallDef); if (snd != null) { if (del != null) { del = new HashSet(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 null * if the resource could not be found. */ @Override public URL findResource(String name) { return super.findResource(name); } @Override public final Enumeration getResources(String name) throws IOException { return getResourcesImpl(name); } synchronized Enumeration 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> sub = new ArrayList>(); // always consult SCL first if (shouldDelegateResource(path, null)) sub.add(parents.systemCL().getResources(name)); Set del = ProxyClassPackages.findCoveredPkg(pkg); if (fallDef != null) { Set snd = ProxyClassPackages.findCoveredPkg(fallDef); if (snd != null) { if (del != null) { del = new HashSet(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 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 Package objects defined by this * ClassLoader */ @Override protected synchronized Package[] getPackages() { return getPackages(new HashSet()); } /** * 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 Package objects defined by this * ClassLoader */ private Package[] getPackages(Set addedParents) { Map all = new HashMap(); // 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 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 it = parents.loaders().iterator(); return it.hasNext() ? it.next() : null; } // // System Class Loader Packages Support // private static Map sclPackages = Collections.synchronizedMap(new HashMap()); private static Boolean isSystemPackage(String pkg) { return sclPackages.get(pkg); } private static void registerSystemPackage(String pkg, boolean isSystemPkg) { sclPackages.put(pkg, isSystemPkg); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy