Index: core/src/org/netbeans/core/NbTopManager.java =================================================================== RCS file: /cvs/core/src/org/netbeans/core/NbTopManager.java,v --- core/src/org/netbeans/core/NbTopManager.java 29 Jan 2002 15:45:07 -0000 1.132 +++ core/src/org/netbeans/core/NbTopManager.java 30 Jan 2002 10:01:38 -0000 @@ -18,6 +18,7 @@ import java.awt.event.*; import java.beans.*; import java.io.*; +import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; import java.util.Enumeration; @@ -40,6 +41,8 @@ import org.openide.filesystems.*; import org.openide.filesystems.FileSystem; import org.openide.filesystems.JarFileSystem; +import org.openide.modules.Dependency; +import org.openide.modules.SpecificationVersion; import org.openide.options.ControlPanel; import org.openide.windows.WindowManager; import org.openide.windows.OutputWriter; @@ -59,13 +62,12 @@ import org.netbeans.core.windows.WindowManagerImpl; import org.netbeans.core.compiler.CompilationEngineImpl; import org.netbeans.core.perftool.StartLog; +import org.netbeans.core.modules.ModuleManager; import org.netbeans.core.modules.ModuleSystem; -import org.openide.modules.Dependency; -import org.openide.modules.SpecificationVersion; /** This class is a TopManager for Corona environment. * -* @author Ales Novak, Jaroslav Tulach, Ian Formanek, Petr Hamernik, Jan Jancura +* @author Ales Novak, Jaroslav Tulach, Ian Formanek, Petr Hamernik, Jan Jancura, Jesse Glick */ public abstract class NbTopManager extends TopManager { /* masks to define the interactivity level */ @@ -150,7 +152,7 @@ // Set up module-versioning properties, which logger prints. Package p = Package.getPackage ("org.openide"); // NOI18N - putSystemProperty ("org.openide.specification.version", p.getSpecificationVersion (), "1.1.6"); // NOI18N + putSystemProperty ("org.openide.specification.version", p.getSpecificationVersion (), "2.3"); // NOI18N putSystemProperty ("org.openide.version", p.getImplementationVersion (), "OwnBuild"); // NOI18N putSystemProperty ("org.openide.major.version", p.getSpecificationTitle (), "IDE/1"); // NOI18N putSystemProperty ("netbeans.buildnumber", p.getImplementationVersion (), "OwnBuild"); // NOI18N @@ -938,18 +940,40 @@ public Lkp () { super (new Lookup[] { new org.netbeans.core.lookup.TMLookup(), + // #14722: pay attention also to META-INF/services/class.Name resources: + createMetaInfServicesLookup(false), createInitialErrorManagerLookup(), }); //System.err.println("creating default lookup"); } + /** @param modules if true, use module classloader, else not */ + private static Lookup createMetaInfServicesLookup(boolean modules) { + try { + Class clazz = Class.forName("org.openide.util.MetaInfServicesLookup"); // NOI18N + Constructor c = clazz.getDeclaredConstructor(new Class[] {ClassLoader.class}); + c.setAccessible(true); + ClassLoader loader; + if (modules) { + loader = get().getModuleSystem().getManager().getClassLoader(); + } else { + loader = Lkp.class.getClassLoader(); + } + return (Lookup)c.newInstance(new Object[] {loader}); + } catch (Exception e) { + e.printStackTrace(); + return Lookup.EMPTY; + } + } + private static Lookup createInitialErrorManagerLookup() { InstanceContent c = new InstanceContent(); - c.add(Boolean.TRUE, new InitialErrorManagerConvertor()); + c.add(Boolean.TRUE, new ConvertorListener()); return new AbstractLookup(c); } - private static final class InitialErrorManagerConvertor implements InstanceContent.Convertor, TaskListener { + private static final class ConvertorListener + implements InstanceContent.Convertor, TaskListener, PropertyChangeListener { public Object convert(Object obj) { //System.err.println("IEMC.convert"); return getDefaultErrorManager(); @@ -972,19 +996,34 @@ if (lookup instanceof Lkp) { Lkp lkp = (Lkp)lookup; Lookup[] old = lkp.getLookups(); - if (old.length != 5) throw new IllegalStateException(); + if (old.length != 6) throw new IllegalStateException(); Lookup[] nue = new Lookup[] { old[0], // TMLookup + old[1], // metaInfServicesLookup // do NOT include initialErrorManagerLookup; this is now replaced by the layer entry // Services/Hidden/org-netbeans-core-default-error-manager.instance - old[2], // NbTM.instanceLookup - old[3], // FolderLookup - old[4], // moduleLookup + old[3], // NbTM.instanceLookup + old[4], // FolderLookup + old[5], // moduleLookup }; lkp.setLookups(nue); } } - + public void propertyChange(PropertyChangeEvent evt) { + //System.err.println("modules changed; changing metaInfServicesLookup"); + if (ModuleManager.PROP_ENABLED_MODULES.equals(evt.getPropertyName())) { + // Time to refresh META-INF/services/ lookup; modules turned on or off. + Lookup lookup = Lookup.getDefault(); + if (lookup instanceof Lkp) { + Lkp lkp = (Lkp)lookup; + Lookup[] old = lkp.getLookups(); + Lookup[] nue = (Lookup[])old.clone(); + nue[1] = createMetaInfServicesLookup(true); + lkp.setLookups(nue); + //System.err.println("lookups: " + java.util.Arrays.asList(arr)); + } + } + } } /** When all module classes are accessible thru systemClassLoader, this @@ -1011,15 +1050,17 @@ StartLog.logProgress ("Got Services folder"); // NOI18N FolderLookup folder = new FolderLookup (df, "SL["); // NOI18N - folder.addTaskListener(new InitialErrorManagerConvertor()); + folder.addTaskListener(new ConvertorListener()); StartLog.logProgress ("created FolderLookup"); // NOI18N // extend the lookup Lookup[] arr = new Lookup[] { - lkp.getLookups ()[0], + lkp.getLookups ()[0], // TMLookup + // replace metaInfServicesLookup with a new one from modules + createMetaInfServicesLookup(true), // Include initialErrorManagerLookup provisionally, until the folder lookup // is actually ready and usable - lkp.getLookups()[1], + lkp.getLookups()[2], // initialErrorManagerLookup NbTopManager.get ().getInstanceLookup (), folder.getLookup (), NbTopManager.get().getModuleSystem().getManager().getModuleLookup(), @@ -1028,6 +1069,9 @@ lkp.setLookups (arr); StartLog.logProgress ("Lookups set"); // NOI18N + + // Also listen for changes in modules, as META-INF/services/ would change: + get().getModuleSystem().getManager().addPropertyChangeListener(new ConvertorListener()); } catch (java.io.IOException ex) { ex.printStackTrace(); throw new IllegalStateException ("Cannot initialize folder Services"); // NOI18N Index: openide/src/org/openide/util/Lookup.java =================================================================== RCS file: /cvs/openide/src/org/openide/util/Lookup.java,v --- openide/src/org/openide/util/Lookup.java 29 Jan 2002 11:20:23 -0000 1.12 +++ openide/src/org/openide/util/Lookup.java 30 Jan 2002 10:01:39 -0000 @@ -47,7 +47,7 @@ ); if (className == null) { - defaultLookup = EMPTY; + defaultLookup = new MetaInfServicesLookup(); return defaultLookup; } @@ -55,7 +55,7 @@ Class c = Class.forName(className); defaultLookup = (Lookup)c.newInstance(); } catch (Exception ex) { - defaultLookup = EMPTY; + defaultLookup = new MetaInfServicesLookup(); ex.printStackTrace(); throw new InternalError (); } Index: openide/src/org/openide/util/MetaInfServicesLookup.java =================================================================== RCS file: openide/src/org/openide/util/MetaInfServicesLookup.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/src/org/openide/util/MetaInfServicesLookup.java 30 Jan 2002 10:01:39 -0000 @@ -0,0 +1,244 @@ +/* + * 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-2002 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.util; + +import java.net.URL; +import java.util.*; +import java.io.*; + +import org.openide.ErrorManager; +import org.openide.util.lookup.*; + +/** A lookup that implements the JDK1.3 JAR services mechanism and delegates + * to META-INF/services/name.of.class files. + *

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. + *

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 { + + private static final Map knownInstances = new WeakHashMap(); // Map + + /** 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 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; + } + + + /* 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) { + String res = "META-INF/services/" + clazz.getName(); // NOI18N + Enumeration en; + try { + en = loader.getResources(res); + } catch (IOException ioe) { + ErrorManager.getDefault().notify(ioe); + 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... + Set foundClasses = new HashSet(); // Set + + 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. + 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; + if (line.charAt(0) == '#') continue; // NOI18N + + // Most lines are fully-qualified class names. + Class inst = Class.forName(line, false, loader); + if (!clazz.isAssignableFrom(inst)) { + throw new ClassNotFoundException(inst.getName() + " not a subclass of " + clazz.getName()); // NOI18N + } + if (foundClasses.add(inst)) { + result.add(new P(inst)); + } + } + } finally { + is.close(); + } + } catch (ClassNotFoundException ex) { + ErrorManager.getDefault().notify(ex); + } catch (IOException ex) { + ErrorManager.getDefault().notify(ex); + } + } + } + + + /** 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() { + synchronized (knownInstances) { + if (object instanceof Class) { + try { + Class c = ((Class)object); + object = knownInstances.get(c); + if (object == null) { + object = c.newInstance(); + knownInstances.put(c, object); + } + } catch (Exception ex) { + ErrorManager.getDefault().notify(ex); + object = null; + } + } + return object; + } + } + + public String getDisplayName() { + return clazz().getName(); + } + + public String getId() { + return clazz().getName(); + } + + protected boolean creatorOf(Object obj) { + return obj == object; + } + + } + +} Index: core/test/unit/src/org/netbeans/core/lookup/MetaInfServicesTest.java =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/MetaInfServicesTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/MetaInfServicesTest.java 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,180 @@ +/* + * 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-2002 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.netbeans.core.lookup; + +import org.netbeans.junit.*; +import junit.textui.TestRunner; +import org.netbeans.core.NbTopManager; +import org.netbeans.core.modules.*; +import org.openide.TopManager; +import org.openide.util.*; +import java.io.File; +import java.util.*; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.SAXParserFactory; + +/** Test whether modules can really register things in their META-INF/services/class.Name + * files, and whether this behaves correctly when the modules are disabled/enabled. + * Note that Plain loads its classpath modules as soon as you ask for it, so these + * tests do not check what happens on the NetBeans startup classpath. + * @author Jesse Glick + */ +public class MetaInfServicesTest extends NbTestCase { + + public MetaInfServicesTest(String name) { + super(name); + } + + public static void main(String[] args) throws Exception { + File temp = File.createTempFile("MetaInfServicesTest", ".dummy"); + System.setProperty("nbjunit.workdir", new File(temp.getParentFile(), temp.getName().substring(0, temp.getName().length() - 6)).getAbsolutePath()); + TestRunner.run(new NbTestSuite(MetaInfServicesTest.class)); + } + + private ModuleManager mgr; + private Module m1, m2; + protected void setUp() throws Exception { + //System.err.println("setUp"); + //Thread.dumpStack(); + clearWorkDir(); + // Load Plain. + TopManager.getDefault(); + // Make a couple of modules. + mgr = NbTopManager.get().getModuleSystem().getManager(); + try { + mgr.mutex().writeAccess(new Mutex.ExceptionAction() { + public Object run() throws Exception { + File jar1 = new File(MetaInfServicesTest.class.getResource("data/services-jar-1.jar").getPath()); + File jar2 = new File(MetaInfServicesTest.class.getResource("data/services-jar-2.jar").getPath()); + m1 = mgr.create(jar1, new ModuleHistory(), false, false, false); + m2 = mgr.create(jar2, new ModuleHistory(), false, false, false); + return null; + } + }); + } catch (MutexException me) { + throw me.getException(); + } + assertEquals(Collections.EMPTY_SET, m1.getProblems()); + } + protected void tearDown() throws Exception { + try { + mgr.mutex().writeAccess(new Mutex.ExceptionAction() { + public Object run() throws Exception { + if (m2.isEnabled()) mgr.disable(m2); + mgr.delete(m2); + if (m1.isEnabled()) mgr.disable(m1); + mgr.delete(m1); + return null; + } + }); + } catch (MutexException me) { + throw me.getException(); + } + m1 = null; + m2 = null; + mgr = null; + } + protected static final int TWIDDLE_ENABLE = 0; + protected static final int TWIDDLE_DISABLE = 1; + protected void twiddle(final Module m, final int action) throws Exception { + try { + mgr.mutex().writeAccess(new Mutex.ExceptionAction() { + public Object run() throws Exception { + switch (action) { + case TWIDDLE_ENABLE: + mgr.enable(m); + break; + case TWIDDLE_DISABLE: + mgr.disable(m); + break; + default: + throw new IllegalArgumentException("bad action: " + action); + } + return null; + } + }); + } catch (MutexException me) { + throw me.getException(); + } + } + + /** Fails to work if you have >1 method per class, because setUp gets run more + * than once (XTest bug I suppose). + */ + public void testEverything() throws Exception { + twiddle(m1, TWIDDLE_ENABLE); + Class xface = TopManager.getDefault().systemClassLoader().loadClass("org.foo.Interface"); + Lookup.Result r = Lookup.getDefault().lookup(new Lookup.Template(xface)); + List instances = new ArrayList(r.allInstances()); + // Expect to get Impl1 from first JAR. + assertEquals(1, instances.size()); + Object instance1 = instances.get(0); + assertTrue(xface.isInstance(instance1)); + assertEquals("org.foo.impl.Implementation1", instance1.getClass().getName()); + // Expect to have (same) Impl1 + Impl2. + LookupL l = new LookupL(); + r.addLookupListener(l); + twiddle(m2, TWIDDLE_ENABLE); + assert(l.gotSomething()); + instances = new ArrayList(r.allInstances()); + assertEquals(2, instances.size()); + assertEquals(instance1, instances.get(0)); + assertEquals("org.bar.Implementation2", instances.get(1).getClass().getName()); + // Expect to lose Impl2. + l.count = 0; + twiddle(m2, TWIDDLE_DISABLE); + assert(l.gotSomething()); + instances = new ArrayList(r.allInstances()); + assertEquals(1, instances.size()); + assertEquals(instance1, instances.get(0)); + // Expect to lose Impl1 too. + l.count = 0; + twiddle(m1, TWIDDLE_DISABLE); + assert(l.gotSomething()); + instances = new ArrayList(r.allInstances()); + assertEquals(0, instances.size()); + // Expect to not get anything: wrong xface version + l.count = 0; + twiddle(m1, TWIDDLE_ENABLE); + // not really important: assert(!l.gotSomething()); + instances = new ArrayList(r.allInstances()); + assertEquals(0, instances.size()); + Class xface2 = TopManager.getDefault().systemClassLoader().loadClass("org.foo.Interface"); + assertTrue(xface != xface2); + Lookup.Result r2 = Lookup.getDefault().lookup(new Lookup.Template(xface2)); + instances = new ArrayList(r2.allInstances()); + assertEquals(1, instances.size()); + // Let's also check up on XML providers, which ought to be in the classpath. + DocumentBuilderFactory dbf = (DocumentBuilderFactory)Lookup.getDefault().lookup(DocumentBuilderFactory.class); + assertNotNull(dbf); + SAXParserFactory spf = (SAXParserFactory)Lookup.getDefault().lookup(SAXParserFactory.class); + assertNotNull(spf); + //System.err.println("dbf=" + dbf + " spf=" + spf); + //System.err.println("all DBFs=" + Lookup.getDefault().lookup(new Lookup.Template(DocumentBuilderFactory.class)).allInstances()); + } + + protected static final class LookupL implements LookupListener { + public int count = 0; + public synchronized void resultChanged(LookupEvent ev) { + count++; + notifyAll(); + } + public synchronized boolean gotSomething() throws InterruptedException { + if (count > 0) return true; + wait(9999); + return count > 0; + } + } + +} Index: core/test/unit/src/org/netbeans/core/lookup/data/build.xml =================================================================== RCS file: /cvs/core/test/unit/src/org/netbeans/core/lookup/data/build.xml,v --- core/test/unit/src/org/netbeans/core/lookup/data/build.xml 22 Oct 2001 19:47:17 -0000 1.2 +++ core/test/unit/src/org/netbeans/core/lookup/data/build.xml 30 Jan 2002 10:01:40 -0000 @@ -8,12 +8,12 @@ http://www.sun.com/ The Original Code is NetBeans. The Initial Developer of the Original -Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun +Code is Sun Microsystems, Inc. Portions Copyright 1997-2002 Sun Microsystems, Inc. All Rights Reserved. --> - + @@ -28,11 +28,25 @@ + + + + + + + + + + + + + + - + Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1.mf =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1.mf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1.mf 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,5 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.foo +OpenIDE-Module-Name: Interface & Impl #1 +OpenIDE-Module-IDE-Dependencies: IDE/1 > 2.2 + Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2.mf =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2.mf --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2.mf 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,6 @@ +Manifest-Version: 1.0 +OpenIDE-Module: org.bar +OpenIDE-Module-Name: Impl #2 +OpenIDE-Module-Module-Dependencies: org.foo +OpenIDE-Module-IDE-Dependencies: IDE/1 > 2.2 + Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/META-INF/services/org.foo.Interface =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/META-INF/services/org.foo.Interface --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/META-INF/services/org.foo.Interface 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,4 @@ +# Some header info, maybe. + +# Our first impl here: +org.foo.impl.Implementation1 Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/Interface.java =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/Interface.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/Interface.java 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,2 @@ +package org.foo; +public interface Interface {} Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/impl/Implementation1.java =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/impl/Implementation1.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-1/org/foo/impl/Implementation1.java 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,3 @@ +package org.foo.impl; +import org.foo.Interface; +public class Implementation1 implements Interface {} Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/META-INF/services/org.foo.Interface =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/META-INF/services/org.foo.Interface --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/META-INF/services/org.foo.Interface 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1, @@ +org.bar.Implementation2 Index: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/org/bar/Implementation2.java =================================================================== RCS file: core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/org/bar/Implementation2.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ core/test/unit/src/org/netbeans/core/lookup/data/services-jar-2/org/bar/Implementation2.java 30 Jan 2002 10:01:40 -0000 @@ -0,0 +1,3 @@ +package org.bar; +import org.foo.Interface; +public class Implementation2 implements Interface {} Index: openide/test/build.xml =================================================================== RCS file: /cvs/openide/test/build.xml,v --- openide/test/build.xml 24 Jan 2002 17:37:44 -0000 1.33 +++ openide/test/build.xml 30 Jan 2002 10:01:40 -0000 @@ -55,6 +55,7 @@ + @@ -77,6 +78,8 @@ + + Index: openide/test/unit/src/org/openide/util/MetaInfServicesLookupTest.java =================================================================== RCS file: openide/test/unit/src/org/openide/util/MetaInfServicesLookupTest.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/MetaInfServicesLookupTest.java 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,92 @@ +/* + * 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-2002 Sun + * Microsystems, Inc. All Rights Reserved. + */ + +package org.openide.util; + +import org.netbeans.junit.*; +import junit.textui.TestRunner; +import java.net.*; +import java.util.*; + +/** Test finding services from manifest. + * @author Jesse Glick + */ +public class MetaInfServicesLookupTest extends NbTestCase { + + public MetaInfServicesLookupTest(String name) { + super(name); + } + + public static void main(String[] args) { + TestRunner.run(new NbTestSuite(MetaInfServicesLookupTest.class)); + } + + ClassLoader c1, c2, c2a, c3, c4; + + protected void setUp() throws Exception { + c1 = new URLClassLoader(new URL[] { + MetaInfServicesLookupTest.class.getResource("data/services-jar-1.jar"), + }); + c2 = new URLClassLoader(new URL[] { + MetaInfServicesLookupTest.class.getResource("data/services-jar-2.jar"), + }, c1); + c2a = new URLClassLoader(new URL[] { + MetaInfServicesLookupTest.class.getResource("data/services-jar-2.jar"), + }, c1); + c3 = new URLClassLoader(new URL[] { + MetaInfServicesLookupTest.class.getResource("data/services-jar-2.jar"), + }); + c4 = new URLClassLoader(new URL[] { + MetaInfServicesLookupTest.class.getResource("data/services-jar-1.jar"), + MetaInfServicesLookupTest.class.getResource("data/services-jar-2.jar"), + }); + } + + public void testBasicUsage() throws Exception { + Lookup l = new MetaInfServicesLookup(c2); + Class xface = c1.loadClass("org.foo.Interface"); + List results = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances()); + assertEquals(2, results.size()); + // Note that they have to be in order: + assertEquals("org.foo.impl.Implementation1", results.get(0).getClass().getName()); + assertEquals("org.bar.Implementation2", results.get(1).getClass().getName()); + // Make sure it does not gratuitously replace items: + List results2 = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances()); + assertEquals(results, results2); + } + + public void testLoaderSkew() throws Exception { + Class xface1 = c1.loadClass("org.foo.Interface"); + Lookup l3 = new MetaInfServicesLookup(c3); + // If we cannot load Interface, there should be no impls of course... quietly! + assertEquals(Collections.EMPTY_LIST, + new ArrayList(l3.lookup(new Lookup.Template(xface1)).allInstances())); + Lookup l4 = new MetaInfServicesLookup(c4); + // If we can load Interface but it is the wrong one, ignore it. + assertEquals(Collections.EMPTY_LIST, + new ArrayList(l4.lookup(new Lookup.Template(xface1)).allInstances())); + // Make sure l4 is really OK - it can load from its own JARs. + Class xface4 = c4.loadClass("org.foo.Interface"); + assertEquals(2, l4.lookup(new Lookup.Template(xface4)).allInstances().size()); + } + + public void testStability() throws Exception { + Lookup l = new MetaInfServicesLookup(c2); + Class xface = c1.loadClass("org.foo.Interface"); + Object first = l.lookup(new Lookup.Template(xface)).allInstances().iterator().next(); + l = new MetaInfServicesLookup(c2a); + Object second = l.lookup(new Lookup.Template(xface)).allInstances().iterator().next(); + assertEquals(first, second); + } + +} Index: openide/test/unit/src/org/openide/util/data/.cvsignore =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/.cvsignore --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/.cvsignore 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1, @@ +*.jar Index: openide/test/unit/src/org/openide/util/data/build.xml =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/build.xml --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/build.xml 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + Index: openide/test/unit/src/org/openide/util/data/services-jar-1/META-INF/services/org.foo.Interface =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/services-jar-1/META-INF/services/org.foo.Interface --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/services-jar-1/META-INF/services/org.foo.Interface 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,4 @@ +# Some header info, maybe. + +# Our first impl here: +org.foo.impl.Implementation1 Index: openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/Interface.java =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/Interface.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/Interface.java 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,2 @@ +package org.foo; +public interface Interface {} Index: openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/impl/Implementation1.java =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/impl/Implementation1.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/services-jar-1/org/foo/impl/Implementation1.java 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,3 @@ +package org.foo.impl; +import org.foo.Interface; +public class Implementation1 implements Interface {} Index: openide/test/unit/src/org/openide/util/data/services-jar-2/META-INF/services/org.foo.Interface =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/services-jar-2/META-INF/services/org.foo.Interface --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/services-jar-2/META-INF/services/org.foo.Interface 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1, @@ +org.bar.Implementation2 Index: openide/test/unit/src/org/openide/util/data/services-jar-2/org/bar/Implementation2.java =================================================================== RCS file: openide/test/unit/src/org/openide/util/data/services-jar-2/org/bar/Implementation2.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ openide/test/unit/src/org/openide/util/data/services-jar-2/org/bar/Implementation2.java 30 Jan 2002 10:01:41 -0000 @@ -0,0 +1,3 @@ +package org.bar; +import org.foo.Interface; +public class Implementation2 implements Interface {}