001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans;
043:
044: import java.io.IOException;
045: import java.net.URL;
046: import java.util.ArrayList;
047: import java.util.Arrays;
048: import java.util.Collections;
049: import java.util.Enumeration;
050: import java.util.HashMap;
051: import java.util.HashSet;
052: import java.util.Iterator;
053: import java.util.LinkedHashSet;
054: import java.util.List;
055: import java.util.Map;
056: import java.util.Set;
057: import java.util.logging.Level;
058: import java.util.logging.Logger;
059: import org.openide.util.Enumerations;
060: import org.openide.util.Lookup;
061:
062: /**
063: * A class loader that has multiple parents and uses them for loading
064: * classes and resources. It is optimized for working in the enviroment
065: * of a deeply nested classloader hierarchy. It uses shared knowledge
066: * about package population to route the loading request directly
067: * to the correct classloader.
068: * It doesn't load classes or resources itself, but allows subclasses
069: * to add such functionality.
070: *
071: * @author Petr Nejedly, Jesse Glick
072: */
073: public class ProxyClassLoader extends ClassLoader implements
074: Util.PackageAccessibleClassLoader {
075:
076: private static final Logger LOGGER = Logger
077: .getLogger(ProxyClassLoader.class.getName());
078: private static final boolean LOG_LOADING;
079: private static final ClassLoader TOP_CL = ProxyClassLoader.class
080: .getClassLoader();
081:
082: static {
083: boolean prop1 = System
084: .getProperty("org.netbeans.ProxyClassLoader.level") != null;
085: LOG_LOADING = prop1 || LOGGER.isLoggable(Level.FINE);
086: }
087:
088: /** All known packages */
089: private final Map<String, Package> packages = new HashMap<String, Package>();
090:
091: /** All parents of this classloader, including their parents recursively */
092: private ProxyClassLoader[] parents;
093:
094: private final boolean transitive;
095:
096: /** The base class loader that is before all ProxyClassLoaders. */
097: private ClassLoader systemCL = TOP_CL;
098:
099: /** A shared map of all packages known by all classloaders. Also covers META-INF based resources.
100: * It contains two kinds of keys: dot-separated package names and slash-separated
101: * META-INF resource names, e.g. {"org.foobar", "/services/org.foobar.Foo"}
102: */
103: private static Map<String, Set<ProxyClassLoader>> packageCoverage = new HashMap<String, Set<ProxyClassLoader>>();
104:
105: private Set<ProxyClassLoader> parentSet = new HashSet<ProxyClassLoader>();
106:
107: private static Map<String, Boolean> sclPackages = Collections
108: .synchronizedMap(new HashMap<String, Boolean>());
109:
110: /** Create a multi-parented classloader.
111: * @param parents all direct parents of this classloader, except system one.
112: * @param coveredPackages Enumeration of Strings if format "org.something"
113: + * containing all packages to be covered by this classloader.
114: * @param transitive whether other PCLs depending on this one will
115: * automatically search through its parent list
116: */
117: public ProxyClassLoader(ClassLoader[] parents, boolean transitive) {
118: super (TOP_CL);
119: this .transitive = transitive;
120:
121: this .parents = coalesceParents(parents);
122: parentSet.addAll(Arrays.asList(this .parents));
123: }
124:
125: protected final void addCoveredPackages(
126: Iterable<String> coveredPackages) {
127: synchronized (packageCoverage) {
128: for (String pkg : coveredPackages) {
129: Set<ProxyClassLoader> delegates = packageCoverage
130: .get(pkg);
131: if (delegates == null) {
132: delegates = Collections
133: .<ProxyClassLoader> singleton(this );
134: packageCoverage.put(pkg, delegates);
135: } else if (delegates.size() == 1) {
136: delegates = new HashSet<ProxyClassLoader>(delegates);
137: packageCoverage.put(pkg, delegates);
138: delegates.add(this );
139: } else {
140: delegates.add(this );
141: }
142: }
143: }
144:
145: }
146:
147: // this is used only by system classloader, maybe we can redesign it a bit
148: // to live without this functionality, then destroy may also go away
149: /** Add new parents dynamically.
150: * @param nueparents the new parents to add (append to list)
151: * @throws IllegalArgumentException in case of a null or cyclic parent (duplicate OK)
152: */
153: public void append(ClassLoader[] nueparents)
154: throws IllegalArgumentException {
155: if (nueparents == null)
156: throw new IllegalArgumentException("null parents array"); // NOI18N
157:
158: for (ClassLoader cl : nueparents) {
159: if (cl == null)
160: throw new IllegalArgumentException("null parent: "
161: + Arrays.asList(nueparents)); // NOI18N
162: }
163:
164: ProxyClassLoader[] resParents = null;
165: ModuleFactory moduleFactory = Lookup.getDefault().lookup(
166: ModuleFactory.class);
167: if (moduleFactory != null
168: && moduleFactory.removeBaseClassLoader()) {
169: // this hack is here to prevent having the application classloader
170: // as parent to all module classloaders.
171: systemCL = ClassLoader.getSystemClassLoader();
172: resParents = coalesceAppend(new ProxyClassLoader[0],
173: nueparents);
174: } else {
175: resParents = coalesceAppend(parents, nueparents);
176: }
177: synchronized (this ) {
178: // synchronized because we don't want to mess up potentially running
179: // classloading
180: parents = resParents;
181: parentSet.clear();
182: parentSet.addAll(Arrays.asList(parents));
183: }
184: }
185:
186: /**
187: * Loads the class with the specified name. The implementation of
188: * this method searches for classes in the following order:<p>
189: * <ol>
190: * <li> Looks for a known package and pass the loading to the ClassLoader
191: for that package.
192: * <li> For unknown packages passes the call directly
193: * already been loaded.
194: * </ol>
195: *
196: * @param name the name of the class
197: * @param resolve if <code>true</code> then resolve the class
198: * @return the resulting <code>Class</code> object
199: * @exception ClassNotFoundException if the class could not be found
200: */
201: @Override
202: protected synchronized Class loadClass(String name, boolean resolve)
203: throws ClassNotFoundException {
204: if (LOG_LOADING && !name.startsWith("java.util.logging.")) {
205: LOGGER.log(Level.FINEST, "{0} initiated loading of {1}",
206: new Object[] { this , name });
207: }
208:
209: Class cls = null;
210:
211: int last = name.lastIndexOf('.');
212: if (last == -1) {
213: throw new ClassNotFoundException(
214: "Will not load classes from default package ("
215: + name + ")"); // NOI18N
216: }
217:
218: // Strip+intern or use from package coverage
219: String pkg = (last >= 0) ? name.substring(0, last) : "";
220:
221: final String path = pkg.replace('.', '/') + "/";
222:
223: Set<ProxyClassLoader> del = packageCoverage.get(pkg);
224:
225: Boolean boo = sclPackages.get(pkg);
226: if ((boo == null || boo.booleanValue())
227: && shouldDelegateResource(path, null)) {
228: try {
229: cls = systemCL.loadClass(name);
230: if (boo == null)
231: sclPackages.put(pkg, true);
232: return cls; // try SCL first
233: } catch (ClassNotFoundException e) {
234: // No dissaster, try other loaders
235: }
236: }
237:
238: if (del == null) {
239: // uncovered package, go directly to SCL (may throw the CNFE for us)
240: //if (shouldDelegateResource(path, null)) cls = systemCL.loadClass(name);
241: } else if (del.size() == 1) {
242: // simple package coverage
243: ProxyClassLoader pcl = del.iterator().next();
244: if (pcl == this
245: || (parentSet.contains(pcl) && shouldDelegateResource(
246: path, pcl))) {
247: cls = pcl.selfLoadClass(pkg, name);
248: if (cls != null)
249: sclPackages.put(pkg, false);
250: }/* else { // maybe it is also covered by SCL
251: if (shouldDelegateResource(path, null)) cls = systemCL.loadClass(name);
252: }*/
253: } else {
254: // multicovered package, search in order
255: for (ProxyClassLoader pcl : parents) { // all our accessible parents
256: if (del.contains(pcl)
257: && shouldDelegateResource(path, pcl)) { // that cover given package
258: cls = pcl.selfLoadClass(pkg, name);
259: if (cls != null)
260: break;
261: }
262: }
263: if (cls == null && del.contains(this ))
264: cls = selfLoadClass(pkg, name);
265: if (cls != null)
266: sclPackages.put(pkg, false);
267: }
268: if (cls == null && shouldDelegateResource(path, null))
269: cls = systemCL.loadClass(name); // may throw CNFE
270: if (cls == null)
271: throw new ClassNotFoundException(name);
272: if (resolve)
273: resolveClass(cls);
274: return cls;
275: }
276:
277: /** May return null */
278: private synchronized Class selfLoadClass(String pkg, String name) {
279: Class cls = findLoadedClass(name);
280: if (cls == null) {
281: cls = doLoadClass(pkg, name);
282: if (LOG_LOADING && !name.startsWith("java.util.logging."))
283: LOGGER.log(Level.FINEST, "{0} loaded {1}",
284: new Object[] { this , name });
285: }
286: return cls;
287: }
288:
289: /** This ClassLoader can't load anything itself. Subclasses
290: * may override this method to do some class loading themselves. The
291: * implementation should not throw any exception, just return
292: * <CODE>null</CODE> if it can't load required class.
293: *
294: * @param name the name of the class
295: * @return the resulting <code>Class</code> object or <code>null</code>
296: */
297: protected Class doLoadClass(String pkg, String name) {
298: return null;
299: }
300:
301: private String stripInitialSlash(String resource) { // #90310
302: if (resource.startsWith("/")) {
303: LOGGER
304: .log(
305: Level.WARNING,
306: "Should not use initial '/' in calls to ClassLoader.getResource(s): {0}",
307: resource);
308: return resource.substring(1);
309: } else {
310: return resource;
311: }
312: }
313:
314: /**
315: * Finds the resource with the given name.
316: * @param name a "/"-separated path name that identifies the resource.
317: * @return a URL for reading the resource, or <code>null</code> if
318: * the resource could not be found.
319: * @see #findResource(String)
320: */
321: @Override
322: public final URL getResource(String name) {
323: URL url = null;
324: name = stripInitialSlash(name);
325:
326: int last = name.lastIndexOf('/');
327: if (last == -1) {
328: printDefaultPackageWarning(name);
329: }
330:
331: String pkg = (last >= 0) ? name.substring(0, last).replace('/',
332: '.') : "";
333: String path = name.substring(0, last + 1);
334:
335: Boolean systemPackage = sclPackages.get(pkg);
336: if ((systemPackage == null || systemPackage)
337: && shouldDelegateResource(path, null)) {
338: URL u = systemCL.getResource(name);
339: if (u != null) {
340: if (systemPackage == null) {
341: sclPackages.put(pkg, true);
342: }
343: return u;
344: }
345: // else try other loaders
346: }
347:
348: Set<ProxyClassLoader> del = packageCoverage.get(pkg);
349:
350: if (del == null) {
351: // uncovered package, go directly to SCL
352: if (shouldDelegateResource(path, null))
353: url = systemCL.getResource(name);
354: } else if (del.size() == 1) {
355: // simple package coverage
356: ProxyClassLoader pcl = del.iterator().next();
357: if (pcl == this
358: || (parentSet.contains(pcl) && shouldDelegateResource(
359: path, pcl)))
360: url = pcl.findResource(name);
361: } else {
362: // multicovered package, search in order
363: for (ProxyClassLoader pcl : parents) { // all our accessible parents
364: if (del.contains(pcl)
365: && shouldDelegateResource(path, pcl)) { // that cover given package
366: url = pcl.findResource(name);
367: if (url != null)
368: break;
369: }
370: }
371: if (url == null && del.contains(this ))
372: url = findResource(name);
373: }
374:
375: // uncovered package, go directly to SCL
376: if (url == null && shouldDelegateResource(path, null))
377: url = systemCL.getResource(name);
378:
379: return url;
380: }
381:
382: /** This ClassLoader can't load anything itself. Subclasses
383: * may override this method to do some resource loading themselves.
384: *
385: * @param name the resource name
386: * @return a URL for reading the resource, or <code>null</code>
387: * if the resource could not be found.
388: */
389: @Override
390: protected URL findResource(String name) {
391: return null;
392: }
393:
394: /**
395: * Finds all the resource with the given name. The implementation of
396: * this method uses the {@link #simpleFindResources(String)} method to find
397: * all the resources available from this classloader and adds all the
398: * resources from all the parents.
399: *
400: * @param name the resource name
401: * @return an Enumeration of URLs for the resources
402: * @throws IOException if I/O errors occur
403: */
404: @Override
405: protected final synchronized Enumeration<URL> findResources(
406: String name) throws IOException {
407: name = stripInitialSlash(name);
408: final int slashIdx = name.lastIndexOf('/');
409: if (slashIdx == -1) {
410: printDefaultPackageWarning(name);
411: }
412:
413: final String path = name.substring(0, slashIdx + 1);
414: List<Enumeration<URL>> sub = new ArrayList<Enumeration<URL>>();
415:
416: // always consult SCL first
417: if (shouldDelegateResource(path, null))
418: sub.add(systemCL.getResources(name));
419:
420: if (name.startsWith("META-INF/")) { // common but expensive resources
421:
422: String relName = name.substring(8);
423: Set<ProxyClassLoader> del = packageCoverage.get(relName);
424:
425: if (del != null) { // unclaimed resource, go directly to SCL
426: for (ProxyClassLoader pcl : parents) { // all our accessible parents
427: if (del.contains(pcl)
428: && shouldDelegateResource(path, pcl)) { // that cover given package
429: sub.add(pcl.simpleFindResources(name));
430: }
431: }
432: if (del.contains(this ))
433: sub.add(simpleFindResources(name));
434: }
435: } else { // Don't bother optimizing this call by domains.
436: for (ProxyClassLoader pcl : parents) {
437: if (shouldDelegateResource(path, pcl))
438: sub.add(pcl.simpleFindResources(name));
439: }
440: sub.add(simpleFindResources(name));
441: }
442: // Should not be duplicates, assuming the parent loaders are properly distinct
443: // from one another and do not overlap in JAR usage, which they ought not.
444: // Anyway MetaInfServicesLookup, the most important client of this method, does
445: // its own duplicate filtering already.
446: return Enumerations.concat(Collections.enumeration(sub));
447: }
448:
449: protected Enumeration<URL> simpleFindResources(String name)
450: throws IOException {
451: return super .findResources(name);
452: }
453:
454: /**
455: * Returns a Package that has been defined by this class loader or any
456: * of its parents.
457: *
458: * @param name the package name
459: * @return the Package corresponding to the given name, or null if not found
460: */
461: @Override
462: protected Package getPackage(String name) {
463: return getPackageFast(name, true);
464: }
465:
466: /**
467: * Faster way to find a package.
468: * @param name package name in org.netbeans.modules.foo format
469: * @param sname package name in org/netbeans/modules/foo/ format
470: * @param recurse whether to also ask parents
471: * @return located package, or null
472: */
473: protected Package getPackageFast(String name, boolean recurse) {
474: synchronized (packages) {
475: Package pkg = packages.get(name);
476: if (pkg != null) {
477: return pkg;
478: }
479: if (!recurse) {
480: return null;
481: }
482: for (int i = 0; i < parents.length; i++) {
483: ProxyClassLoader par = parents[i];
484: pkg = par.getPackageFast(name, false);
485: if (pkg != null)
486: break;
487: }
488: if (pkg == null) {
489: // Cannot access either Package.getSystemPackage nor ClassLoader.getPackage
490: // from here, so do the best we can though it will cause unnecessary
491: // duplication of the package cache (PCL.packages vs. CL.packages):
492: pkg = super .getPackage(name);
493: }
494: if (pkg != null) {
495: packages.put(name, pkg);
496: }
497: return pkg;
498: }
499: }
500:
501: /** This is here just for locking serialization purposes.
502: * Delegates to super.definePackage with proper locking.
503: * Also tracks the package in our private cache, since
504: * getPackageFast(...,...,false) will not call super.getPackage.
505: */
506: @Override
507: protected Package definePackage(String name, String specTitle,
508: String specVersion, String specVendor, String implTitle,
509: String implVersion, String implVendor, URL sealBase)
510: throws IllegalArgumentException {
511: synchronized (packages) {
512: Package pkg = super .definePackage(name, specTitle,
513: specVersion, specVendor, implTitle, implVersion,
514: implVendor, sealBase);
515: packages.put(name, pkg);
516: return pkg;
517: }
518: }
519:
520: /**
521: * Returns all of the Packages defined by this class loader and its parents.
522: *
523: * @return the array of <code>Package</code> objects defined by this
524: * <code>ClassLoader</code>
525: */
526: @Override
527: protected synchronized Package[] getPackages() {
528: return getPackages(new HashSet<ClassLoader>());
529: }
530:
531: /**
532: * Returns all of the Packages defined by this class loader and its parents.
533: * Do not recurse to parents in addedParents set. It speeds up execution
534: * time significantly.
535: * @return the array of <code>Package</code> objects defined by this
536: * <code>ClassLoader</code>
537: */
538: private synchronized Package[] getPackages(
539: Set<ClassLoader> addedParents) {
540: Map<String, Package> all = new HashMap<String, Package>();
541: // XXX call shouldDelegateResource on each?
542: addPackages(all, super .getPackages());
543: for (int i = 0; i < parents.length; i++) {
544: ClassLoader par = parents[i];
545: if (par instanceof ProxyClassLoader
546: && addedParents.add(par)) {
547: // XXX should ideally use shouldDelegateResource here...
548: addPackages(all, ((ProxyClassLoader) par)
549: .getPackages(addedParents));
550: }
551: }
552: synchronized (packages) {
553: all.keySet().removeAll(packages.keySet());
554: packages.putAll(all);
555: }
556: return packages.values().toArray(new Package[packages.size()]);
557: }
558:
559: public Package getPackageAccessibly(String name) {
560: return getPackage(name);
561: }
562:
563: public Package[] getPackagesAccessibly() {
564: return getPackages();
565: }
566:
567: /**
568: * #38368: Warn that the default package should not be used.
569: */
570: private static void printDefaultPackageWarning(String name) {
571: // #42201 - commons-logging lib tries to read its config from this file, ignore
572: if (!"commons-logging.properties".equals(name)
573: && !"jndi.properties".equals(name)) { // NOI18N
574: LOGGER
575: .log(
576: Level.INFO,
577: null,
578: new IllegalStateException(
579: "You are trying to access file: "
580: + name
581: + " from the default package. Please see http://www.netbeans.org/download/dev/javadoc/org-openide-modules/org/openide/modules/doc-files/classpath.html#default_package"));
582: }
583: }
584:
585: /** Coalesce parent classloaders into an optimized set.
586: * This means that all parents of the specified classloaders
587: * are also added recursively, removing duplicates along the way.
588: * Search order should be preserved (parents before children, stable w.r.t. inputs).
589: * @param loaders list of suggested parents (no nulls or duplicates permitted)
590: * @return optimized list of parents (no nulls or duplicates)
591: * @throws IllegalArgumentException if there are cycles
592: */
593: private ProxyClassLoader[] coalesceParents(ClassLoader[] loaders)
594: throws IllegalArgumentException {
595: return coalesceAppend(new ProxyClassLoader[0], loaders);
596: }
597:
598: /** Coalesce a new set of loaders into the existing ones.
599: */
600: private ProxyClassLoader[] coalesceAppend(
601: ProxyClassLoader[] existing, ClassLoader[] appended)
602: throws IllegalArgumentException {
603: int likelySize = existing.length + appended.length;
604:
605: LinkedHashSet<ClassLoader> uniq = new LinkedHashSet<ClassLoader>(
606: likelySize);
607: uniq.addAll(Arrays.asList(existing));
608:
609: if (uniq.containsAll(Arrays.asList(appended)))
610: return existing; // No change required.
611:
612: for (ClassLoader l : appended)
613: addRec(uniq, l); // add all loaders (maybe recursively)
614:
615: // validate the configuration
616: // it is valid if all heading non-ProxyClassLoaders are parents of the last one
617: boolean head = true;
618: List<ProxyClassLoader> pcls = new ArrayList<ProxyClassLoader>(
619: uniq.size());
620: for (ClassLoader l : uniq) {
621: if (head) {
622: if (l instanceof ProxyClassLoader) {
623: // only PCLs after this point
624: head = false;
625: pcls.add((ProxyClassLoader) l);
626: } else {
627: if (isParentOf(systemCL, l)) {
628: systemCL = l;
629: } else {
630: throw new IllegalArgumentException(
631: "Bad ClassLoader ordering: "
632: + Arrays.asList(appended));
633: }
634: }
635: } else {
636: if (l instanceof ProxyClassLoader) {
637: pcls.add((ProxyClassLoader) l);
638: } else {
639: throw new IllegalArgumentException(
640: "Bad ClassLoader ordering: "
641: + Arrays.asList(appended));
642:
643: }
644: }
645: }
646:
647: ProxyClassLoader[] ret = pcls.toArray(new ProxyClassLoader[pcls
648: .size()]);
649: return ret;
650: }
651:
652: private static boolean isParentOf(ClassLoader parent,
653: ClassLoader child) {
654: while (child != null) {
655: if (child == parent)
656: return true;
657: child = child.getParent();
658: }
659: return false;
660: }
661:
662: private void addRec(Set<ClassLoader> resultingUnique,
663: ClassLoader loader) throws IllegalArgumentException {
664: if (loader == this )
665: throw new IllegalArgumentException("cycle in parents"); // NOI18N
666: if (resultingUnique.contains(loader))
667: return;
668: if (loader instanceof ProxyClassLoader
669: && ((ProxyClassLoader) loader).transitive) {
670: for (ProxyClassLoader lpar : ((ProxyClassLoader) loader).parents) {
671: addRec(resultingUnique, lpar);
672: }
673: }
674: resultingUnique.add(loader);
675: }
676:
677: private void addPackages(Map<String, Package> all, Package[] pkgs) {
678: // Would be easier if Package.equals() was just defined sensibly...
679: for (int i = 0; i < pkgs.length; i++) {
680: all.put(pkgs[i].getName(), pkgs[i]);
681: }
682: }
683:
684: protected final void setSystemClassLoader(ClassLoader s) {
685: systemCL = s;
686: }
687:
688: protected boolean shouldDelegateResource(String pkg,
689: ClassLoader parent) {
690: return true;
691: }
692:
693: /** Called before releasing the classloader so it can itself unregister
694: * from the global ClassLoader pool */
695: public void destroy() {
696: synchronized (packageCoverage) {
697: for (Iterator<String> it = packageCoverage.keySet()
698: .iterator(); it.hasNext();) {
699: String pkg = it.next();
700: Set<ProxyClassLoader> set = packageCoverage.get(pkg);
701: if (set.contains(this ) && set.size() == 1) {
702: it.remove();
703: } else {
704: set.remove(this);
705: }
706: }
707: }
708: }
709:
710: }
|