/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.util.lookup; import java.net.URL; import java.util.*; import java.io.*; import org.openide.util.*; /** A lookup that implements the JDK1.3 JAR services mechanism and delegates * to META-INF/services/name.of.class files. * <p>It is not dynamic - so if you need to change the classloader or JARs, * wrap it in a ProxyLookup and change the delegate when necessary. * Existing instances will be kept if the implementation classes are unchanged, * so there is "stability" in doing this provided some parent loaders are the same * as the previous ones. * <p>If this is to be made public, please move it to the org.openide.util.lookup * package; currently used by the core via reflection, until it is needed some * other way. * @author Jaroslav Tulach, Jesse Glick * @see "#14722" */ final class MetaInfServicesLookup extends AbstractLookup { // Better not to use ErrorManager here - EM.gD will use this class, might cause cycles etc. private static final boolean DEBUG = Boolean.getBoolean("org.openide.util.lookup.MetaInfServicesLookup.DEBUG"); // NOI18N private static final Map knownInstances = new WeakHashMap(); // Map<Class,Object> /** A set of all requested classes. * Note that classes that we actually succeeded on can never be removed * from here because we hold a strong reference to the loader. * However we also hold classes which are definitely not loadable by * our loader. */ private final Set classes = new WeakSet(); // Set<Class> /** class loader to use */ private final ClassLoader loader; /** Create a lookup reading from the classpath. * That is, the same classloader as this class itself. */ public MetaInfServicesLookup() { this(MetaInfServicesLookup.class.getClassLoader()); } /** Create a lookup reading from a specified classloader. */ public MetaInfServicesLookup(ClassLoader loader) { this.loader = loader; if (DEBUG) System.err.println("Created: " + this); // NOI18N } public String toString() { return "MetaInfServicesLookup[" + loader + "]"; // NOI18N } /* Tries to load appropriate resources from manifest files. */ protected synchronized final void beforeLookup(Lookup.Template t) { Class c = t.getType(); if (classes.add(c)) { // Added new class, search for it. List arr = new ArrayList(); search(c, arr); Iterator it = arr.iterator(); while (it.hasNext()) { Pair p = (Pair)it.next(); addPair(p); } } } /** Finds all pairs and adds them to the collection. * * @param clazz class to find * @param result collection to add Pair to */ private void search(Class clazz, Collection result) { if (DEBUG) System.err.println("Searching for " + clazz.getName() + " in " + clazz.getClassLoader() + " from " + this); // NOI18N String res = "META-INF/services/" + clazz.getName(); // NOI18N Enumeration en; try { en = loader.getResources(res); } catch (IOException ioe) { // do not use ErrorManager because we are in the startup code // and ErrorManager might not be ready ioe.printStackTrace(); return; } // Do not create multiple instances in case more than one JAR // has the same entry in it (and they load to the same class). // Probably would not happen, assuming JARs only list classes // they own, but just in case... Collection foundClasses = new ArrayList (); // Collection<Class> Collection removeClasses = new ArrayList (); // Collection<Class> boolean foundOne = false; while (en.hasMoreElements()) { if (!foundOne) { foundOne = true; // Double-check that in fact we can load the *interface* class. // For example, say class I is defined in two JARs, J1 and J2. // There is also an implementation M1 defined in J1, and another // implementation M2 defined in J2. // Classloaders C1 and C2 are made from J1 and J2. // A MetaInfServicesLookup is made from C1. Then the user asks to // lookup I as loaded from C2. J1 has the services line and lists // M1, and we can in fact make it. However it is not of the desired // type to be looked up. Don't do this check, which could be expensive, // unless we expect to be getting some results, however. Class realMcCoy = null; try { realMcCoy = loader.loadClass(clazz.getName()); } catch (ClassNotFoundException cnfe) { // our loader does not know about it, OK } if (realMcCoy != clazz) { // Either the interface class is not available at all in our loader, // or it is not the same version as we expected. Don't provide results. if (DEBUG) { if (realMcCoy != null) { System.err.println(clazz.getName() + " is not the real McCoy! Actually found it in " + realMcCoy.getClassLoader()); // NOI18N } else { System.err.println(clazz.getName() + " could not be found in " + loader); // NOI18N } } return; } } URL url = (URL)en.nextElement(); try { InputStream is = url.openStream(); try { BufferedReader reader = new BufferedReader(new InputStreamReader(is, "UTF-8")); // NOI18N while (true) { String line = reader.readLine(); if (line == null) break; // Ignore blank lines and comments. line = line.trim(); if (line.length() == 0) continue; boolean remove = false; if (line.charAt(0) == '#') { if (line.length () == 1 || line.charAt (1) != '-') { continue; } // line starting with #- is a sign to remove that class from lookup remove = true; line = line.substring (2); } Class inst = null; try { // Most lines are fully-qualified class names. inst = Class.forName(line, false, loader); } catch (ClassNotFoundException cnfe) { if (remove) { // if we are removing somthing and the something // cannot be found it is ok to do nothing continue; } else { // but if we are not removing just rethrow throw cnfe; } } if (!clazz.isAssignableFrom(inst)) { if (DEBUG) System.err.println("Not a subclass"); // NOI18N throw new ClassNotFoundException(inst.getName() + " not a subclass of " + clazz.getName()); // NOI18N } if (remove) { removeClasses.add (inst); } else { foundClasses.add(inst); } } } finally { is.close(); } } catch (ClassNotFoundException ex) { // do not use ErrorManager because we are in the startup code // and ErrorManager might not be ready ex.printStackTrace(); } catch (IOException ex) { // do not use ErrorManager because we are in the startup code // and ErrorManager might not be ready ex.printStackTrace(); } } if (DEBUG) System.err.println("Found impls of " + clazz.getName() + ": " + foundClasses + " and removed: " + removeClasses + " from: " + this); // NOI18N foundClasses.removeAll (removeClasses); Iterator it = foundClasses.iterator (); while (it.hasNext ()) { Class inst = (Class)it.next (); result.add(new P(inst)); } } /** Pair that holds name of a class and maybe the instance. */ private static final class P extends Pair { /** May be one of three things: * 1. The implementation class which was named in the services file. * 2. An instance of it. * 3. Null, if creation of the instance resulted in an error. */ private Object object; public P(Class clazz) { this.object = clazz; } /** Finds the class. */ private Class clazz() { Object o = object; if (o instanceof Class) { return (Class)o; } else if (o != null) { return o.getClass(); } else { // Broken. return Object.class; } } public boolean equals(Object o) { if (o instanceof P) { return ((P)o).clazz().equals(clazz()); } return false; } public int hashCode() { return clazz().hashCode(); } protected boolean instanceOf(Class c) { return c.isAssignableFrom(clazz()); } public Class getType() { return clazz(); } public Object getInstance() { Object o = object; // keeping local copy to avoid another // thread to modify it under my hands if (o instanceof Class) { synchronized (o) { // o is Class and we will not create // 2 instances of the same class try { Class c = ((Class)o); synchronized (knownInstances) { // guards only the static cache o = knownInstances.get(c); } if (o == null) { o = c.newInstance(); synchronized (knownInstances) { // guards only the static cache knownInstances.put(c, o); } } // Do not assign to instance var unless there is a complete synch // block between the newInstance and this line. Otherwise we could // be assigning a half-constructed instance that another thread // could see and return immediately. object = o; } catch (Exception ex) { // do not use ErrorManager because we are in the startup code // and ErrorManager might not be ready ex.printStackTrace(); object = null; } } } return object; } public String getDisplayName() { return clazz().getName(); } public String getId() { return clazz().getName(); } protected boolean creatorOf(Object obj) { return obj == object; } } }