openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTest.java
author Jaroslav Tulach <jtulach@netbeans.org>
Fri, 20 Mar 2009 16:46:35 +0100
changeset 526 519bef079589
parent 523 d8c4cf6857f0
child 542 b483c7924239
permissions -rw-r--r--
#160630: Making RecognizeInstanceObjectsTest pass by providing correct Thread.contextCL
     1 /*
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
     5  *
     6  * The contents of this file are subject to the terms of either the GNU
     7  * General Public License Version 2 only ("GPL") or the Common
     8  * Development and Distribution License("CDDL") (collectively, the
     9  * "License"). You may not use this file except in compliance with the
    10  * License. You can obtain a copy of the License at
    11  * http://www.netbeans.org/cddl-gplv2.html
    12  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    13  * specific language governing permissions and limitations under the
    14  * License.  When distributing the software, include this License Header
    15  * Notice in each file and include the License file at
    16  * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    17  * particular file as subject to the "Classpath" exception as provided
    18  * by Sun in the GPL Version 2 section of the License file that
    19  * accompanied this code. If applicable, add the following below the
    20  * License Header, with the fields enclosed by brackets [] replaced by
    21  * your own identifying information:
    22  * "Portions Copyrighted [year] [name of copyright owner]"
    23  *
    24  * Contributor(s):
    25  *
    26  * The Original Software is NetBeans. The Initial Developer of the Original
    27  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    28  * Microsystems, Inc. All Rights Reserved.
    29  *
    30  * If you wish your version of this file to be governed by only the CDDL
    31  * or only the GPL Version 2, indicate your decision by adding
    32  * "[Contributor] elects to include this software in this distribution
    33  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    34  * single choice of license, a recipient has the option to distribute
    35  * your version of this file under either the CDDL, the GPL Version 2 or
    36  * to extend the choice of license to its licensees as provided above.
    37  * However, if you add GPL Version 2 code and therefore, elected the GPL
    38  * Version 2 license, then the option applies only if the new code is
    39  * made subject to such option by the copyright holder.
    40  */
    41 
    42 package org.openide.util.lookup;
    43 
    44 import java.awt.Component;
    45 import java.io.File;
    46 import java.io.FileOutputStream;
    47 import java.io.IOException;
    48 import java.io.InputStream;
    49 import java.io.InputStreamReader;
    50 import java.lang.ref.Reference;
    51 import java.lang.ref.WeakReference;
    52 import java.net.URL;
    53 import java.net.URLClassLoader;
    54 import java.util.ArrayList;
    55 import java.util.Collection;
    56 import java.util.Collections;
    57 import java.util.Comparator;
    58 import java.util.Enumeration;
    59 import java.util.HashSet;
    60 import java.util.Iterator;
    61 import java.util.List;
    62 import java.util.Map;
    63 import java.util.Set;
    64 import java.util.TreeSet;
    65 import java.util.WeakHashMap;
    66 import java.util.jar.JarEntry;
    67 import java.util.jar.JarOutputStream;
    68 import java.util.logging.Level;
    69 import java.util.logging.Logger;
    70 import java.util.regex.Matcher;
    71 import java.util.regex.Pattern;
    72 import junit.framework.Test;
    73 import org.bar.Comparator2;
    74 import org.netbeans.junit.MockServices;
    75 import org.netbeans.junit.NbTestCase;
    76 import org.openide.util.Enumerations;
    77 import org.openide.util.Lookup;
    78 import org.openide.util.LookupEvent;
    79 import org.openide.util.LookupListener;
    80 import org.openide.util.RequestProcessor;
    81 import org.openide.util.test.MockLookup;
    82 
    83 /** Test finding services from manifest.
    84  * @author Jesse Glick
    85  */
    86 public class MetaInfServicesLookupTest extends NbTestCase {
    87     private Logger LOG;
    88     private Map<ClassLoader,Lookup> lookups = new WeakHashMap<ClassLoader,Lookup>();
    89     
    90     public MetaInfServicesLookupTest(String name) {
    91         super(name);
    92         LOG = Logger.getLogger("Test." + name);
    93     }
    94 
    95     protected String prefix() {
    96         return "META-INF/services/";
    97     }
    98     
    99     protected Lookup createLookup(ClassLoader c) {
   100         return Lookups.metaInfServices(c);
   101     }
   102     
   103     @Override
   104     protected Level logLevel() {
   105         return Level.INFO;
   106     }
   107 
   108     private Lookup getTestedLookup(ClassLoader c) {
   109         MockServices.setServices();
   110         Lookup l = lookups.get(c);
   111         if (l == null) {
   112             l = createLookup(c);
   113             lookups.put(c, l);
   114         }
   115         return l;
   116     }
   117 
   118     private URL findJar(String n) throws IOException {
   119         LOG.info("Looking for " + n);
   120         File jarDir = new File(getWorkDir(), "jars");
   121         jarDir.mkdirs();
   122         File jar = new File(jarDir, n);
   123         if (jar.exists()) {
   124             return jar.toURI().toURL();
   125         }
   126         
   127         LOG.info("generating " + jar);
   128         
   129         URL data = MetaInfServicesLookupTest.class.getResource(n.replaceAll("\\.jar", "\\.txt"));
   130         assertNotNull("Data found", data);
   131         StringBuffer sb = new StringBuffer();
   132         InputStreamReader r = new InputStreamReader(data.openStream());
   133         for(;;) {
   134             int ch = r.read();
   135             if (ch == -1) {
   136                 break;
   137             }
   138             sb.append((char)ch);
   139         }
   140         
   141         JarOutputStream os = new JarOutputStream(new FileOutputStream(jar));
   142         
   143         Pattern p = Pattern.compile(":([^:]+):([^:]*)", Pattern.MULTILINE | Pattern.DOTALL);
   144         Matcher m = p.matcher(sb);
   145         Pattern foobar = Pattern.compile("^(org\\.(foo|bar)\\..*)$", Pattern.MULTILINE);
   146         Set<String> names = new TreeSet<String>();
   147         while (m.find()) {
   148             assert m.groupCount() == 2;
   149             String entryName = prefix() + m.group(1);
   150             LOG.info("putting there entry: " + entryName);
   151             os.putNextEntry(new JarEntry(entryName));
   152             os.write(m.group(2).getBytes());
   153             os.closeEntry();
   154             
   155             Matcher fb = foobar.matcher(m.group(2));
   156             while (fb.find()) {
   157                 String clazz = fb.group(1).replace('.', '/') + ".class";
   158                 LOG.info("will copy " + clazz);
   159                 names.add(clazz);
   160             }
   161         }
   162         
   163         for (String copy : names) {
   164             os.putNextEntry(new JarEntry(copy));
   165             LOG.info("copying " + copy);
   166             InputStream from = MetaInfServicesLookupTest.class.getResourceAsStream("/" + copy);
   167             assertNotNull(copy, from);
   168             for (;;) {
   169                 int ch = from.read();
   170                 if (ch == -1) {
   171                     break;
   172                 }
   173                 os.write(ch);
   174             }
   175             from.close();
   176             os.closeEntry();
   177         }
   178         os.close();
   179         LOG.info("done " + jar);
   180         return jar.toURI().toURL();
   181     }
   182 
   183     ClassLoader c1, c2, c2a, c3, c4;
   184 
   185     @Override
   186     protected void setUp() throws Exception {
   187         clearWorkDir();
   188         ClassLoader app = getClass().getClassLoader().getParent();
   189         ClassLoader c0 = app;
   190         
   191         c1 = new URLClassLoader(new URL[] {
   192             findJar("services-jar-1.jar"),
   193         }, c0);
   194         c2 = new URLClassLoader(new URL[] {
   195             findJar("services-jar-2.jar"),
   196         }, c1);
   197         c2a = new URLClassLoader(new URL[] {
   198             findJar("services-jar-2.jar"),
   199         }, c1);
   200         c3 = new URLClassLoader(new URL[] { findJar("services-jar-2.jar") },
   201             c0
   202         );
   203         c4 = new URLClassLoader(new URL[] {
   204             findJar("services-jar-1.jar"),
   205             findJar("services-jar-2.jar"),
   206         }, c0);
   207     }
   208 
   209     @Override
   210     protected void tearDown() throws Exception {
   211         Set<Reference<Lookup>> weak = new HashSet<Reference<Lookup>>();
   212         for (Lookup l : lookups.values()) {
   213             weak.add(new WeakReference<Lookup>(l));
   214         }
   215         
   216         lookups = null;
   217         
   218         for(Reference<Lookup> ref : weak) {
   219             assertGC("Lookup can disappear", ref);
   220         }
   221     }
   222 
   223     public void testBasicUsage() throws Exception {
   224         Lookup l = getTestedLookup(c2);
   225         Class xface = c1.loadClass("org.foo.Interface");
   226         List results = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances());
   227         assertEquals("Two items in result: " + results, 2, results.size());
   228         // Note that they have to be in order:
   229         assertEquals("org.foo.impl.Implementation1", results.get(0).getClass().getName());
   230         assertEquals("org.bar.Implementation2", results.get(1).getClass().getName());
   231         // Make sure it does not gratuitously replace items:
   232         List results2 = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances());
   233         assertEquals(results, results2);
   234     }
   235 
   236     public void testLoaderSkew() throws Exception {
   237         Class xface1 = c1.loadClass("org.foo.Interface");
   238         Lookup l3 = getTestedLookup(c3);
   239         // If we cannot load Interface, there should be no impls of course... quietly!
   240         assertEquals(Collections.EMPTY_LIST,
   241                 new ArrayList(l3.lookup(new Lookup.Template(xface1)).allInstances()));
   242         Lookup l4 = getTestedLookup(c4);
   243         // If we can load Interface but it is the wrong one, ignore it.
   244         assertEquals(Collections.EMPTY_LIST,
   245                 new ArrayList(l4.lookup(new Lookup.Template(xface1)).allInstances()));
   246         // Make sure l4 is really OK - it can load from its own JARs.
   247         Class xface4 = c4.loadClass("org.foo.Interface");
   248         assertEquals(2, l4.lookup(new Lookup.Template(xface4)).allInstances().size());
   249     }
   250 
   251     public void testStability() throws Exception {
   252         Lookup l = getTestedLookup(c2);
   253         Class xface = c1.loadClass("org.foo.Interface");
   254         Object first = l.lookup(new Lookup.Template(xface)).allInstances().iterator().next();
   255         l = getTestedLookup(c2a);
   256         Object second = l.lookup(new Lookup.Template(xface)).allInstances().iterator().next();
   257         assertEquals(first, second);
   258     }
   259 
   260     public void testMaskingOfResources() throws Exception {
   261         Lookup l1 = getTestedLookup(c1);
   262         Lookup l2 = getTestedLookup(c2);
   263         Lookup l4 = getTestedLookup(c4);
   264 
   265         assertNotNull("services1.jar defines a class that implements runnable", l1.lookup(Runnable.class));
   266         assertNull("services2.jar does not defines a class that implements runnable", l2.lookup(Runnable.class));
   267         assertNull("services1.jar defines Runnable, but services2.jar masks it out", l4.lookup(Runnable.class));
   268     }
   269 
   270     public void testOrdering() throws Exception {
   271         Lookup l = getTestedLookup(c1);
   272         Class xface = c1.loadClass("java.util.Comparator");
   273         List results = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances());
   274         assertEquals(1, results.size());
   275 
   276         l = getTestedLookup(c2);
   277         xface = c2.loadClass("java.util.Comparator");
   278         results = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances());
   279         assertEquals(2, results.size());
   280         // Test order:
   281         assertEquals("org.bar.Comparator2", results.get(0).getClass().getName());
   282         assertEquals("org.foo.impl.Comparator1", results.get(1).getClass().getName());
   283 
   284         // test that items without position are always at the end
   285         l = getTestedLookup(c2);
   286         xface = c2.loadClass("java.util.Iterator");
   287         results = new ArrayList(l.lookup(new Lookup.Template(xface)).allInstances());
   288         assertEquals(2, results.size());
   289         // Test order:
   290         assertEquals("org.bar.Iterator2", results.get(0).getClass().getName());
   291         assertEquals("org.foo.impl.Iterator1", results.get(1).getClass().getName());
   292     }
   293 
   294     public void testNoCallToGetResourceForObjectIssue65124() throws Exception {
   295         class Loader extends ClassLoader {
   296             private int counter;
   297 
   298             @Override
   299             protected URL findResource(String name) {
   300                 if (name.equals(prefix() + "java.lang.Object")) {
   301                     counter++;
   302                 }
   303 
   304                 URL retValue;
   305 
   306                 retValue = super.findResource(name);
   307                 return retValue;
   308             }
   309 
   310             @Override
   311             protected Enumeration findResources(String name) throws IOException {
   312                 if (name.equals(prefix() + "java.lang.Object")) {
   313                     counter++;
   314                 }
   315                 Enumeration retValue;
   316 
   317                 retValue = super.findResources(name);
   318                 return retValue;
   319             }
   320         }
   321         Loader loader = new Loader();
   322         Lookup l = getTestedLookup(loader);
   323 
   324         Object no = l.lookup(String.class);
   325         assertNull("Not found of course", no);
   326         assertEquals("No lookup of Object", 0, loader.counter);
   327     }
   328 
   329     public void testCanGarbageCollectClasses() throws Exception {
   330         class Loader extends ClassLoader {
   331             public Loader() {
   332                 super(Loader.class.getClassLoader().getParent());
   333             }
   334 
   335             @Override
   336             protected URL findResource(String name) {
   337                 if (name.equals(prefix() + "java.lang.Runnable")) {
   338                     return Loader.class.getResource("MetaInfServicesLookupTestRunnable.txt");
   339                 }
   340 
   341                 URL retValue;
   342 
   343                 retValue = super.findResource(name);
   344                 return retValue;
   345             }
   346 
   347             @Override
   348             protected Class<?> findClass(String name) throws ClassNotFoundException {
   349                 if (name.equals("org.openide.util.lookup.MetaInfServicesLookupTestRunnable")) {
   350                     try {
   351                         InputStream is = getClass().getResourceAsStream("MetaInfServicesLookupTestRunnable.class");
   352                         byte[] arr = new byte[is.available()];
   353                         int read = is.read(arr);
   354                         assertEquals("Fully read", arr.length, read);
   355                         return defineClass(name, arr, 0, arr.length);
   356                     } catch (IOException ex) {
   357                         throw new ClassNotFoundException("Cannot load", ex);
   358                     }
   359                 }
   360                 throw new ClassNotFoundException();
   361             }
   362 
   363 
   364 
   365             @Override
   366             protected Enumeration findResources(String name) throws IOException {
   367                 if (name.equals(prefix() + "java.lang.Runnable")) {
   368                     return Enumerations.singleton(findResource(name));
   369                 }
   370                 return super.findResources(name);
   371             }
   372         }
   373         Loader loader = new Loader();
   374         Lookup l = getTestedLookup(loader);
   375 
   376 
   377         Object no = l.lookup(Runnable.class);
   378         assertNotNull("Found of course", no);
   379         assertEquals("The right name", "MetaInfServicesLookupTestRunnable", no.getClass().getSimpleName());
   380         if (no.getClass().getClassLoader() != loader) {
   381             fail("Wrong classloader: " + no.getClass().getClassLoader());
   382         }
   383 
   384         WeakReference<Object> ref = new WeakReference<Object>(no.getClass());
   385         loader = null;
   386         no = null;
   387         l = null;
   388         lookups.clear();
   389         MockLookup.setInstances();
   390         Thread.currentThread().setContextClassLoader(null);
   391         assertGC("Class can be garbage collected", ref);
   392     }
   393 
   394     public void testListenersAreNotifiedWithoutHoldingALockIssue36035() throws Exception {
   395         final Lookup l = getTestedLookup(c2);
   396         final Class xface = c1.loadClass("org.foo.Interface");
   397         final Lookup.Result res = l.lookup(new Lookup.Template(Object.class));
   398 
   399         class L implements LookupListener, Runnable {
   400             private Thread toInterrupt;
   401 
   402             public void run() {
   403                 assertNotNull("Possible to query lookup", l.lookup(xface));
   404                 assertEquals("and there are two items", 2, res.allInstances().size());
   405                 toInterrupt.interrupt();
   406             }
   407 
   408             public synchronized void resultChanged(LookupEvent ev) {
   409                 toInterrupt = Thread.currentThread();
   410                 RequestProcessor.getDefault().post(this);
   411                 try {
   412                     wait(3000);
   413                     fail("Should be interrupted - means it was not possible to finish query in run() method");
   414                 } catch (InterruptedException ex) {
   415                     // this is what we want
   416                 }
   417             }
   418         }
   419         L listener = new L();
   420 
   421         res.addLookupListener(listener);
   422         assertEquals("Nothing yet", 0, res.allInstances().size());
   423 
   424         assertNotNull("Interface found", l.lookup(xface));
   425         assertNotNull("Listener notified", listener.toInterrupt);
   426 
   427         assertEquals("Now two", 2, res.allInstances().size());
   428     }
   429     
   430     public void testWrongOrderAsInIssue100320() throws Exception {
   431         ClassLoader app = getClass().getClassLoader().getParent();
   432         ClassLoader c0 = app;
   433         ClassLoader ctmp = new URLClassLoader(new URL[] {
   434             findJar("problem100320.jar"),
   435         }, c0);
   436         Lookup lookup = Lookups.metaInfServices(ctmp, prefix());
   437 
   438         Collection<?> colAWT = lookup.lookupAll(Component.class);
   439         assertEquals("There is enough objects to switch to InheritanceTree", 12, colAWT.size());
   440         
   441         
   442         List<?> col1 = new ArrayList<Object>(lookup.lookupAll(Comparator.class));
   443         assertEquals("Two", 2, col1.size());
   444         Collection<?> col2 = lookup.lookupAll(ctmp.loadClass(Comparator2.class.getName()));
   445         assertEquals("One", 1, col2.size());
   446         List<?> col3 = new ArrayList<Object>(lookup.lookupAll(Comparator.class));
   447         assertEquals("Two2", 2, col3.size());
   448         
   449         Iterator<?> it1 = col1.iterator();
   450         Iterator<?> it3 = col3.iterator();
   451         if (
   452             it1.next() != it3.next() || 
   453             it1.next() != it3.next() 
   454         ) {
   455             fail("Collections are different:\nFirst: " + col1 + "\nLast:  " + col3);
   456         }
   457     }
   458 }