#156692: Manual implementation of linear hashing. Allows garbage collection of Class objects. golden
authorJaroslav Tulach <jtulach@netbeans.org>
Thu, 12 Mar 2009 20:30:58 +0100
changeset 523d8c4cf6857f0
parent 522 7959193556a8
child 524 95f75a332024
child 901 d0ba52c6a6f7
#156692: Manual implementation of linear hashing. Allows garbage collection of Class objects.
openide.util/src/org/openide/util/lookup/MetaInfServicesLookup.java
openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTest.java
openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTestRunnable.java
openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTestRunnable.txt
openide.util/test/unit/src/org/openide/util/lookup/NamedServicesLookupTest.java
     1.1 --- a/openide.util/src/org/openide/util/lookup/MetaInfServicesLookup.java	Thu Mar 12 10:21:19 2009 +0100
     1.2 +++ b/openide.util/src/org/openide/util/lookup/MetaInfServicesLookup.java	Thu Mar 12 20:30:58 2009 +0100
     1.3 @@ -45,6 +45,8 @@
     1.4  import java.io.IOException;
     1.5  import java.io.InputStream;
     1.6  import java.io.InputStreamReader;
     1.7 +import java.lang.ref.Reference;
     1.8 +import java.lang.ref.WeakReference;
     1.9  import java.net.URL;
    1.10  import java.util.ArrayList;
    1.11  import java.util.Collection;
    1.12 @@ -52,9 +54,7 @@
    1.13  import java.util.HashSet;
    1.14  import java.util.LinkedHashSet;
    1.15  import java.util.List;
    1.16 -import java.util.Map;
    1.17  import java.util.Set;
    1.18 -import java.util.WeakHashMap;
    1.19  import java.util.logging.Level;
    1.20  import java.util.logging.Logger;
    1.21  import org.openide.util.Lookup;
    1.22 @@ -69,8 +69,14 @@
    1.23  final class MetaInfServicesLookup extends AbstractLookup {
    1.24  
    1.25      private static final Logger LOGGER = Logger.getLogger(MetaInfServicesLookup.class.getName());
    1.26 -
    1.27 -    private static final Map<Class,Object> knownInstances = new WeakHashMap<Class,Object>();
    1.28 +    private static int knownInstancesCount;
    1.29 +    private static final List<Reference<Object>> knownInstances;
    1.30 +    static {
    1.31 +        knownInstances = new ArrayList<Reference<Object>>();
    1.32 +        for (int i = 0; i < 512; i++) {
    1.33 +            knownInstances.add(null);
    1.34 +        }
    1.35 +    }
    1.36  
    1.37      /** A set of all requested classes.
    1.38       * Note that classes that we actually succeeded on can never be removed
    1.39 @@ -94,12 +100,14 @@
    1.40          LOGGER.log(Level.FINE, "Created: {0}", this);
    1.41      }
    1.42  
    1.43 +    @Override
    1.44      public String toString() {
    1.45          return "MetaInfServicesLookup[" + loader + "]"; // NOI18N
    1.46      }
    1.47  
    1.48      /* Tries to load appropriate resources from manifest files.
    1.49       */
    1.50 +    @Override
    1.51      protected final void beforeLookup(Lookup.Template t) {
    1.52          Class c = t.getType();
    1.53  
    1.54 @@ -381,6 +389,7 @@
    1.55              }
    1.56          }
    1.57  
    1.58 +        @Override
    1.59          public boolean equals(Object o) {
    1.60              if (o instanceof P) {
    1.61                  return ((P) o).clazz().equals(clazz());
    1.62 @@ -389,6 +398,7 @@
    1.63              return false;
    1.64          }
    1.65  
    1.66 +        @Override
    1.67          public int hashCode() {
    1.68              return clazz().hashCode();
    1.69          }
    1.70 @@ -411,9 +421,25 @@
    1.71  
    1.72                      try {
    1.73                          Class<?> c = ((Class) o);
    1.74 +                        o = null;
    1.75  
    1.76                          synchronized (knownInstances) { // guards only the static cache
    1.77 -                            o = knownInstances.get(c);
    1.78 +                            int size = knownInstances.size();
    1.79 +                            int index = c.hashCode() % size;
    1.80 +                            for (int i = 0; i < size; i++) {
    1.81 +                                Reference<Object> ref = knownInstances.get(index);
    1.82 +                                Object obj = ref == null ? null : ref.get();
    1.83 +                                if (obj == null) {
    1.84 +                                    break;
    1.85 +                                }
    1.86 +                                if (c == obj.getClass()) {
    1.87 +                                    o = obj;
    1.88 +                                    break;
    1.89 +                                }
    1.90 +                                if (++index == size) {
    1.91 +                                    index = 0;
    1.92 +                                }
    1.93 +                            }
    1.94                          }
    1.95  
    1.96                          if (o == null) {
    1.97 @@ -424,7 +450,32 @@
    1.98                              }
    1.99  
   1.100                              synchronized (knownInstances) { // guards only the static cache
   1.101 -                                knownInstances.put(c, o);
   1.102 +                                hashPut(o);
   1.103 +
   1.104 +                                int size = knownInstances.size();
   1.105 +                                if (knownInstancesCount > size * 2 / 3) {
   1.106 +                                    LOGGER.log(Level.CONFIG, "Cache of size {0} is 2/3 full. Rehashing.", size);
   1.107 +                                    HashSet<Reference<Object>> all = new HashSet<Reference<Object>>();
   1.108 +                                    all.addAll(knownInstances);
   1.109 +                                    for (int i = 0; i < size; i++) {
   1.110 +                                        knownInstances.set(i, null);
   1.111 +                                    }
   1.112 +                                    for (int i = 0; i < size; i++) {
   1.113 +                                        knownInstances.add(null);
   1.114 +                                    }
   1.115 +                                    knownInstancesCount = 0;
   1.116 +                                    for (Reference<Object> r : all) {
   1.117 +                                        if (r == null) {
   1.118 +                                            continue;
   1.119 +                                        }
   1.120 +                                        Object instance = r.get();
   1.121 +                                        if (instance == null) {
   1.122 +                                            continue;
   1.123 +                                        }
   1.124 +                                        hashPut(instance);
   1.125 +                                    }
   1.126 +                                }
   1.127 +
   1.128                              }
   1.129                          }
   1.130  
   1.131 @@ -454,5 +505,23 @@
   1.132          protected boolean creatorOf(Object obj) {
   1.133              return obj == object;
   1.134          }
   1.135 +
   1.136 +        private static void hashPut(Object o) {
   1.137 +            Class<?> c = o.getClass();
   1.138 +            int size = knownInstances.size();
   1.139 +            int index = c.hashCode() % size;
   1.140 +            for (int i = 0; i < size; i++) {
   1.141 +                Reference<Object> ref = knownInstances.get(index);
   1.142 +                Object obj = ref == null ? null : ref.get();
   1.143 +                if (obj == null) {
   1.144 +                    knownInstances.set(index, new WeakReference<Object>(o));
   1.145 +                    knownInstancesCount++;
   1.146 +                    break;
   1.147 +                }
   1.148 +                if (++index == size) {
   1.149 +                    index = 0;
   1.150 +                }
   1.151 +            }
   1.152 +        }
   1.153      }
   1.154  }
     2.1 --- a/openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTest.java	Thu Mar 12 10:21:19 2009 +0100
     2.2 +++ b/openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTest.java	Thu Mar 12 20:30:58 2009 +0100
     2.3 @@ -56,22 +56,24 @@
     2.4  import java.util.Collections;
     2.5  import java.util.Comparator;
     2.6  import java.util.Enumeration;
     2.7 -import java.util.HashMap;
     2.8  import java.util.HashSet;
     2.9  import java.util.Iterator;
    2.10  import java.util.List;
    2.11  import java.util.Map;
    2.12  import java.util.Set;
    2.13 -import java.util.Set;
    2.14  import java.util.TreeSet;
    2.15 +import java.util.WeakHashMap;
    2.16  import java.util.jar.JarEntry;
    2.17  import java.util.jar.JarOutputStream;
    2.18  import java.util.logging.Level;
    2.19  import java.util.logging.Logger;
    2.20  import java.util.regex.Matcher;
    2.21  import java.util.regex.Pattern;
    2.22 +import junit.framework.Test;
    2.23  import org.bar.Comparator2;
    2.24 +import org.netbeans.junit.MockServices;
    2.25  import org.netbeans.junit.NbTestCase;
    2.26 +import org.openide.util.Enumerations;
    2.27  import org.openide.util.Lookup;
    2.28  import org.openide.util.LookupEvent;
    2.29  import org.openide.util.LookupListener;
    2.30 @@ -82,13 +84,13 @@
    2.31   */
    2.32  public class MetaInfServicesLookupTest extends NbTestCase {
    2.33      private Logger LOG;
    2.34 -    private Map<ClassLoader,Lookup> lookups = new HashMap<ClassLoader,Lookup>();
    2.35 +    private Map<ClassLoader,Lookup> lookups = new WeakHashMap<ClassLoader,Lookup>();
    2.36      
    2.37      public MetaInfServicesLookupTest(String name) {
    2.38          super(name);
    2.39          LOG = Logger.getLogger("Test." + name);
    2.40      }
    2.41 -    
    2.42 +
    2.43      protected String prefix() {
    2.44          return "META-INF/services/";
    2.45      }
    2.46 @@ -97,11 +99,13 @@
    2.47          return Lookups.metaInfServices(c);
    2.48      }
    2.49      
    2.50 +    @Override
    2.51      protected Level logLevel() {
    2.52          return Level.INFO;
    2.53      }
    2.54  
    2.55      private Lookup getTestedLookup(ClassLoader c) {
    2.56 +        MockServices.setServices();
    2.57          Lookup l = lookups.get(c);
    2.58          if (l == null) {
    2.59              l = createLookup(c);
    2.60 @@ -168,7 +172,7 @@
    2.61                  os.write(ch);
    2.62              }
    2.63              from.close();
    2.64 -            os.closeEntry();;
    2.65 +            os.closeEntry();
    2.66          }
    2.67          os.close();
    2.68          LOG.info("done " + jar);
    2.69 @@ -177,6 +181,7 @@
    2.70  
    2.71      ClassLoader c1, c2, c2a, c3, c4;
    2.72  
    2.73 +    @Override
    2.74      protected void setUp() throws Exception {
    2.75          clearWorkDir();
    2.76          ClassLoader app = getClass().getClassLoader().getParent();
    2.77 @@ -200,6 +205,7 @@
    2.78          }, c0);
    2.79      }
    2.80  
    2.81 +    @Override
    2.82      protected void tearDown() throws Exception {
    2.83          Set<Reference<Lookup>> weak = new HashSet<Reference<Lookup>>();
    2.84          for (Lookup l : lookups.values()) {
    2.85 @@ -288,8 +294,9 @@
    2.86          class Loader extends ClassLoader {
    2.87              private int counter;
    2.88  
    2.89 +            @Override
    2.90              protected URL findResource(String name) {
    2.91 -                if (name.equals("META-INF/services/java.lang.Object")) {
    2.92 +                if (name.equals(prefix() + "java.lang.Object")) {
    2.93                      counter++;
    2.94                  }
    2.95  
    2.96 @@ -299,8 +306,9 @@
    2.97                  return retValue;
    2.98              }
    2.99  
   2.100 +            @Override
   2.101              protected Enumeration findResources(String name) throws IOException {
   2.102 -                if (name.equals("META-INF/services/java.lang.Object")) {
   2.103 +                if (name.equals(prefix() + "java.lang.Object")) {
   2.104                      counter++;
   2.105                  }
   2.106                  Enumeration retValue;
   2.107 @@ -317,6 +325,69 @@
   2.108          assertEquals("No lookup of Object", 0, loader.counter);
   2.109      }
   2.110  
   2.111 +    public void testCanGarbageCollectClasses() throws Exception {
   2.112 +        class Loader extends ClassLoader {
   2.113 +            public Loader() {
   2.114 +                super(Loader.class.getClassLoader().getParent());
   2.115 +            }
   2.116 +
   2.117 +            @Override
   2.118 +            protected URL findResource(String name) {
   2.119 +                if (name.equals(prefix() + "java.lang.Runnable")) {
   2.120 +                    return Loader.class.getResource("MetaInfServicesLookupTestRunnable.txt");
   2.121 +                }
   2.122 +
   2.123 +                URL retValue;
   2.124 +
   2.125 +                retValue = super.findResource(name);
   2.126 +                return retValue;
   2.127 +            }
   2.128 +
   2.129 +            @Override
   2.130 +            protected Class<?> findClass(String name) throws ClassNotFoundException {
   2.131 +                if (name.equals("org.openide.util.lookup.MetaInfServicesLookupTestRunnable")) {
   2.132 +                    try {
   2.133 +                        InputStream is = getClass().getResourceAsStream("MetaInfServicesLookupTestRunnable.class");
   2.134 +                        byte[] arr = new byte[is.available()];
   2.135 +                        int read = is.read(arr);
   2.136 +                        assertEquals("Fully read", arr.length, read);
   2.137 +                        return defineClass(name, arr, 0, arr.length);
   2.138 +                    } catch (IOException ex) {
   2.139 +                        throw new ClassNotFoundException("Cannot load", ex);
   2.140 +                    }
   2.141 +                }
   2.142 +                throw new ClassNotFoundException();
   2.143 +            }
   2.144 +
   2.145 +
   2.146 +
   2.147 +            @Override
   2.148 +            protected Enumeration findResources(String name) throws IOException {
   2.149 +                if (name.equals(prefix() + "java.lang.Runnable")) {
   2.150 +                    return Enumerations.singleton(findResource(name));
   2.151 +                }
   2.152 +                return super.findResources(name);
   2.153 +            }
   2.154 +        }
   2.155 +        Loader loader = new Loader();
   2.156 +        Lookup l = getTestedLookup(loader);
   2.157 +
   2.158 +
   2.159 +        Object no = l.lookup(Runnable.class);
   2.160 +        assertNotNull("Found of course", no);
   2.161 +        assertEquals("The right name", "MetaInfServicesLookupTestRunnable", no.getClass().getSimpleName());
   2.162 +        if (no.getClass().getClassLoader() != loader) {
   2.163 +            fail("Wrong classloader: " + no.getClass().getClassLoader());
   2.164 +        }
   2.165 +
   2.166 +        WeakReference<Object> ref = new WeakReference<Object>(no.getClass());
   2.167 +        loader = null;
   2.168 +        no = null;
   2.169 +        l = null;
   2.170 +        lookups.clear();
   2.171 +        assertGC("Class can be garbage collected", ref);
   2.172 +    }
   2.173 +
   2.174      public void testListenersAreNotifiedWithoutHoldingALockIssue36035() throws Exception {
   2.175          final Lookup l = getTestedLookup(c2);
   2.176          final Class xface = c1.loadClass("org.foo.Interface");
   2.177 @@ -356,10 +427,10 @@
   2.178      public void testWrongOrderAsInIssue100320() throws Exception {
   2.179          ClassLoader app = getClass().getClassLoader().getParent();
   2.180          ClassLoader c0 = app;
   2.181 -        ClassLoader c1 = new URLClassLoader(new URL[] {
   2.182 +        ClassLoader ctmp = new URLClassLoader(new URL[] {
   2.183              findJar("problem100320.jar"),
   2.184          }, c0);
   2.185 -        Lookup lookup = Lookups.metaInfServices(c1, prefix());
   2.186 +        Lookup lookup = Lookups.metaInfServices(ctmp, prefix());
   2.187  
   2.188          Collection<?> colAWT = lookup.lookupAll(Component.class);
   2.189          assertEquals("There is enough objects to switch to InheritanceTree", 12, colAWT.size());
   2.190 @@ -367,7 +438,7 @@
   2.191          
   2.192          List<?> col1 = new ArrayList<Object>(lookup.lookupAll(Comparator.class));
   2.193          assertEquals("Two", 2, col1.size());
   2.194 -        Collection<?> col2 = lookup.lookupAll(c1.loadClass(Comparator2.class.getName()));
   2.195 +        Collection<?> col2 = lookup.lookupAll(ctmp.loadClass(Comparator2.class.getName()));
   2.196          assertEquals("One", 1, col2.size());
   2.197          List<?> col3 = new ArrayList<Object>(lookup.lookupAll(Comparator.class));
   2.198          assertEquals("Two2", 2, col3.size());
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTestRunnable.java	Thu Mar 12 20:30:58 2009 +0100
     3.3 @@ -0,0 +1,50 @@
     3.4 +/*
     3.5 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3.6 + *
     3.7 + * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
     3.8 + *
     3.9 + * The contents of this file are subject to the terms of either the GNU
    3.10 + * General Public License Version 2 only ("GPL") or the Common
    3.11 + * Development and Distribution License("CDDL") (collectively, the
    3.12 + * "License"). You may not use this file except in compliance with the
    3.13 + * License. You can obtain a copy of the License at
    3.14 + * http://www.netbeans.org/cddl-gplv2.html
    3.15 + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    3.16 + * specific language governing permissions and limitations under the
    3.17 + * License.  When distributing the software, include this License Header
    3.18 + * Notice in each file and include the License file at
    3.19 + * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
    3.20 + * particular file as subject to the "Classpath" exception as provided
    3.21 + * by Sun in the GPL Version 2 section of the License file that
    3.22 + * accompanied this code. If applicable, add the following below the
    3.23 + * License Header, with the fields enclosed by brackets [] replaced by
    3.24 + * your own identifying information:
    3.25 + * "Portions Copyrighted [year] [name of copyright owner]"
    3.26 + *
    3.27 + * Contributor(s):
    3.28 + *
    3.29 + * The Original Software is NetBeans. The Initial Developer of the Original
    3.30 + * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
    3.31 + * Microsystems, Inc. All Rights Reserved.
    3.32 + *
    3.33 + * If you wish your version of this file to be governed by only the CDDL
    3.34 + * or only the GPL Version 2, indicate your decision by adding
    3.35 + * "[Contributor] elects to include this software in this distribution
    3.36 + * under the [CDDL or GPL Version 2] license." If you do not indicate a
    3.37 + * single choice of license, a recipient has the option to distribute
    3.38 + * your version of this file under either the CDDL, the GPL Version 2 or
    3.39 + * to extend the choice of license to its licensees as provided above.
    3.40 + * However, if you add GPL Version 2 code and therefore, elected the GPL
    3.41 + * Version 2 license, then the option applies only if the new code is
    3.42 + * made subject to such option by the copyright holder.
    3.43 + */
    3.44 +
    3.45 +package org.openide.util.lookup;
    3.46 +
    3.47 +
    3.48 +/**
    3.49 + */
    3.50 +public final class MetaInfServicesLookupTestRunnable implements Runnable {
    3.51 +    public void run() {
    3.52 +    }
    3.53 +}
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/openide.util/test/unit/src/org/openide/util/lookup/MetaInfServicesLookupTestRunnable.txt	Thu Mar 12 20:30:58 2009 +0100
     4.3 @@ -0,0 +1,1 @@
     4.4 +org.openide.util.lookup.MetaInfServicesLookupTestRunnable
     5.1 --- a/openide.util/test/unit/src/org/openide/util/lookup/NamedServicesLookupTest.java	Thu Mar 12 10:21:19 2009 +0100
     5.2 +++ b/openide.util/test/unit/src/org/openide/util/lookup/NamedServicesLookupTest.java	Thu Mar 12 20:30:58 2009 +0100
     5.3 @@ -41,26 +41,33 @@
     5.4  
     5.5  package org.openide.util.lookup;
     5.6  
     5.7 +import junit.framework.Test;
     5.8 +import org.netbeans.junit.MockServices;
     5.9  import org.openide.util.Lookup;
    5.10 +import org.openide.util.test.MockLookup;
    5.11  
    5.12  
    5.13  /** Test finding services from manifest.
    5.14   * @author Jaroslav Tulach
    5.15   */
    5.16  public class NamedServicesLookupTest extends MetaInfServicesLookupTest {
    5.17 +    static {
    5.18 +        MockLookup.init();
    5.19 +    }
    5.20      public NamedServicesLookupTest(String name) {
    5.21          super(name);
    5.22      }
    5.23 -    
    5.24 +
    5.25 +    @Override
    5.26      protected String prefix() {
    5.27          return "META-INF/namedservices/sub/path/";
    5.28      }
    5.29      
    5.30 +    @Override
    5.31      protected Lookup createLookup(ClassLoader c) {
    5.32 -        ClassLoader prev = Thread.currentThread().getContextClassLoader();
    5.33 -        Thread.currentThread().setContextClassLoader(c);
    5.34 +        MockLookup.setInstances(c);
    5.35          Lookup l = Lookups.forPath("sub/path");
    5.36 -        Thread.currentThread().setContextClassLoader(prev);
    5.37 +        MockLookup.setInstances();
    5.38          return l;
    5.39      }
    5.40