You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2020/08/10 09:08:21 UTC

[netbeans] branch master updated: Make it easy to create dynamically updated ProxyLookup instances without subclassing

This is an automated email from the ASF dual-hosted git repository.

jtulach pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git


The following commit(s) were added to refs/heads/master by this push:
     new 2a4a18a  Make it easy to create dynamically updated ProxyLookup instances without subclassing
2a4a18a is described below

commit 2a4a18a201fec8b66c167eef012004e2c4be21f3
Author: Tim Boudreau <ti...@timboudreau.com>
AuthorDate: Mon Aug 10 11:06:50 2020 +0200

    Make it easy to create dynamically updated ProxyLookup instances without subclassing
---
 platform/openide.util.lookup/apichanges.xml        |  27 +++
 platform/openide.util.lookup/manifest.mf           |   2 +-
 .../src/org/openide/util/lookup/ProxyLookup.java   | 184 +++++++++++++------
 .../util/lookup/ProxyLookupFactoryMethodsTest.java | 196 +++++++++++++++++++++
 4 files changed, 358 insertions(+), 51 deletions(-)

diff --git a/platform/openide.util.lookup/apichanges.xml b/platform/openide.util.lookup/apichanges.xml
index 99c701f..f3d71c3 100644
--- a/platform/openide.util.lookup/apichanges.xml
+++ b/platform/openide.util.lookup/apichanges.xml
@@ -25,6 +25,33 @@
     <apidef name="lookup">Lookup API</apidef>
 </apidefs>
 <changes>
+    <change id="ProxyLookupController">
+        <api name="lookup"/>
+        <summary>Add ProxyLookup.Controller to set lookups dynamically without 
+            subclassing</summary>
+        <version major="8" minor="43"/>
+        <date year="2020" month="7" day="4"/>
+        <author login="tboudreau"/>
+        <compatibility addition="yes" source="compatible" semantic="compatible" binary="compatible"/>
+        <description>
+            <p>
+                One of the most common usages of  <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                is to dynamically change the set of lookups being delegated to. However the 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html#setLookups-org.openide.util.Lookup...-">setLookups(...)</a>
+                method is <code>protected</code>. To avoid the need to subclass 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                this change introduces
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.Controller.html">ProxyLookup.Controller</a>
+                that gives the creator of <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>
+                a way to call 
+                <a href="@TOP@/org/openide/util/lookup/ProxyLookup.Controller.html#setLookups-org.openide.util.Lookup...-">setLookups(...)</a>
+                without exposing the method to others having just a reference to 
+                the <a href="@TOP@/org/openide/util/lookup/ProxyLookup.html">ProxyLookup</a>.
+            </p>
+        </description>
+        <class name="ProxyLookup" package="org.openide.util.lookup"/>
+        <issue number="NETBEANS-4699"/>
+    </change>    
     <change id="AbstractProcessorSupportedSource">
         <api name="lookup"/>
         <summary>Declare support for all source levels.</summary>
diff --git a/platform/openide.util.lookup/manifest.mf b/platform/openide.util.lookup/manifest.mf
index 7b0558c..372496f 100644
--- a/platform/openide.util.lookup/manifest.mf
+++ b/platform/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.42
+OpenIDE-Module-Specification-Version: 8.43
 
diff --git a/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java b/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
index e18d04d..17cb46e 100644
--- a/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
+++ b/platform/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java
@@ -57,6 +57,25 @@ public class ProxyLookup extends Lookup {
     public ProxyLookup(Lookup... lookups) {
         data = ImmutableInternalData.EMPTY.setLookupsNoFire(lookups, true);
     }
+    /**
+     * Create a {@code ProxyLookup} whose contents can be set dynamically 
+     * subclassing. The passed
+     * {@link Controller} can be later be used to call
+     * {@link Controller#setLookups} which then 
+     * {@link ProxyLookup#setLookups changes} the lookups this {@code ProxyLookup} 
+     * delegates to. The passed controller may
+     * only be used for <i>one</i> ProxyLookup.
+     *
+     * @param controller A {@link Controller} which can be used to set the lookups
+     * @throws IllegalStateException if the passed controller has already
+     * been attached to another ProxyLookup 
+     * @since 8.43
+     */
+    @SuppressWarnings("LeakingThisInConstructor")
+    public ProxyLookup(Controller controller) {
+        this();
+        controller.setProxyLookup(this);
+    }
 
     /**
      * Create a lookup initially proxying to no others.
@@ -89,7 +108,72 @@ public class ProxyLookup extends Lookup {
         }
         return map.keySet();
     }
-    
+
+    /**
+     * A controller which allows the set of lookups being proxied to be
+     * set dynamically for those who create the instance of
+     * {@link ProxyLookup}.
+     *
+     * @since 8.43
+     */
+    public static final class Controller {
+
+        private ProxyLookup consumer;
+
+        /**
+         * Creates a new controller to be attached to a {@link ProxyLookup}.
+         * @since 8.43
+         */
+        public Controller() {
+        }
+
+        /**
+         * Set the lookups on the {@link ProxyLookup} this controller controls.
+         * If called before a {@link ProxyLookup} has been attached to this
+         * controller, an IllegalStateException will be thrown.
+         *
+         * @param notifyIn an executor to notify changes in
+         * @param lookups an array of Lookups to be proxied
+         * @throws IllegalStateException if called before this instance
+         * has been passed to the constructor of (exactly one) {@link ProxyLookup}
+         * @since 8.43
+         */
+        public void setLookups(Executor notifyIn, Lookup... lookups) {
+            if (consumer == null) {
+                throw new IllegalStateException("Cannot use Controller until "
+                        + "a ProxyLookup has been created with it.");
+            }
+            consumer.setLookups(notifyIn, lookups);
+        }
+
+        /**
+         * Set the lookups on the {@link ProxyLookup} this controller controls.
+         * If called before a {@link ProxyLookup} has been attached to this
+         * controller, an IllegalStateException will be thrown.
+         *
+         * @param exe An executor to notify in
+         * @param lookups An array of Lookups to be proxied
+         * @throws IllegalStateException if called before this instance
+         * has been passed to the constructor of (exactly one) {@link ProxyLookup}
+         * @since 8.43
+         */
+        public void setLookups(Lookup... lookups) {
+            if (consumer == null) {
+                throw new IllegalStateException("Cannot use Controller until "
+                        + "a ProxyLookup has been created with it.");
+            }
+            setLookups(null, lookups);
+        }
+
+        void setProxyLookup(ProxyLookup lkp) {
+            if (consumer != null) {
+                throw new IllegalStateException("Controller cannot be used "
+                        + "with more than one ProxyLookup.");
+            }
+            consumer = lkp;
+        }
+    }
+
     /**
      * Changes the delegates.
      *
@@ -99,7 +183,7 @@ public class ProxyLookup extends Lookup {
     protected final void setLookups(Lookup... lookups) {
         setLookups(null, lookups);
     }
-    
+
     /**
      * Changes the delegates immediatelly, notifies the listeners in provided
      * executor, potentially later.
@@ -113,10 +197,10 @@ public class ProxyLookup extends Lookup {
         Set<Lookup> newL;
         Set<Lookup> current;
         Lookup[] old;
-        
+
         Map<Result,LookupListener> toRemove = new IdentityHashMap<Lookup.Result, LookupListener>();
         Map<Result,LookupListener> toAdd = new IdentityHashMap<Lookup.Result, LookupListener>();
-        
+
         ImmutableInternalData orig;
         synchronized (ProxyLookup.this) {
             orig = getData();
@@ -126,7 +210,7 @@ public class ProxyLookup extends Lookup {
             }
             arr = setData(newData, lookups, toAdd, toRemove);
         }
-        
+
         // better to do this later than in synchronized block
         for (Map.Entry<Result, LookupListener> e : toRemove.entrySet()) {
             e.getKey().removeLookupListener(e.getValue());
@@ -144,7 +228,7 @@ public class ProxyLookup extends Lookup {
                 r.collectFires(evAndListeners);
             }
         }
-        
+
         class Notify implements Runnable {
             public void run() {
                 Iterator it = evAndListeners.iterator();
@@ -208,7 +292,7 @@ public class ProxyLookup extends Lookup {
     public final <T> Item<T> lookupItem(Template<T> template) {
         beforeLookup(template);
 
-        Lookup[] tmpLkps; 
+        Lookup[] tmpLkps;
         synchronized (ProxyLookup.this) {
             tmpLkps = getData().getLookups(false);
         }
@@ -256,14 +340,14 @@ public class ProxyLookup extends Lookup {
     }
 
     private Collection<Reference<R>> setData(
-        ImmutableInternalData newData, Lookup[] current, 
+        ImmutableInternalData newData, Lookup[] current,
         Map<Result,LookupListener> toAdd, Map<Result,LookupListener> toRemove
     ) {
         assert Thread.holdsLock(ProxyLookup.this);
         assert newData != null;
-        
+
         ImmutableInternalData previous = this.getData();
-        
+
         if (previous == newData) {
             return Collections.emptyList();
         }
@@ -314,14 +398,14 @@ public class ProxyLookup extends Lookup {
     private static final class R<T> extends WaitableResult<T> {
         /** weak listener & result */
         private final WeakResult<T> weakL;
-        
+
         /** list of listeners added */
         private LookupListenerList listeners;
 
         /** collection of Objects */
         private Collection[] cache;
 
-        
+
         /** associated lookup */
         private ImmutableInternalData data;
 
@@ -330,7 +414,7 @@ public class ProxyLookup extends Lookup {
         public R(ProxyLookup proxy, Lookup.Template<T> t) {
             this.weakL = new WeakResult<T>(proxy, this, t);
         }
-        
+
         private ProxyLookup proxy() {
             return weakL.result.proxy;
         }
@@ -339,7 +423,7 @@ public class ProxyLookup extends Lookup {
         private Result<T>[] newResults(int len) {
             return new Result[len];
         }
-        
+
         @Override
         protected void finalize() {
             weakL.result.run();
@@ -369,7 +453,7 @@ public class ProxyLookup extends Lookup {
                     if (current != data) {
                         continue;
                     }
-                    
+
                     Lookup[] currentLkps = data.getLookups(false);
                     if (currentLkps.length != myLkps.length) {
                         continue BIG_LOOP;
@@ -379,8 +463,8 @@ public class ProxyLookup extends Lookup {
                             continue BIG_LOOP;
                         }
                     }
-                    
-                    // some other thread might compute the result mean while. 
+
+                    // some other thread might compute the result mean while.
                     // if not finish the computation yourself
                     if (weakL.getResults() != null) {
                         return weakL.getResults();
@@ -548,7 +632,7 @@ public class ProxyLookup extends Lookup {
         public void resultChanged(LookupEvent ev) {
             collectFires(null);
         }
-        
+
         private static ThreadLocal<R<?>> IN = new ThreadLocal<>();
         protected void collectFires(Collection<Object> evAndListeners) {
             R<?> prev = IN.get();
@@ -563,7 +647,7 @@ public class ProxyLookup extends Lookup {
                 IN.set(prev);
             }
         }
-        
+
         private void collImpl(Collection<Object> evAndListeners) {
             boolean modified = true;
 
@@ -589,7 +673,7 @@ public class ProxyLookup extends Lookup {
                     }
                     ll = listeners.getListenerList();
                     assert ll != null;
-                    
+
 
                     // ignore events if they arrive as a result of call to allItems
                     // or allInstances, bellow...
@@ -632,7 +716,7 @@ public class ProxyLookup extends Lookup {
                     }
                 }
             }
-            
+
             if (modified) {
                 LookupEvent ev = new LookupEvent(this);
                 AbstractLookup.notifyListeners(ll, ev, evAndListeners);
@@ -646,7 +730,7 @@ public class ProxyLookup extends Lookup {
             boolean callBeforeLookup, boolean callBeforeOnWait
         ) {
             Template<T> template = template();
-            
+
             proxy().beforeLookup(callBeforeLookup, template);
 
             Lookup.Result<T>[] arr = initResults();
@@ -691,11 +775,11 @@ public class ProxyLookup extends Lookup {
             synchronized (proxy()) {
                 Collection[] cc = getCache();
                 if (cc != oldCC) {
-                    // don't change the cache when it is based on 
+                    // don't change the cache when it is based on
                     // outdated results
                     return;
                 }
-                
+
                 if (cc == null || cc == R.NO_CACHE) {
                     // initialize the cache to indicate this result is in use
                     setCache(cc = new Collection[3]);
@@ -707,14 +791,14 @@ public class ProxyLookup extends Lookup {
                     cc[indexToCache] = ret;
                 }
             }
-            
+
         }
     }
     private static final class WeakRef<T> extends WeakReference<R> implements Runnable {
         final WeakResult<T> result;
         final ProxyLookup proxy;
         final Template<T> template;
-        
+
         public WeakRef(R r, WeakResult<T> result, ProxyLookup proxy, Template<T> template) {
             super(r);
             this.result = result;
@@ -727,17 +811,17 @@ public class ProxyLookup extends Lookup {
             proxy.unregisterTemplate(template);
         }
     }
-    
-    
+
+
     private static final class WeakResult<T> extends WaitableResult<T> implements LookupListener, Runnable {
         /** all results */
         private Lookup.Result<T>[] results;
         private final WeakRef<T> result;
-        
+
         public WeakResult(ProxyLookup proxy, R r, Template<T> t) {
             this.result = new WeakRef<T>(r, this, proxy, t);
         }
-        
+
         final void removeListeners() {
             Lookup.Result<T>[] arr = this.getResults();
             if (arr == null) {
@@ -822,15 +906,15 @@ public class ProxyLookup extends Lookup {
             return allItems();
         }
     } // end of WeakResult
-    
+
     static abstract class ImmutableInternalData extends Object {
         static final ImmutableInternalData EMPTY = new EmptyInternalData();
         static final Lookup[] EMPTY_ARR = new Lookup[0];
 
-        
+
         protected ImmutableInternalData() {
         }
-        
+
         public static ImmutableInternalData create(Object lkp, Map<Template, Reference<R>> results) {
             if (results.size() == 0 && lkp == EMPTY_ARR) {
                 return EMPTY;
@@ -839,7 +923,7 @@ public class ProxyLookup extends Lookup {
                 Entry<Template,Reference<R>> e = results.entrySet().iterator().next();
                 return new SingleInternalData(lkp, e.getKey(), e.getValue());
             }
-            
+
             return new RealInternalData(lkp, results);
         }
 
@@ -850,7 +934,7 @@ public class ProxyLookup extends Lookup {
         final Collection<Reference<R>> references() {
             return getResults().values();
         }
-        
+
         final <T> ImmutableInternalData removeTemplate(ProxyLookup proxy, Template<T> template) {
             if (getResults().containsKey(template)) {
                 HashMap<Template,Reference<R>> c = new HashMap<Template, Reference<ProxyLookup.R>>(getResults());
@@ -865,12 +949,12 @@ public class ProxyLookup extends Lookup {
                 return this;
             }
         }
-        
+
         <T> R<T> findResult(ProxyLookup proxy, ImmutableInternalData[] newData, Template<T> template) {
             assert Thread.holdsLock(proxy);
-            
+
             Map<Template,Reference<R>> map = getResults();
-            
+
             Reference<R> ref = map.get(template);
             R r = (ref == null) ? null : ref.get();
 
@@ -878,7 +962,7 @@ public class ProxyLookup extends Lookup {
                 newData[0] = this;
                 return convertResult(r);
             }
-            
+
             HashMap<Template, Reference<R>> res = new HashMap<Template, Reference<R>>(map);
             R<T> newR = new R<T>(proxy, template);
             res.put(template, new java.lang.ref.SoftReference<R>(newR));
@@ -887,13 +971,13 @@ public class ProxyLookup extends Lookup {
         }
         final ImmutableInternalData setLookupsNoFire(Lookup[] lookups, boolean skipCheck) {
             Object l;
-            
+
             if (!skipCheck) {
                 Lookup[] previous = getLookups(false);
                 if (previous == lookups) {
                     return this;
                 }
-            
+
                 if (previous.length == lookups.length) {
                     int same = 0;
                     for (int i = 0; i < previous.length; i++) {
@@ -907,7 +991,7 @@ public class ProxyLookup extends Lookup {
                     }
                 }
             }
-            
+
             if (lookups.length == 1) {
                 l = lookups[0];
                 assert l != null : "Cannot assign null delegate";
@@ -918,11 +1002,11 @@ public class ProxyLookup extends Lookup {
                     l = lookups.clone();
                 }
             }
-            
+
             if (isEmpty() && l == EMPTY_ARR) {
                 return this;
             }
-            
+
             return create(l, getResults());
         }
         final Lookup[] getLookups(boolean clone) {
@@ -938,17 +1022,17 @@ public class ProxyLookup extends Lookup {
             }
         }
         final List<Lookup> getLookupsList() {
-            return Arrays.asList(getLookups(false));            
+            return Arrays.asList(getLookups(false));
         }
 
     } // end of ImmutableInternalData
-    
+
     private static final class SingleInternalData extends ImmutableInternalData {
         /** lookups to delegate to (either Lookup or array of Lookups) */
         private final Object lookups;
         private final Template template;
         private final Reference<ProxyLookup.R> result;
-                
+
         public SingleInternalData(Object lookups, Template<?> template, Reference<ProxyLookup.R> result) {
             this.lookups = lookups;
             this.template = template;
@@ -962,7 +1046,7 @@ public class ProxyLookup extends Lookup {
         protected Map<Template, Reference<R>> getResults() {
             return Collections.singletonMap(template, result);
         }
-        
+
         protected Object getRawLookups() {
             return lookups;
         }
@@ -990,7 +1074,7 @@ public class ProxyLookup extends Lookup {
             assert needsStrict = true;
             return needsStrict && !isUnmodifiable(results) ? unmodifiableMap(results) : results;
         }
-        
+
         @Override
         protected Object getRawLookups() {
             return lookups;
@@ -1008,7 +1092,7 @@ public class ProxyLookup extends Lookup {
             return res;
         }
     }
-    
+
     private static final class EmptyInternalData extends ImmutableInternalData {
         EmptyInternalData() {
         }
diff --git a/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java b/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java
new file mode 100644
index 0000000..715e9f1
--- /dev/null
+++ b/platform/openide.util.lookup/test/unit/src/org/openide/util/lookup/ProxyLookupFactoryMethodsTest.java
@@ -0,0 +1,196 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.openide.util.lookup;
+
+import java.util.Arrays;
+import static java.util.Arrays.asList;
+import java.util.HashSet;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import org.junit.Test;
+import static org.junit.Assert.*;
+import org.openide.util.Lookup;
+import org.openide.util.LookupEvent;
+import org.openide.util.LookupListener;
+import org.openide.util.lookup.ProxyLookupFactoryMethodsTest.TThreadFactory.TThread;
+
+/**
+ *
+ * @author Tim Boudreau
+ */
+public class ProxyLookupFactoryMethodsTest {
+
+    private ProxyLookup.Controller controller1;
+    private ProxyLookup.Controller controller2;
+
+    private Lookup createWithSingleConsumer(Lookup... lookups) {
+        ProxyLookup.Controller controller = new ProxyLookup.Controller();
+        controller1 = controller;
+        ProxyLookup result = new ProxyLookup(controller);
+        result.setLookups(lookups);
+        return result;
+    }
+
+    private Lookup createWithBiConsumer(Lookup... lookups) {
+        ProxyLookup.Controller controller = new ProxyLookup.Controller();
+        controller2 = controller;
+        ProxyLookup result = new ProxyLookup(controller);
+        result.setLookups(lookups);
+        return result;
+    }
+
+    @Test
+    public void testCannotUseControllerOnMultipleLookups() {
+        ProxyLookup.Controller ctrllr = new ProxyLookup.Controller();
+        ProxyLookup first = new ProxyLookup(ctrllr);
+        assertTrue(first.lookupAll(String.class).isEmpty());
+        try {
+            ProxyLookup second = new ProxyLookup(ctrllr);
+            fail("Exception should have been thrown using controller more than "
+                    + "once but was able to create " + second);
+        } catch (IllegalStateException ex) {
+            // ok
+        }
+    }
+
+    @Test
+    public void testStartWithEmptyController() {
+        ProxyLookup.Controller ctrllr = new ProxyLookup.Controller();
+        ProxyLookup lkp = new ProxyLookup(ctrllr);
+        assertTrue(lkp.lookupAll(String.class).isEmpty());
+        ctrllr.setLookups(Lookups.fixed("a"), Lookups.fixed("b"));
+        assertEquals(new HashSet<>(Arrays.asList("a", "b")),
+                new HashSet<>(lkp.lookupAll(String.class)));
+    }
+
+    @Test
+    public void testSimpleFactory() {
+        Lookup a = Lookups.fixed("a");
+        Lookup b = Lookups.fixed("b");
+        Lookup c = Lookups.fixed("c");
+
+        Lookup target = createWithSingleConsumer(a, b);
+        assertNotNull(controller1);
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b")));
+        assertFalse(target.lookupAll(String.class).contains("c"));
+
+        controller1.setLookups(new Lookup[]{a, b, c});
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b", "c")));
+
+        controller1.setLookups(new Lookup[0]);
+        assertTrue(target.lookupAll(String.class).isEmpty());
+    }
+
+    @Test
+    public void testThreadedFactory() throws Throwable {
+        ExecutorService svc = Executors.newSingleThreadExecutor(new TThreadFactory());
+        Lookup a = Lookups.fixed("a");
+        Lookup b = Lookups.fixed("b");
+        Lookup c = Lookups.fixed("c");
+
+        Lookup target = createWithBiConsumer(a, b);
+        Lookup.Result<String> result = target.lookupResult(String.class);
+
+        LL lis = new LL();
+        result.addLookupListener(lis);
+        // Ugh, ProxyLookup.LazyList does not implement the contract
+        // of Collection.equals().
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("a", "b")));
+
+        assertNotNull(controller2);
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b")));
+        assertFalse(target.lookupAll(String.class).contains("c"));
+
+        controller2.setLookups(svc, new Lookup[]{a, b, c});
+
+        lis.assertNotifiedInExecutor();
+
+        assertTrue(target.lookupAll(String.class).containsAll(asList("a", "b", "c")));
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("a", "b", "c")));
+
+        controller2.setLookups(svc, new Lookup[0]);
+        lis.assertNotifiedInExecutor();
+        assertTrue(target.lookupAll(String.class).isEmpty());
+        assertTrue(result.allInstances().isEmpty());
+
+        controller2.setLookups(null, new Lookup[]{b, c});
+        assertTrue(target.lookupAll(String.class).containsAll(asList("b", "c")));
+        assertEquals(new HashSet<>(result.allInstances()), new HashSet<>(Arrays.asList("b", "c")));
+        lis.assertNotifiedSynchronously();
+    }
+
+    static final class TThreadFactory implements ThreadFactory {
+
+        @Override
+        public Thread newThread(Runnable r) {
+            return new TThread(r);
+        }
+
+        static class TThread extends Thread {
+
+            TThread(Runnable r) {
+                super(r);
+                setName("test-thread");
+                setDaemon(true);
+            }
+        }
+    }
+
+    static class LL implements LookupListener {
+
+        private Thread notifyThread;
+        private CountDownLatch latch = new CountDownLatch(1);
+
+        void assertNotifiedInExecutor() throws InterruptedException {
+            CountDownLatch l;
+            synchronized (this) {
+                l = latch;
+            }
+            latch.await(10, TimeUnit.SECONDS);
+            Thread t;
+            synchronized (this) {
+                t = notifyThread;
+                notifyThread = null;
+            }
+            assertNotNull(t);
+            assertTrue(t instanceof TThread);
+        }
+
+        void assertNotifiedSynchronously() throws InterruptedException {
+            assertSame(Thread.currentThread(), notifyThread);
+        }
+
+        @Override
+        public void resultChanged(LookupEvent ev) {
+            CountDownLatch l;
+            synchronized (this) {
+                notifyThread = Thread.currentThread();
+                l = latch;
+                latch = new CountDownLatch(1);
+                assert l != null;
+            }
+            l.countDown();
+        }
+
+    }
+
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists