#156692: Manual implementation of linear hashing. Allows garbage collection of Class objects.
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