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;
62
/** Implementation of lookup that can delegate to others.
64
* @author Jaroslav Tulach
67
public class ProxyLookup extends Lookup {
68
/** empty array of lookups for potential use */
69
private static final Lookup[] EMPTY_ARR = new Lookup[0];
71
/** lookups to delegate to (either Lookup or array of Lookups) */
72
private Object lookups;
74
/** map of templates to currently active results */
75
private HashMap<Template<?>,Reference<R>> results;
77
/** Create a proxy to some other lookups.
78
* @param lookups the initial delegates
80
public ProxyLookup(Lookup... lookups) {
81
this.setLookupsNoFire(lookups);
85
* Create a lookup initially proxying to no others.
86
* Permits serializable subclasses.
89
protected ProxyLookup() {
94
public String toString() {
95
return "ProxyLookup(class=" + getClass() + ")->" + Arrays.asList(getLookups(false)); // NOI18N
98
/** Getter for the delegates.
99
* @return the array of lookups we delegate to
102
protected final Lookup[] getLookups() {
103
return getLookups(true);
106
/** getter for the delegates, that can but need not do a clone.
107
* @param clone true if clone of internal array is requested
109
private final Lookup[] getLookups(boolean clone) {
110
Object l = this.lookups;
111
if (l instanceof Lookup) {
112
return new Lookup[] { (Lookup)l };
114
Lookup[] arr = (Lookup[])l;
122
private Set<Lookup> identityHashSet(Collection<Lookup> current) {
123
Map<Lookup,Void> map = new IdentityHashMap<Lookup, Void>();
124
for (Lookup lookup : current) {
125
map.put(lookup, null);
130
/** Called from setLookups and constructor.
131
* @param lookups the lookups to setup
133
private void setLookupsNoFire(Lookup[] lookups) {
134
if (lookups.length == 1) {
135
this.lookups = lookups[0];
136
assert this.lookups != null : "Cannot assign null delegate";
138
if (lookups.length == 0) {
139
this.lookups = EMPTY_ARR;
141
this.lookups = lookups.clone();
147
* Changes the delegates.
149
* @param lookups the new lookups to delegate to
150
* @since 1.19 protected
152
protected final void setLookups(Lookup... lookups) {
153
Collection<Reference<R>> arr;
158
Map<Result,LookupListener> toRemove = new IdentityHashMap<Lookup.Result, LookupListener>();
159
Map<Result,LookupListener> toAdd = new IdentityHashMap<Lookup.Result, LookupListener>();
161
synchronized (this) {
162
old = getLookups(false);
163
current = identityHashSet(Arrays.asList(old));
164
newL = identityHashSet(Arrays.asList(lookups));
166
setLookupsNoFire(lookups);
168
if ((results == null) || results.isEmpty()) {
169
// no affected results => exit
173
arr = new ArrayList<Reference<R>>(results.values());
175
Set<Lookup> removed = identityHashSet(current);
176
removed.removeAll(newL); // current contains just those lookups that have disappeared
177
newL.removeAll(current); // really new lookups
179
if (removed.isEmpty() && newL.isEmpty()) {
180
// no need to notify changes
184
for (Reference<R> ref : arr) {
187
r.lookupChange(newL, removed, old, lookups, toAdd, toRemove);
192
// better to do this later than in synchronized block
193
for (Map.Entry<Result, LookupListener> e : toRemove.entrySet()) {
194
e.getKey().removeLookupListener(e.getValue());
196
for (Map.Entry<Result, LookupListener> e : toAdd.entrySet()) {
197
e.getKey().addLookupListener(e.getValue());
201
// this cannot be done from the synchronized block
202
ArrayList<Object> evAndListeners = new ArrayList<Object>();
203
for (Reference<R> ref : arr) {
206
r.collectFires(evAndListeners);
211
Iterator it = evAndListeners.iterator();
212
while (it.hasNext()) {
213
LookupEvent ev = (LookupEvent)it.next();
214
LookupListener l = (LookupListener)it.next();
220
/** Notifies subclasses that a query is about to be processed.
221
* Subclasses can update its state before the actual processing
222
* begins. It is allowed to call <code>setLookups</code> method
223
* to change/update the set of objects the proxy delegates to.
225
* @param template the template of the query
228
protected void beforeLookup(Template<?> template) {
231
public final <T> T lookup(Class<T> clazz) {
232
beforeLookup(new Template<T>(clazz));
234
Lookup[] tmpLkps = this.getLookups(false);
236
for (int i = 0; i < tmpLkps.length; i++) {
237
T o = tmpLkps[i].lookup(clazz);
248
public final <T> Item<T> lookupItem(Template<T> template) {
249
beforeLookup(template);
251
Lookup[] tmpLkps = this.getLookups(false);
253
for (int i = 0; i < tmpLkps.length; i++) {
254
Item<T> o = tmpLkps[i].lookupItem(template);
264
@SuppressWarnings("unchecked")
265
private static <T> R<T> convertResult(R r) {
269
public final synchronized <T> Result<T> lookup(Lookup.Template<T> template) {
270
if (results != null) {
271
Reference<R> ref = results.get(template);
272
R r = (ref == null) ? null : ref.get();
275
return convertResult(r);
278
results = new HashMap<Template<?>,Reference<R>>();
281
R<T> newR = new R<T>(template);
282
results.put(template, new java.lang.ref.SoftReference<R>(newR));
287
/** Unregisters a template from the has map.
289
private final synchronized void unregisterTemplate(Template<?> template) {
290
if (results == null) {
294
Reference<R> ref = results.remove(template);
296
if ((ref != null) && (ref.get() != null)) {
297
// seems like there is a reference to a result for this template
298
// thta is still alive
299
results.put(template, ref);
303
/** Result of a lookup request. Allows access to single object
304
* that was found (not too useful) and also to all objects found
307
private final class R<T> extends WaitableResult<T> {
308
/** list of listeners added */
309
private javax.swing.event.EventListenerList listeners;
311
/** template for this result */
312
private Lookup.Template<T> template;
314
/** collection of Objects */
315
private Collection[] cache;
317
/** weak listener & result */
318
private WeakResult<T> weakL;
322
public R(Lookup.Template<T> t) {
324
weakL = new WeakResult<T>(this);
327
/** When garbage collected, remove the template from the has map.
330
protected void finalize() {
331
unregisterTemplate(template);
334
@SuppressWarnings("unchecked")
335
private Result<T>[] newResults(int len) {
336
return new Result[len];
339
/** initializes the results
341
private Result<T>[] initResults() {
342
synchronized (this) {
343
if (weakL.results != null) {
344
return weakL.results;
348
Lookup[] myLkps = getLookups(false);
349
Result<T>[] arr = newResults(myLkps.length);
351
for (int i = 0; i < arr.length; i++) {
352
arr[i] = myLkps[i].lookup(template);
355
synchronized (this) {
356
// some other thread might compute the result mean while.
357
// if not finish the computation yourself
358
if (weakL.results != null) {
359
return weakL.results;
362
for (int i = 0; i < arr.length; i++) {
363
arr[i].addLookupListener(weakL);
372
/** Called when there is a change in the list of proxied lookups.
373
* @param added set of added lookups
374
* @param remove set of removed lookups
375
* @param current array of current lookups
377
protected void lookupChange(
378
Set<Lookup> added, Set<Lookup> removed, Lookup[] old, Lookup[] current,
379
Map<Result,LookupListener> toAdd, Map<Result,LookupListener> toRemove
381
synchronized (this) {
382
if (weakL.results == null) {
383
// not computed yet, do not need to do anything
387
// map (Lookup, Lookup.Result)
388
Map<Lookup,Result<T>> map = new IdentityHashMap<Lookup,Result<T>>(old.length * 2);
390
for (int i = 0; i < old.length; i++) {
391
if (removed.contains(old[i])) {
393
toRemove.put(weakL.results[i], weakL);
395
// remember the association
396
map.put(old[i], weakL.results[i]);
400
Lookup.Result<T>[] arr = newResults(current.length);
402
for (int i = 0; i < current.length; i++) {
403
if (added.contains(current[i])) {
405
arr[i] = current[i].lookup(template);
406
toAdd.put(arr[i], weakL);
409
arr[i] = map.get(current[i]);
411
if (arr[i] == null) {
413
throw new IllegalStateException();
418
// remember the new results
425
public void addLookupListener(LookupListener l) {
426
synchronized (this) {
427
if (listeners == null) {
428
listeners = new EventListenerList();
432
listeners.add(LookupListener.class, l);
437
public void removeLookupListener(LookupListener l) {
438
if (listeners != null) {
439
listeners.remove(LookupListener.class, l);
443
/** Access to all instances in the result.
444
* @return collection of all instances
446
@SuppressWarnings("unchecked")
447
public java.util.Collection<T> allInstances() {
448
return computeResult(0);
451
/** Classes of all results. Set of the most concreate classes
452
* that are registered in the system.
453
* @return set of Class objects
455
@SuppressWarnings("unchecked")
457
public java.util.Set<Class<? extends T>> allClasses() {
458
return (java.util.Set<Class<? extends T>>) computeResult(1);
461
/** All registered items. The collection of all pairs of
462
* ii and their classes.
463
* @return collection of Lookup.Item
465
@SuppressWarnings("unchecked")
467
public java.util.Collection<? extends Item<T>> allItems() {
468
return computeResult(2);
471
/** Computes results from proxied lookups.
472
* @param indexToCache 0 = allInstances, 1 = allClasses, 2 = allItems
473
* @return the collection or set of the objects
475
private java.util.Collection computeResult(int indexToCache) {
477
Lookup.Result<T>[] arr = myBeforeLookup();
479
// if the call to beforeLookup resulted in deletion of caches
480
synchronized (this) {
482
Collection result = cache[indexToCache];
483
if (result != null) {
489
// initialize the collection to hold result
490
Collection<Object> compute;
491
Collection<Object> ret;
493
if (indexToCache == 1) {
494
HashSet<Object> s = new HashSet<Object>();
496
ret = Collections.unmodifiableSet(s);
498
List<Object> l = new ArrayList<Object>(arr.length * 2);
500
ret = Collections.unmodifiableList(l);
503
// fill the collection
504
for (int i = 0; i < arr.length; i++) {
505
switch (indexToCache) {
507
compute.addAll(arr[i].allInstances());
510
compute.addAll(arr[i].allClasses());
513
compute.addAll(arr[i].allItems());
516
assert false : "Wrong index: " + indexToCache;
522
synchronized (this) {
524
// initialize the cache to indicate this result is in use
525
cache = new Collection[3];
528
if (arr == weakL.results) {
529
// updates the results, if the results have not been
530
// changed during the computation of allInstances
531
cache[indexToCache] = ret;
538
/** When the result changes, fire the event.
540
public void resultChanged(LookupEvent ev) {
544
protected void collectFires(Collection<Object> evAndListeners) {
545
// clear cached instances
547
Collection oldInstances;
548
synchronized (this) {
550
// nobody queried the result yet
553
oldInstances = cache[0];
557
if (listeners == null || listeners.getListenerCount() == 0) {
559
cache = new Collection[3];
563
// ignore events if they arrive as a result of call to allItems
564
// or allInstances, bellow...
568
boolean modified = true;
570
if (oldItems != null) {
571
Collection newItems = allItems();
572
if (oldItems.equals(newItems)) {
576
if (oldInstances != null) {
577
Collection newInstances = allInstances();
578
if (oldInstances.equals(newInstances)) {
582
synchronized (this) {
584
// we have to initialize the cache
585
// to show that the result has been initialized
586
cache = new Collection[3];
593
LookupEvent ev = new LookupEvent(this);
594
AbstractLookup.notifyListeners(listeners.getListenerList(), ev, evAndListeners);
598
/** Implementation of my before lookup.
599
* @return results to work on.
601
private Lookup.Result<T>[] myBeforeLookup() {
602
ProxyLookup.this.beforeLookup(template);
604
Lookup.Result<T>[] arr = initResults();
606
// invoke update on the results
607
for (int i = 0; i < arr.length; i++) {
608
if (arr[i] instanceof WaitableResult) {
609
WaitableResult w = (WaitableResult) arr[i];
610
w.beforeLookup(template);
617
/** Used by proxy results to synchronize before lookup.
619
protected void beforeLookup(Lookup.Template t) {
620
if (t.getType() == template.getType()) {
625
private static final class WeakResult<T> extends WaitableResult<T> implements LookupListener {
627
private Lookup.Result<T>[] results;
629
private Reference<R> result;
631
public WeakResult(R r) {
632
this.result = new WeakReference<R>(r);
635
protected void beforeLookup(Lookup.Template t) {
644
private void removeListeners() {
645
Lookup.Result<T>[] arr = this.results;
650
for(int i = 0; i < arr.length; i++) {
651
arr[i].removeLookupListener(this);
655
protected void collectFires(Collection<Object> evAndListeners) {
656
R<?> r = result.get();
658
r.collectFires(evAndListeners);
664
public void addLookupListener(LookupListener l) {
668
public void removeLookupListener(LookupListener l) {
672
public Collection<T> allInstances() {
677
public void resultChanged(LookupEvent ev) {
687
public Collection<? extends Item<T>> allItems() {
693
public Set<Class<? extends T>> allClasses() {
697
} // end of WeakResult