diff -r ae63769675a7 openide.explorer/src/org/netbeans/modules/openide/explorer/ExplorerActionsImpl.java --- a/openide.explorer/src/org/netbeans/modules/openide/explorer/ExplorerActionsImpl.java Tue May 15 11:55:10 2012 +0200 +++ b/openide.explorer/src/org/netbeans/modules/openide/explorer/ExplorerActionsImpl.java Thu May 17 11:09:10 2012 +0200 @@ -91,7 +91,7 @@ */ public final class ExplorerActionsImpl { /** background updater of actions */ - private static final RequestProcessor RP = new RequestProcessor("Explorer Actions"); // NOI18N + private static final RequestProcessor RP = new RequestProcessor("Explorer Actions", 1, false, false); // NOI18N private static final Logger LOG = Logger.getLogger(ExplorerActionsImpl.class.getName()); diff -r ae63769675a7 openide.explorer/src/org/openide/explorer/DefaultEMLookup.java --- a/openide.explorer/src/org/openide/explorer/DefaultEMLookup.java Tue May 15 11:55:10 2012 +0200 +++ b/openide.explorer/src/org/openide/explorer/DefaultEMLookup.java Thu May 17 11:09:10 2012 +0200 @@ -54,6 +54,7 @@ import java.beans.*; import java.util.*; +import org.openide.util.RequestProcessor; /** @@ -161,9 +162,15 @@ } } - public void propertyChange(PropertyChangeEvent evt) { + public void propertyChange(final PropertyChangeEvent evt) { if (ExplorerManager.PROP_SELECTED_NODES == evt.getPropertyName()) { - updateLookups((Node[]) evt.getNewValue()); + RequestProcessor.getDefault().post(new Runnable() { + + @Override + public void run() { + updateLookups((Node[]) evt.getNewValue()); + } + }); } } diff -r ae63769675a7 openide.loaders/src/org/openide/loaders/DataNode.java --- a/openide.loaders/src/org/openide/loaders/DataNode.java Tue May 15 11:55:10 2012 +0200 +++ b/openide.loaders/src/org/openide/loaders/DataNode.java Thu May 17 11:09:10 2012 +0200 @@ -973,6 +973,12 @@ public void beforeLookup(Class clazz) { if (clazz.isAssignableFrom(FileObject.class)) { + Exception e = new Exception(); + final int len = e.getStackTrace().length; + System.err.println("depth: " + len); + if (len > 300) { + System.err.println("too deep"); + } updateFilesInCookieSet(obj.files()); } } diff -r ae63769675a7 openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java --- a/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java Tue May 15 11:55:10 2012 +0200 +++ b/openide.util.lookup/src/org/openide/util/lookup/ProxyLookup.java Thu May 17 11:09:10 2012 +0200 @@ -46,10 +46,12 @@ import java.lang.ref.Reference; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; +import java.util.EventListener; import java.util.HashMap; import java.util.HashSet; import java.util.IdentityHashMap; @@ -560,7 +562,12 @@ /** When the result changes, fire the event. */ public void resultChanged(LookupEvent ev) { - collectFires(null); + CoaleasingListener ca = installCoaleasingListener(); + try { + collectFires(null); + } finally { + uninstallCoaleasingListener(ca); + } } protected void collectFires(Collection evAndListeners) { @@ -649,12 +656,17 @@ Lookup.Result[] arr = initResults(); if (callBeforeOnWait) { - // invoke update on the results - for (int i = 0; i < arr.length; i++) { - if (arr[i] instanceof WaitableResult) { - WaitableResult w = (WaitableResult) arr[i]; - w.beforeLookup(template); + CoaleasingListener l = installCoaleasingListener(); + try { + // invoke update on the results + for (int i = 0; i < arr.length; i++) { + if (arr[i] instanceof WaitableResult) { + WaitableResult w = (WaitableResult) arr[i]; + w.beforeLookup(template); + } } + } finally { + uninstallCoaleasingListener(l); } } @@ -700,7 +712,108 @@ } } + + private CoaleasingListener installCoaleasingListener() { + synchronized (proxy()) { + if (listeners == null) { + return null; + } + if (listeners instanceof CoaleasingListener) { + CoaleasingListener ca = (CoaleasingListener)listeners; + ca.changeUsage(1); + return ca; + } + + final CoaleasingListener ca = new CoaleasingListener(listeners); + listeners = ca; + return ca; + } + } + + private void uninstallCoaleasingListener(CoaleasingListener l) { + if (l == null) { + return; + } + + synchronized (proxy()) { + CoaleasingListener ca = (CoaleasingListener) listeners; + if (ca.changeUsage(-1)) { + listeners = l.delegate; + } + } + l.deliver(); + } + } + private static final class CoaleasingListener extends EventListenerList + implements LookupListener { + final EventListenerList delegate; + private volatile LookupEvent toDeliver; + private int usageCount; + + public CoaleasingListener(EventListenerList delegate) { + this.delegate = delegate; + this.usageCount = 1; + } + + final boolean changeUsage(int delta) { + usageCount += delta; + return usageCount == 0; + } + + @Override + public Object[] getListenerList() { + return new Object[] { LookupListener.class, this }; + } + + @Override + public T[] getListeners(Class t) { + int n = LookupListener.class == t ? 1 : 0; + T[] result = (T[])Array.newInstance(t, n); + if (n > 0) { + result[0] = (T) this; + } + return result; + } + + @Override + public int getListenerCount() { + return 1; + } + + @Override + public int getListenerCount(Class t) { + return LookupListener.class == t ? 1 : 0; + } + + @Override + public void add(Class t, T l) { + delegate.add(t, l); + } + + @Override + public void remove(Class t, T l) { + delegate.remove(t, l); + } + + @Override + public void resultChanged(LookupEvent ev) { + if (toDeliver == null) { + toDeliver = ev; + } + assert toDeliver.getSource() == ev.getSource(); + } + + final void deliver() { + LookupEvent ev = toDeliver; + if (ev != null) { + for (LookupListener l : delegate.getListeners(LookupListener.class)) { + l.resultChanged(ev); + } + } + } + } + private static final class WeakRef extends WeakReference implements Runnable { final WeakResult result; final ProxyLookup proxy; @@ -1124,7 +1237,7 @@ @Override public boolean isEmpty() { - return delegate().isEmpty(); + return !iterator().hasNext(); } @Override diff -r ae63769675a7 openide.util.lookup/test/unit/src/org/openide/util/lookup/DeepProxyTest.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openide.util.lookup/test/unit/src/org/openide/util/lookup/DeepProxyTest.java Thu May 17 11:09:10 2012 +0200 @@ -0,0 +1,124 @@ +/* + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + * + * Copyright 2012 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 2012 Sun Microsystems, Inc. + */ +package org.openide.util.lookup; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.netbeans.junit.NbTestCase; +import org.openide.util.Lookup; +import org.openide.util.Lookup.Result; +import org.openide.util.LookupEvent; +import org.openide.util.LookupListener; + +/** + * + * @author Jaroslav Tulach + */ +public class DeepProxyTest extends NbTestCase implements LookupListener { + private ProxyLookup proxy; + private int cnt; + private int size; + private AtomicInteger share; + + public DeepProxyTest(String name) { + super(name); + } + + @Override + protected void setUp() throws Exception { + Matcher m = Pattern.compile("[0-9]+").matcher(getName()); + assertTrue("There is a number in " + getName(), m.find()); + size = Integer.parseInt(m.group(0)); + assertTrue("Found", size > 0); + share = new AtomicInteger(0); + + Lookup[] arr = new Lookup[size]; + for (int i = 0; i < size; i++) { + arr[i] = new AddingLkp(share); + } + + proxy = new ProxyLookup(arr); + } + + + + public void testDeep1000() { + Result res = proxy.lookupResult(Number.class); + res.addLookupListener(this); + assertEquals("Empty now", 0, res.allInstances().size()); + share.set(1); + assertEquals("Right size", size, res.allInstances().size()); + assertEquals("one change is enough", 1, cnt); + } + + @Override + public void resultChanged(LookupEvent ev) { + cnt++; + if (new Exception().getStackTrace().length > 100) { + fail("Too deep recursion!"); + } + } + + private static final class AddingLkp extends AbstractLookup { + private final AtomicInteger number; + private final InstanceContent ic; + + public AddingLkp(AtomicInteger number) { + this(number, new InstanceContent()); + } + + private AddingLkp(AtomicInteger number, InstanceContent ic) { + super(ic); + this.number = number; + this.ic = ic; + } + + @Override + protected void beforeLookup(Template template) { + if (number.get() != 0 && template.getType() == Number.class) { + ic.set(Collections.singleton(number), null); + } + } + } +}