An attempt to make the result hold the data weak, but it does not work because of GC of template's class
2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6 * The contents of this file are subject to the terms of either the GNU
7 * General Public License Version 2 only ("GPL") or the Common
8 * Development and Distribution License("CDDL") (collectively, the
9 * "License"). You may not use this file except in compliance with the
10 * License. You can obtain a copy of the License at
11 * http://www.netbeans.org/cddl-gplv2.html
12 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13 * specific language governing permissions and limitations under the
14 * License. When distributing the software, include this License Header
15 * Notice in each file and include the License file at
16 * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17 * particular file as subject to the "Classpath" exception as provided
18 * by Sun in the GPL Version 2 section of the License file that
19 * accompanied this code. If applicable, add the following below the
20 * License Header, with the fields enclosed by brackets [] replaced by
21 * your own identifying information:
22 * "Portions Copyrighted [year] [name of copyright owner]"
26 * The Original Software is NetBeans. The Initial Developer of the Original
27 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28 * Microsystems, Inc. All Rights Reserved.
30 * If you wish your version of this file to be governed by only the CDDL
31 * or only the GPL Version 2, indicate your decision by adding
32 * "[Contributor] elects to include this software in this distribution
33 * under the [CDDL or GPL Version 2] license." If you do not indicate a
34 * single choice of license, a recipient has the option to distribute
35 * your version of this file under either the CDDL, the GPL Version 2 or
36 * to extend the choice of license to its licensees as provided above.
37 * However, if you add GPL Version 2 code and therefore, elected the GPL
38 * Version 2 license, then the option applies only if the new code is
39 * made subject to such option by the copyright holder.
42 package org.openide.util.lookup;
44 import java.lang.ref.Reference;
45 import java.lang.ref.WeakReference;
46 import java.util.ArrayList;
47 import java.util.Arrays;
48 import java.util.Collection;
49 import java.util.Collections;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.IdentityHashMap;
53 import java.util.Iterator;
54 import java.util.List;
57 import javax.swing.event.EventListenerList;
58 import org.openide.util.Lookup;
59 import org.openide.util.LookupEvent;
60 import org.openide.util.LookupListener;
61 import org.openide.util.Utilities;
63 /** Implementation of lookup that can delegate to others.
65 * @author Jaroslav Tulach
68 public class ProxyLookup extends Lookup {
69 /** empty array of lookups for potential use */
70 static final Lookup[] EMPTY_ARR = new Lookup[0];
72 /** lookups to delegate to (either Lookup or array of Lookups) */
73 private Object lookups;
75 /** data representing the state of the lookup */
76 private ImmutableInternalData data;
78 /** Create a proxy to some other lookups.
79 * @param lookups the initial delegates
81 public ProxyLookup(Lookup... lookups) {
82 this.setLookupsNoFire(lookups);
86 * Create a lookup initially proxying to no others.
87 * Permits serializable subclasses.
90 protected ProxyLookup() {
95 public String toString() {
96 return "ProxyLookup(class=" + getClass() + ")->" + Arrays.asList(getLookups(false)); // NOI18N
99 /** Getter for the delegates.
100 * @return the array of lookups we delegate to
103 protected final Lookup[] getLookups() {
104 synchronized (ProxyLookup.this) {
105 return getLookups(true);
109 /** getter for the delegates, that can but need not do a clone.
110 * @param clone true if clone of internal array is requested
112 private final Lookup[] getLookups(boolean clone) {
113 assert Thread.holdsLock(ProxyLookup.this);
114 Object l = this.lookups;
115 if (l instanceof Lookup) {
116 return new Lookup[] { (Lookup)l };
118 Lookup[] arr = (Lookup[])l;
126 private Set<Lookup> identityHashSet(Collection<Lookup> current) {
127 Map<Lookup,Void> map = new IdentityHashMap<Lookup, Void>();
128 for (Lookup lookup : current) {
129 map.put(lookup, null);
134 /** Called from setLookups and constructor.
135 * @param lookups the lookups to setup
137 private void setLookupsNoFire(Lookup[] lookups) {
138 if (lookups.length == 1) {
139 this.lookups = lookups[0];
140 assert this.lookups != null : "Cannot assign null delegate";
142 if (lookups.length == 0) {
143 this.lookups = EMPTY_ARR;
145 this.lookups = lookups.clone();
151 * Changes the delegates.
153 * @param lookups the new lookups to delegate to
154 * @since 1.19 protected
156 protected final void setLookups(Lookup... lookups) {
157 Collection<Reference<R>> arr;
162 Map<Result,LookupListener> toRemove = new IdentityHashMap<Lookup.Result, LookupListener>();
163 Map<Result,LookupListener> toAdd = new IdentityHashMap<Lookup.Result, LookupListener>();
165 synchronized (ProxyLookup.this) {
166 old = getLookups(false);
167 current = identityHashSet(Arrays.asList(old));
168 newL = identityHashSet(Arrays.asList(lookups));
170 setLookupsNoFire(lookups);
172 if (getData() == null) {
173 // no affected results => exit
177 arr = getData().references();
179 Set<Lookup> removed = identityHashSet(current);
180 removed.removeAll(newL); // current contains just those lookups that have disappeared
181 newL.removeAll(current); // really new lookups
183 if (removed.isEmpty() && newL.isEmpty()) {
184 // no need to notify changes
188 for (Reference<R> ref : arr) {
191 r.lookupChange(newL, removed, old, lookups, toAdd, toRemove);
196 // better to do this later than in synchronized block
197 for (Map.Entry<Result, LookupListener> e : toRemove.entrySet()) {
198 e.getKey().removeLookupListener(e.getValue());
200 for (Map.Entry<Result, LookupListener> e : toAdd.entrySet()) {
201 e.getKey().addLookupListener(e.getValue());
205 // this cannot be done from the synchronized block
206 ArrayList<Object> evAndListeners = new ArrayList<Object>();
207 for (Reference<R> ref : arr) {
210 r.collectFires(evAndListeners);
215 Iterator it = evAndListeners.iterator();
216 while (it.hasNext()) {
217 LookupEvent ev = (LookupEvent)it.next();
218 LookupListener l = (LookupListener)it.next();
224 /** Notifies subclasses that a query is about to be processed.
225 * Subclasses can update its state before the actual processing
226 * begins. It is allowed to call <code>setLookups</code> method
227 * to change/update the set of objects the proxy delegates to.
229 * @param template the template of the query
232 protected void beforeLookup(Template<?> template) {
235 public final <T> T lookup(Class<T> clazz) {
236 beforeLookup(new Template<T>(clazz));
239 synchronized (ProxyLookup.this) {
240 tmpLkps = this.getLookups(false);
243 for (int i = 0; i < tmpLkps.length; i++) {
244 T o = tmpLkps[i].lookup(clazz);
255 public final <T> Item<T> lookupItem(Template<T> template) {
256 beforeLookup(template);
259 synchronized (ProxyLookup.this) {
260 tmpLkps = this.getLookups(false);
263 for (int i = 0; i < tmpLkps.length; i++) {
264 Item<T> o = tmpLkps[i].lookupItem(template);
274 @SuppressWarnings("unchecked")
275 private static <T> R<T> convertResult(R r) {
279 public final <T> Result<T> lookup(Lookup.Template<T> template) {
280 synchronized (ProxyLookup.this) {
281 ImmutableInternalData[] res = { data };
282 R<T> newR = ImmutableInternalData.findResult(this, res, template);
288 /** Unregisters a template from the has map.
290 private final void unregisterTemplate(Template<?> template) {
291 synchronized (ProxyLookup.this) {
292 ImmutableInternalData id = getData();
296 setData(id.removeTemplate(this, template));
300 private ImmutableInternalData getData() {
304 private void setData(ImmutableInternalData data) {
305 assert Thread.holdsLock(ProxyLookup.this);
309 /** Result of a lookup request. Allows access to single object
310 * that was found (not too useful) and also to all objects found
313 private static final class R<T> extends WaitableResult<T> {
314 /** list of listeners added */
315 private javax.swing.event.EventListenerList listeners;
317 /** collection of Objects */
318 private Collection[] cache;
320 /** weak listener & result */
321 private final WeakResult<T> weakL;
322 private final Lookup.Template<T> template;
326 public R(ProxyLookup proxy, Lookup.Template<T> t) {
327 this.weakL = new WeakResult<T>(proxy, this);
331 @SuppressWarnings("unchecked")
332 private Result<T>[] newResults(int len) {
333 return new Result[len];
336 /** initializes the results
338 private Result<T>[] initResults() {
341 synchronized (weakL.getLock()) {
342 if (weakL.getResults() != null) {
343 return weakL.getResults();
345 myLkps = weakL.getLookups(false);
348 Result<T>[] arr = newResults(myLkps.length);
350 for (int i = 0; i < arr.length; i++) {
351 arr[i] = myLkps[i].lookup(template);
354 synchronized (weakL.getLock()) {
355 Lookup[] currentLkps = weakL.getLookups(false);
356 if (currentLkps.length != myLkps.length) {
359 for (int i = 0; i < currentLkps.length; i++) {
360 if (currentLkps[i] != myLkps[i]) {
365 // some other thread might compute the result mean while.
366 // if not finish the computation yourself
367 if (weakL.getResults() != null) {
368 return weakL.getResults();
371 for (int i = 0; i < arr.length; i++) {
372 arr[i].addLookupListener(weakL);
375 weakL.setResults(arr);
382 /** Called when there is a change in the list of proxied lookups.
383 * @param added set of added lookups
384 * @param remove set of removed lookups
385 * @param current array of current lookups
387 protected void lookupChange(
388 Set<Lookup> added, Set<Lookup> removed, Lookup[] old, Lookup[] current,
389 Map<Result,LookupListener> toAdd, Map<Result,LookupListener> toRemove
391 synchronized (weakL.getLock()) {
392 if (weakL.getResults() == null) {
393 // not computed yet, do not need to do anything
397 // map (Lookup, Lookup.Result)
398 Map<Lookup,Result<T>> map = new IdentityHashMap<Lookup,Result<T>>(old.length * 2);
400 for (int i = 0; i < old.length; i++) {
401 if (removed.contains(old[i])) {
403 toRemove.put(weakL.getResults()[i], weakL);
405 // remember the association
406 map.put(old[i], weakL.getResults()[i]);
410 Lookup.Result<T>[] arr = newResults(current.length);
412 for (int i = 0; i < current.length; i++) {
413 if (added.contains(current[i])) {
415 arr[i] = current[i].lookup(template);
416 toAdd.put(arr[i], weakL);
419 arr[i] = map.get(current[i]);
421 if (arr[i] == null) {
423 throw new IllegalStateException();
428 // remember the new results
429 weakL.setResults(arr);
435 public void addLookupListener(LookupListener l) {
436 synchronized (weakL.getLock()) {
437 if (listeners == null) {
438 listeners = new EventListenerList();
442 listeners.add(LookupListener.class, l);
447 public void removeLookupListener(LookupListener l) {
448 if (listeners != null) {
449 listeners.remove(LookupListener.class, l);
453 /** Access to all instances in the result.
454 * @return collection of all instances
456 @SuppressWarnings("unchecked")
457 public java.util.Collection<T> allInstances() {
458 return computeResult(0);
461 /** Classes of all results. Set of the most concreate classes
462 * that are registered in the system.
463 * @return set of Class objects
465 @SuppressWarnings("unchecked")
467 public java.util.Set<Class<? extends T>> allClasses() {
468 return (java.util.Set<Class<? extends T>>) computeResult(1);
471 /** All registered items. The collection of all pairs of
472 * ii and their classes.
473 * @return collection of Lookup.Item
475 @SuppressWarnings("unchecked")
477 public java.util.Collection<? extends Item<T>> allItems() {
478 return computeResult(2);
481 /** Computes results from proxied lookups.
482 * @param indexToCache 0 = allInstances, 1 = allClasses, 2 = allItems
483 * @return the collection or set of the objects
485 private java.util.Collection computeResult(int indexToCache) {
487 Lookup.Result<T>[] arr = myBeforeLookup();
489 // if the call to beforeLookup resulted in deletion of caches
490 synchronized (weakL.getLock()) {
491 if (getCache() != null) {
492 Collection result = getCache()[indexToCache];
493 if (result != null) {
499 // initialize the collection to hold result
500 Collection<Object> compute;
501 Collection<Object> ret;
503 if (indexToCache == 1) {
504 HashSet<Object> s = new HashSet<Object>();
506 ret = Collections.unmodifiableSet(s);
508 List<Object> l = new ArrayList<Object>(arr.length * 2);
510 ret = Collections.unmodifiableList(l);
513 // fill the collection
514 for (int i = 0; i < arr.length; i++) {
515 switch (indexToCache) {
517 compute.addAll(arr[i].allInstances());
520 compute.addAll(arr[i].allClasses());
523 compute.addAll(arr[i].allItems());
526 assert false : "Wrong index: " + indexToCache;
532 synchronized (weakL.getLock()) {
533 if (getCache() == null) {
534 // initialize the cache to indicate this result is in use
535 setCache(new Collection[3]);
538 if (arr == weakL.getResults()) {
539 // updates the results, if the results have not been
540 // changed during the computation of allInstances
541 getCache()[indexToCache] = ret;
548 /** When the result changes, fire the event.
550 public void resultChanged(LookupEvent ev) {
554 protected void collectFires(Collection<Object> evAndListeners) {
555 // clear cached instances
557 Collection oldInstances;
558 synchronized (weakL.getLock()) {
559 if (getCache() == null) {
560 // nobody queried the result yet
563 oldInstances = getCache()[0];
564 oldItems = getCache()[2];
567 if (listeners == null || listeners.getListenerCount() == 0) {
569 setCache(new Collection[3]);
573 // ignore events if they arrive as a result of call to allItems
574 // or allInstances, bellow...
578 boolean modified = true;
580 if (oldItems != null) {
581 Collection newItems = allItems();
582 if (oldItems.equals(newItems)) {
586 if (oldInstances != null) {
587 Collection newInstances = allInstances();
588 if (oldInstances.equals(newInstances)) {
592 synchronized (weakL.getLock()) {
593 if (getCache() == null) {
594 // we have to initialize the cache
595 // to show that the result has been initialized
596 setCache(new Collection[3]);
603 LookupEvent ev = new LookupEvent(this);
604 AbstractLookup.notifyListeners(listeners.getListenerList(), ev, evAndListeners);
608 /** Implementation of my before lookup.
609 * @return results to work on.
611 private Lookup.Result<T>[] myBeforeLookup() {
612 weakL.proxyBefore(template);
614 Lookup.Result<T>[] arr = initResults();
616 // invoke update on the results
617 for (int i = 0; i < arr.length; i++) {
618 if (arr[i] instanceof WaitableResult) {
619 WaitableResult w = (WaitableResult) arr[i];
620 w.beforeLookup(template);
627 /** Used by proxy results to synchronize before lookup.
629 protected void beforeLookup(Lookup.Template t) {
630 if (t.getType() == template.getType()) {
635 private Collection[] getCache() {
639 private void setCache(Collection[] cache) {
640 assert Thread.holdsLock(weakL.getLock());
644 private static final class WeakResult<T> extends WaitableResult<T> implements LookupListener, Runnable {
646 private Lookup.Result<T>[] results;
648 private final Reference<R> result;
650 private final Reference<ProxyLookup> proxy;
652 public WeakResult(ProxyLookup proxy, R r) {
653 this.result = new RefR(r, this);
654 this.proxy = new WeakReference<ProxyLookup>(proxy);
657 protected void beforeLookup(Lookup.Template t) {
666 final void unregisterTemplate() {
667 ProxyLookup p = proxy.get();
669 Lookup.Template<T> template = r == null ? null : r.template;
670 if (p != null && template != null) {
671 p.unregisterTemplate(template);
675 final void proxyBefore(Template template) {
676 ProxyLookup p = proxy.get();
678 p.beforeLookup(template);
682 final Object getLock() {
683 ProxyLookup p = proxy.get();
687 // some fallback to lock on that will not change once the ProxyLookup is GCed
692 final Lookup[] getLookups(boolean clone) {
693 ProxyLookup p = proxy.get();
695 return p.getLookups(clone);
702 final void removeListeners() {
703 Lookup.Result<T>[] arr = this.getResults();
708 for(int i = 0; i < arr.length; i++) {
709 arr[i].removeLookupListener(this);
713 protected void collectFires(Collection<Object> evAndListeners) {
714 R<?> r = result.get();
716 r.collectFires(evAndListeners);
722 public void addLookupListener(LookupListener l) {
726 public void removeLookupListener(LookupListener l) {
730 public Collection<T> allInstances() {
735 public void resultChanged(LookupEvent ev) {
745 public Collection<? extends Item<T>> allItems() {
751 public Set<Class<? extends T>> allClasses() {
760 private Lookup.Result<T>[] getResults() {
764 private void setResults(Lookup.Result<T>[] results) {
765 this.results = results;
767 } // end of WeakResult
768 private static final class ImmutableInternalData extends Object {
769 /** map of templates to currently active results */
770 private final HashMap<Template<?>,Reference<R>> results;
772 public ImmutableInternalData(HashMap<Template<?>, Reference<ProxyLookup.R>> results) {
773 this.results = results;
776 final Collection<Reference<R>> references() {
777 return results.values();
780 final <T> ImmutableInternalData removeTemplate(ProxyLookup proxy, Template<T> template) {
781 if (results.containsKey(template)) {
782 HashMap<Template<?>,Reference<R>> c = new HashMap<Lookup.Template<?>, Reference<ProxyLookup.R>>(results);
783 Reference<R> ref = c.remove(template);
784 if (ref != null && ref.get() != null) {
785 // seems like there is a reference to a result for this template
786 // thta is still alive
789 return new ImmutableInternalData(c);
796 static <T> R<T> findResult(ProxyLookup proxy, ImmutableInternalData[] oldAndNew, Template<T> template) {
797 assert Thread.holdsLock(proxy);
799 if (oldAndNew[0] != null) {
800 Reference<R> ref = oldAndNew[0].results.get(template);
801 R r = (ref == null) ? null : ref.get();
804 return convertResult(r);
808 HashMap<Template<?>, Reference<R>> res;
809 if (oldAndNew[0] == null) {
810 res = new HashMap<Template<?>, Reference<R>>();
812 res = new HashMap<Template<?>, Reference<R>>(oldAndNew[0].results);
815 R<T> newR = new R<T>(proxy, template);
816 res.put(template, new java.lang.ref.SoftReference<R>(newR));
817 oldAndNew[0] = new ImmutableInternalData(res);
822 private static final class RefR extends WeakReference<R> implements Runnable {
823 private WeakResult weakL;
824 public RefR(R r, WeakResult weakL) {
825 super(r, Utilities.activeReferenceQueue());
830 weakL.unregisterTemplate();