diff --git a/openide.util.lookup/manifest.mf b/openide.util.lookup/manifest.mf --- a/openide.util.lookup/manifest.mf +++ b/openide.util.lookup/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.util.lookup OpenIDE-Module-Localizing-Bundle: org/openide/util/lookup/Bundle.properties -OpenIDE-Module-Specification-Version: 8.33 +OpenIDE-Module-Specification-Version: 8.34 diff --git a/openide.util.lookup/src/org/openide/util/lookup/AbstractLookup.java b/openide.util.lookup/src/org/openide/util/lookup/AbstractLookup.java --- a/openide.util.lookup/src/org/openide/util/lookup/AbstractLookup.java +++ b/openide.util.lookup/src/org/openide/util/lookup/AbstractLookup.java @@ -46,6 +46,7 @@ import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; +import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -61,12 +62,14 @@ import java.util.Set; import java.util.TreeSet; import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import org.openide.util.Lookup; import org.openide.util.LookupEvent; import org.openide.util.LookupListener; import org.openide.util.lookup.implspi.ActiveQueue; +import org.openide.util.lookup.implspi.CallbackReferencesSupport; /** Implementation of the lookup from OpenAPIs that is based on the @@ -1354,12 +1357,25 @@ /** caches for results */ public Object caches; + + private static final AtomicBoolean registered = new AtomicBoolean(false); + private final CallbackReferencesSupport callbackSupport; /** Creates a weak refernece to a new result R in context of lookup * for given template */ private ReferenceToResult(R result, AbstractLookup lookup, Template template) { - super(result, activeQueue()); + this(new CallbackReferencesSupport(), result, lookup, template); + } + + private ReferenceToResult(CallbackReferencesSupport callbackSupport, + R result, AbstractLookup lookup, Template template) { + super(result, callbackSupport.getReferenceQueue()); + this.callbackSupport = callbackSupport; + callbackSupport.setCallback(this); + if (!registered.getAndSet(true)) { + CallbackReferencesSupport.addReferenceSupportProvider(ReferenceToResult.class, new SupportProvider()); + } this.template = template; this.lookup = lookup; getResult().reference = this; @@ -1396,9 +1412,24 @@ } } + @Override + protected final void finalize() throws Throwable { + callbackSupport.notifyFinalized(); + super.finalize(); + } + private ReferenceToResult cloneRef() { return new ReferenceToResult(getResult(), lookup, template); } + + private static final class SupportProvider implements CallbackReferencesSupport.ReferenceSupportProvider { + + @Override + public CallbackReferencesSupport getSupport(Reference reference) { + return ((ReferenceToResult) reference).callbackSupport; + } + + } } // end of ReferenceToResult diff --git a/openide.util.lookup/src/org/openide/util/lookup/implspi/CallbackReferencesSupport.java b/openide.util.lookup/src/org/openide/util/lookup/implspi/CallbackReferencesSupport.java new file mode 100644 --- /dev/null +++ b/openide.util.lookup/src/org/openide/util/lookup/implspi/CallbackReferencesSupport.java @@ -0,0 +1,301 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package org.openide.util.lookup.implspi; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Support class for building callback references. Callback references are able + * to execute a dedicated callback when the object the reference refers to, becomes unreachable. + *

+ * Do not use this class, unless you know well what you're doing. Use rather + * org.openide.util.CallbackReferences instead. + *

+ * Usage: + *

    + *
  1. Create new CallbackReferencesSupport(Runnable) with the desired callback, + * or new CallbackReferencesSupport() and set the callback before the reference + * object can become unreachable.
  2. + *
  3. Create the desired reference with getReferenceQueue(). + * It's important that there is one reference instance per one + * CallbackReferencesSupport instance.
  4. + *
  5. Override the reference implementation's finalize method and call + * notifyFinalized() in it.
  6. + *
  7. Register the reference class with the callback support provider via + * addReferenceSupportProvider()
  8. + *
+ * + * @author Martin Entlicher + * @since 8.34 + * @see org.openide.util.CallbackReferences + */ +public final class CallbackReferencesSupport { + + private static final Logger LOGGER = Logger.getLogger(CallbackReferencesSupport.class.getName()); + private static Reference activeReferenceQueue = new WeakReference(null); + private static long activeRefsCount = 0l; + private static final Map, ReferenceSupportProvider> supportProvidersMap = new HashMap, ReferenceSupportProvider>(); + + private final ReferenceQueue callbackRefQueue; // Need to hold the reference so that it's not GC'ed prematurely + Runnable callback; + + /** + * Gets the active reference queue. + * @return the singleton queue + */ + private static synchronized ReferenceQueue queue() { + ReferenceQueue rq = activeReferenceQueue.get(); + if (rq == null) { + rq = new ReferenceQueue(); + activeReferenceQueue = new WeakReference(rq); + Daemon.ping(); + } + return rq; + } + + /** + * Register a reference support provider. We need a way how to get the + * instance of {@link CallbackReferencesSupport} associated with the + * Reference when the reference is cleared by GC. + * @param refClass The class type of the reference. + * @param supportProvider The provider that knows how to retrieve the support from the reference of this class type. + */ + public static void addReferenceSupportProvider(Class refClass, ReferenceSupportProvider supportProvider) { + synchronized (supportProvidersMap) { + supportProvidersMap.put(refClass, supportProvider); + } + } + + private static CallbackReferencesSupport getSupport(Reference ref) { + Class refClass = ref.getClass(); + ReferenceSupportProvider rsp = null; + synchronized (supportProvidersMap) { + for (Map.Entry, ReferenceSupportProvider> entry : supportProvidersMap.entrySet()) { + if (entry.getKey().isAssignableFrom(refClass)) { + rsp = entry.getValue(); + break; + } + } + } + if (rsp != null) { + return rsp.getSupport(ref); + } else { + return null; + } + } + + private static Runnable checkCallback(Runnable callback) { + if (callback == null) { + throw new NullPointerException("The callback should not be null."); // NOI18N + } + return callback; + } + + /** + * Creates a new empty callback support, one instance to be used with one {@link Reference}. + * It's necessary to set the callback runnable via + * {@link #setCallback(java.lang.Runnable)} before the associated reference + * is cleared. + */ + public CallbackReferencesSupport() { + this(null, queue()); + } + + /** + * Creates a new callback support with associated callback runnable, + * one instance to be used with one {@link Reference}. + * @param callback The runnable which is run after the associated reference is cleared by GC. + */ + public CallbackReferencesSupport(Runnable callback) { + this(checkCallback(callback), queue()); + } + + private CallbackReferencesSupport(Runnable callback, ReferenceQueue queue) { + this.callbackRefQueue = queue; + this.callback = callback; + synchronized (CallbackReferencesSupport.class) { + activeRefsCount++; + } + } + + /** + * Get the reference queue, solely for the purpose of reference creation. + * @return The reference queue. + */ + public ReferenceQueue getReferenceQueue() { + return callbackRefQueue; + } + + /** + * Set a callback runnable to be called after the associated reference is cleared by GC. + * @param callback + */ + public void setCallback(Runnable callback) { + this.callback = callback; + } + + void cleanup() { + try { + callback.run(); + } finally { + callback = null; + died(); + } + } + + /** + * Notify that the associated reference was finalized. + */ + public void notifyFinalized() { + if (callback != null) { + callback = null; + died(); + } + } + + void died() { + synchronized (CallbackReferencesSupport.class) { + if (--activeRefsCount == 0) { + activeReferenceQueue.clear(); + Daemon.finish(); + } + } + } + + /** + * A provider of {@link CallbackReferencesSupport} for the given {@link Reference}. + * @see #addReferenceSupportProvider(java.lang.Class, org.openide.util.lookup.implspi.CallbackReferencesSupport.ReferenceSupportProvider) + */ + public static interface ReferenceSupportProvider { + + /** + * Retrieve the {@link CallbackReferencesSupport} for the given {@link Reference}. + * @param reference The reference + * @return callback support associated with the reference. + */ + CallbackReferencesSupport getSupport(Reference reference); + + } + + static final class Daemon extends Thread { + private static Daemon running; + + public Daemon() { + super("Cleanable Reference Queue Daemon"); + } + + static synchronized void ping() { + if (running == null) { + Daemon t = new Daemon(); + t.setPriority(Thread.MIN_PRIORITY); + t.setDaemon(true); + t.start(); + LOGGER.fine("starting thread"); + running = t; + } + } + + static synchronized boolean isActive() { + return running != null; + } + + static synchronized void finish() { + if (running != null) { + running.doFinish(); + } + } + + static synchronized ReferenceQueue obtainQueue() { + ReferenceQueue rq= activeReferenceQueue.get(); + if (rq == null) { + running = null; + } + return rq; + } + + private void doFinish() { + interrupt(); + } + + @Override + public void run() { + while (true) { + try { + ReferenceQueue rq = obtainQueue(); + if (rq == null) { + return; + } + Reference ref = rq.remove(); + LOGGER.log(Level.FINE, "Got {0} with {1}", new Object[]{ref, ref == null ? null : ref.get()}); + CallbackReferencesSupport support = getSupport(ref); + if (support == null) { + LOGGER.log(Level.WARNING, "An unexpected reference in the queue: {0}", ref.getClass()); + continue; + } + // do the cleanup + try { + support.cleanup(); + } catch (ThreadDeath td) { + throw td; + } catch (Throwable t) { + // Should not happen. + // If it happens, it is a bug in client code, notify! + LOGGER.log(Level.WARNING, "Cannot process " + ref, t); + } finally { + // to allow GC + ref = null; + support = null; + } + } catch (InterruptedException ex) { + // Can happen during VM shutdown, it seems. Ignore. + continue; + } + } + } + } +} diff --git a/openide.util.lookup/test/unit/src/org/openide/util/lookup/LookupPermGenLeakTest.java b/openide.util.lookup/test/unit/src/org/openide/util/lookup/LookupPermGenLeakTest.java --- a/openide.util.lookup/test/unit/src/org/openide/util/lookup/LookupPermGenLeakTest.java +++ b/openide.util.lookup/test/unit/src/org/openide/util/lookup/LookupPermGenLeakTest.java @@ -132,7 +132,7 @@ public void testClassLoaderCanGC() throws Exception { Reference ref = new WeakReference(createClass()); - // assertGC("Can be GCed", ref); TODO: Uncomment after #257013 is implemented. + assertGC("Can be GCed", ref); } private synchronized int waitForOne() throws InterruptedException { diff --git a/openide.util/apichanges.xml b/openide.util/apichanges.xml --- a/openide.util/apichanges.xml +++ b/openide.util/apichanges.xml @@ -50,6 +50,26 @@ XML API + + + CallbackReferences class provides references that are able to + call a defined callback, after the referent object becomes unreachable. + + + + + +

+ CallbackReferences class provides a set of reference + implementations that are able to process the given callback runnable, + after their referent object becomes unreachable. The callback + processing is performed on a dedicated daemon thread, + which is shut down when there are no more callbacks to process. +

+
+ + +
Weak property and vetoable listeners for a specific property name. diff --git a/openide.util/manifest.mf b/openide.util/manifest.mf --- a/openide.util/manifest.mf +++ b/openide.util/manifest.mf @@ -1,5 +1,5 @@ Manifest-Version: 1.0 OpenIDE-Module: org.openide.util OpenIDE-Module-Localizing-Bundle: org/openide/util/base/Bundle.properties -OpenIDE-Module-Specification-Version: 9.7 +OpenIDE-Module-Specification-Version: 9.8 diff --git a/openide.util/src/org/openide/util/BaseUtilities.java b/openide.util/src/org/openide/util/BaseUtilities.java --- a/openide.util/src/org/openide/util/BaseUtilities.java +++ b/openide.util/src/org/openide/util/BaseUtilities.java @@ -224,6 +224,8 @@ * Be sure to call this method anew for each reference. * Do not attempt to cache the return value. * @since 3.11 + * @Deprecated This API requires an indefinitely running background thread. + * Use {@link CallbackReferences} instead. */ public static ReferenceQueue activeReferenceQueue() { return ActiveQueue.queue(); diff --git a/openide.util/src/org/openide/util/CallbackReferences.java b/openide.util/src/org/openide/util/CallbackReferences.java new file mode 100644 --- /dev/null +++ b/openide.util/src/org/openide/util/CallbackReferences.java @@ -0,0 +1,275 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package org.openide.util; + +import java.lang.ref.PhantomReference; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicBoolean; +import org.openide.util.lookup.implspi.CallbackReferencesSupport; + +/** + * This class provides references, that are able to call a defined callback, + * after the referent object becomes unreachable. It is useful when there is + * a need to run a callback code after the referent is collected. + *

+ * Usually, in order to implement such logic, one needs to either create + * a dedicated thread that blocks on the queue and is Object.notify-ed, + * which is the right approach but consumes valuable system resources (threads), + * or one can periodically check the content of the queue by + * RequestProcessor.Task.schedule which is completely wrong, + * because it wakes up the system every (say) 15 seconds. + *

+ * In order to provide an efficient support for this problem, these references + * has been provided. They share one background thread to process the callbacks, + * which dies when there are no more callbacks to process.
+ * Be sure not to block in such callbacks for a long time as this prevents other + * waiting references from processing. + * + * @author Martin Entlicher + * @since 9.8 + */ +public final class CallbackReferences { + + private CallbackReferences() {} + + private static T referentCheck(T referent) { + if (referent == null) { + throw new NullPointerException("The referent must not be null."); // NOI18N + } + return referent; + } + + /** + * An implementation of {@link PhantomReference} with a callback functionality. + * After the referent becomes unreachable, the provided Runnable callback is executed. + */ + public static class Phantom extends PhantomReference { + + private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); + + final CallbackReferencesSupport support; + + /** + * Create a new phantom reference with a callback. Both referent and the + * callback should not be null. + * @param referent the object the new phantom reference will refer to + * @param callback the callback executed after the referent becomes unreachable. + */ + public Phantom(T referent, Runnable callback) { + this(referentCheck(referent), new CallbackReferencesSupport(callback)); + } + + private Phantom(T referent, CallbackReferencesSupport support) { + super(referent, support.getReferenceQueue()); + this.support = support; + if (!REGISTERED.getAndSet(true)) { + CallbackReferencesSupport.addReferenceSupportProvider(Phantom.class, new SupportProvider()); + } + } + + @Override + protected final void finalize() throws Throwable { + super.finalize(); + support.notifyFinalized(); + } + + private static final class SupportProvider implements CallbackReferencesSupport.ReferenceSupportProvider { + + @Override + public CallbackReferencesSupport getSupport(Reference reference) { + return ((Phantom) reference).support; + } + + } + + } + + /** + * An implementation of {@link SoftReference} with a callback functionality. + * After the referent becomes unreachable, the provided Runnable callback is executed. + */ + public static class Soft extends SoftReference { + + private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); + + final CallbackReferencesSupport support; + + /** + * Create a new soft reference with a callback. Both referent and the + * callback should not be null. + * @param referent the object the new soft reference will refer to + * @param callback the callback executed after the referent becomes unreachable. + */ + public Soft(T referent, Runnable callback) { + this(referentCheck(referent), new CallbackReferencesSupport(callback)); + } + + private Soft(T referent, CallbackReferencesSupport support) { + super(referent, support.getReferenceQueue()); + this.support = support; + if (!REGISTERED.getAndSet(true)) { + CallbackReferencesSupport.addReferenceSupportProvider(Soft.class, new SupportProvider()); + } + } + + @Override + protected final void finalize() throws Throwable { + support.notifyFinalized(); + super.finalize(); + } + + private static final class SupportProvider implements CallbackReferencesSupport.ReferenceSupportProvider { + + @Override + public CallbackReferencesSupport getSupport(Reference reference) { + return ((Soft) reference).support; + } + + } + + } + + /** + * An implementation of {@link WeakReference} with a callback functionality. + * After the referent becomes unreachable, the provided Runnable callback is executed. + */ + public static class Weak extends WeakReference { + + private static final AtomicBoolean REGISTERED = new AtomicBoolean(false); + + final CallbackReferencesSupport support; + + /** + * Create a new weak reference with a callback. Both referent and the + * callback should not be null. + * @param referent the object the new weak reference will refer to + * @param callback the callback executed after the referent becomes unreachable. + */ + public Weak(T referent, Runnable callback) { + this(referentCheck(referent), new CallbackReferencesSupport(callback)); + } + + private Weak(T referent, CallbackReferencesSupport support) { + super(referent, support.getReferenceQueue()); + this.support = support; + if (!REGISTERED.getAndSet(true)) { + CallbackReferencesSupport.addReferenceSupportProvider(Weak.class, new SupportProvider()); + } + } + + @Override + protected final void finalize() throws Throwable { + support.notifyFinalized(); + super.finalize(); + } + + private static final class SupportProvider implements CallbackReferencesSupport.ReferenceSupportProvider { + + @Override + public CallbackReferencesSupport getSupport(Reference reference) { + return ((Weak) reference).support; + } + + } + + } + + /** + * An abstract {@link PhantomReference}, which acts itself as a callback + * by implementing {@link Runable}. + * After the referent becomes unreachable, it's run() method + * is executed. + */ + public static abstract class PhantomCallback extends Phantom implements Runnable { + + /** + * Create a new phantom callback reference. + * @param referent the object the new phantom reference will refer to + */ + public PhantomCallback(T referent) { + super(referentCheck(referent), new CallbackReferencesSupport()); + support.setCallback(this); + } + + } + + /** + * An abstract {@link SoftReference}, which acts itself as a callback + * by implementing {@link Runable}. + * After the referent becomes unreachable, it's run() method + * is executed. + */ + public static abstract class SoftCallback extends Soft implements Runnable { + + /** + * Create a new soft callback reference. + * @param referent the object the new phantom reference will refer to + */ + public SoftCallback(T referent) { + super(referentCheck(referent), new CallbackReferencesSupport()); + support.setCallback(this); + } + + } + + /** + * An abstract {@link WeakReference}, which acts itself as a callback + * by implementing {@link Runable}. + * After the referent becomes unreachable, it's run() method + * is executed. + */ + public static abstract class WeakCallback extends Weak implements Runnable { + + /** + * Create a new weak callback reference. + * @param referent the object the new phantom reference will refer to + */ + public WeakCallback(T referent) { + super(referentCheck(referent), new CallbackReferencesSupport()); + support.setCallback(this); + } + + } + +} diff --git a/openide.util/test/unit/src/org/openide/util/CallbackReferencesTest.java b/openide.util/test/unit/src/org/openide/util/CallbackReferencesTest.java new file mode 100644 --- /dev/null +++ b/openide.util/test/unit/src/org/openide/util/CallbackReferencesTest.java @@ -0,0 +1,237 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2015 Oracle and/or its affiliates. All rights reserved. + * + * Oracle and Java are registered trademarks of Oracle and/or its affiliates. + * Other names may be trademarks of their respective owners. + * + * The contents of this file are subject to the terms of either the GNU + * General Public License Version 2 only ("GPL") or the Common + * Development and Distribution License("CDDL") (collectively, the + * "License"). You may not use this file except in compliance with the + * License. You can obtain a copy of the License at + * http://www.netbeans.org/cddl-gplv2.html + * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the + * specific language governing permissions and limitations under the + * License. When distributing the software, include this License Header + * Notice in each file and include the License file at + * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the GPL Version 2 section of the License file that + * accompanied this code. If applicable, add the following below the + * License Header, with the fields enclosed by brackets [] replaced by + * your own identifying information: + * "Portions Copyrighted [year] [name of copyright owner]" + * + * If you wish your version of this file to be governed by only the CDDL + * or only the GPL Version 2, indicate your decision by adding + * "[Contributor] elects to include this software in this distribution + * under the [CDDL or GPL Version 2] license." If you do not indicate a + * single choice of license, a recipient has the option to distribute + * your version of this file under either the CDDL, the GPL Version 2 or + * to extend the choice of license to its licensees as provided above. + * However, if you add GPL Version 2 code and therefore, elected the GPL + * Version 2 license, then the option applies only if the new code is + * made subject to such option by the copyright holder. + * + * Contributor(s): + * + * Portions Copyrighted 2015 Sun Microsystems, Inc. + */ +package org.openide.util; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import static junit.framework.TestCase.assertTrue; +import static junit.framework.TestCase.fail; +import org.netbeans.junit.NbTestCase; +import static org.netbeans.junit.NbTestCase.assertGC; +import org.openide.util.lookup.implspi.CallbackReferencesSupport; + +/** + * + * @author Martin Entlicher + */ +public class CallbackReferencesTest extends NbTestCase { + + public CallbackReferencesTest(String testName) { + super(testName); + } + + private boolean isDaemonActive() throws Exception { + // CallbackReferencesSupport.Daemon.isActive() + Class daemonClass = Class.forName("org.openide.util.lookup.implspi.CallbackReferencesSupport$Daemon"); + Method isActiveMethod = daemonClass.getDeclaredMethod("isActive"); + isActiveMethod.setAccessible(true); + return (Boolean) isActiveMethod.invoke(null); + } + + @Override + protected void tearDown() throws Exception { + // Test that the Daemon thread finishes eventually + while (isDaemonActive()) { + System.gc(); + Thread.sleep(10); + } + } + + public void testRunnableReferenceIsExecuted () throws Exception { + Object obj = new Object (); + RunnableRef ref = new RunnableRef (obj); + synchronized (ref) { + obj = null; + assertGC ("Should be GCed quickly", ref); + ref.wait (); + assertTrue ("Run method has been executed", ref.executed); + } + } + + public void testRunnablesAreProcessedOneByOne () throws Exception { + Object obj = new Object (); + RunnableRef ref = new RunnableRef (obj); + ref.wait = true; + + + synchronized (ref) { + obj = null; + assertGC ("Is garbage collected", ref); + ref.wait (); + assertTrue ("Still not executed, it is blocked", !ref.executed); + } + + RunnableRef after = new RunnableRef (new Object ()); + synchronized (after) { + assertGC ("Is garbage collected", after); + after.wait (100); // will fail + assertTrue ("Even if GCed, still not processed", !after.executed); + } + + synchronized (after) { + synchronized (ref) { + ref.notify (); + ref.wait (); + assertTrue ("Processed", ref.executed); + } + after.wait (); + assertTrue ("Processed too", after.executed); + } + } + + public void testManyReferencesProcessed() throws InterruptedException { + int n = 100; + Object[] objects = new Object[n]; + ExpensiveRef[] refs = new ExpensiveRef[n]; + for (int i = 0; i < n; i++) { + objects[i] = new Object(); + refs[i] = new ExpensiveRef(objects[i], Integer.toString(i)); + } + objects = null; + for (int i = 0; i < n; i++) { + assertGC("is GC'ed", refs[i]); + } + for (int i = 0; i < n; i++) { + synchronized (refs[i]) { + while (!refs[i].executed) { + refs[i].wait(); + } + } + } + } + + public void testNullNotAccepted() { + NullPointerException npe = null; + try { + new CallbackReferences.Phantom(null, new Runnable() { public void run() {}}); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + npe = null; + try { + new CallbackReferences.Phantom(new Object(), null); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + npe = null; + try { + new CallbackReferences.Soft(null, new Runnable() { public void run() {}}); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + npe = null; + try { + new CallbackReferences.Soft(new Object(), null); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + npe = null; + try { + new CallbackReferences.Weak(null, new Runnable() { public void run() {}}); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + npe = null; + try { + new CallbackReferences.Weak(new Object(), null); + } catch (NullPointerException ex) { + npe = ex; + } + assertNotNull("NullPointerException not thrown!", npe); + } + + private static class RunnableRef extends CallbackReferences.WeakCallback + implements Runnable { + public boolean wait; + public boolean entered; + public boolean executed; + + public RunnableRef (Object o) { + super(o); + } + + @Override + public synchronized void run () { + entered = true; + if (wait) { + // notify we are here + notify (); + try { + wait (); + } catch (InterruptedException ex) { + } + } + executed = true; + + notifyAll (); + } + } + + private static class ExpensiveRef extends CallbackReferences.WeakCallback + implements Runnable { + public boolean executed; + private final String name; + + public ExpensiveRef (Object o, String name) { + super(o); + this.name = name; + } + + @Override + public synchronized void run () { + executed = true; + try { + Thread.sleep(10); + System.gc(); + Thread.sleep(10); + } catch (InterruptedException iex) {} + notifyAll (); + //System.err.println(name+" executed."); + } + } + +}