/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.openide.util.lookup;
import java.util.*;
import java.lang.ref.Reference;
import javax.swing.event.EventListenerList;
import org.openide.util.Lookup;
import org.openide.util.LookupListener;
import org.openide.util.LookupEvent;
/** Implementation of lookup that can delegate to others.
*
* @author Jaroslav Tulach
* @since 1.9
*/
public class ProxyLookup extends Lookup {
/** lookups to delegate to */
private Lookup[] lookups;
/** map of templates to currently active results */
private HashMap results;
/** Create a proxy to some other lookups.
* @param lookups the initial delegates
*/
public ProxyLookup (Lookup[] lookups) {
this.lookups = lookups;
}
/**
* Create a lookup initially proxying to no others.
* Permits serializable subclasses.
* @since 3.27
*/
protected ProxyLookup() {
this(new Lookup[0]);
}
public String toString() {
return "ProxyLookup(class=" + getClass() + ")->" + Arrays.asList(lookups); // NOI18N
}
/** Getter for the delegates.
* @return the array of lookups we delegate to
* @since 1.19
*/
protected final Lookup[] getLookups () {
return lookups;
}
/** Change the delegates. To forbid anybody else then the creator
* of the lookup to change the delegates, this method is protected.
*
* @param lookups the new lookups to delegate to
* @since 1.19 protected
*/
protected final void setLookups (Lookup[] lookups) {
Reference[] arr;
HashSet newL;
HashSet current;
Lookup[] old;
synchronized (this) {
current = new HashSet (Arrays.asList (this.lookups));
newL = new HashSet (Arrays.asList (lookups));
old = this.lookups;
this.lookups = lookups;
if (results == null || results.isEmpty ()) {
// no affected results => exit
return;
}
arr = (Reference[])results.values ().toArray( new Reference[0] );
HashSet removed = new HashSet (current);
removed.removeAll (newL); // current contains just those lookups that have disappeared
newL.removeAll (current); // really new lookups
if (removed.isEmpty () && newL.isEmpty ()) {
// no need to notify changes
return;
}
for (int i = 0; i < arr.length; i++) {
R r = (R)arr[i].get ();
if (r != null) {
r.lookupChange (newL, removed, old, lookups);
}
}
}
// this cannot be done from the synchronized block
for (int i = 0; i < arr.length; i++) {
R r = (R)arr[i].get ();
if (r != null) {
r.resultChanged(null);
}
}
}
/** Notifies subclasses that a query is about to be processed.
* Subclasses can update its state before the actual processing
* begins. It is allowed to call <code>setLookups</code> method
* to change/update the set of objects the proxy delegates to.
*
* @param template the template of the query
* @since 1.31
*/
protected void beforeLookup (Template template) {
}
/* Lookups an object of given interface. This is the simplest method
* for the lookuping, if more registered objects implement the given
* class any of them can be returned.
*
* @param clazz class of the object we are searching for
* @return the object implementing given class or null if no such
* has been found
*/
public final Object lookup (Class clazz) {
beforeLookup (new Template (clazz));
Lookup[] lookups = this.lookups;
for (int i = 0; i < lookups.length; i++) {
Object o = lookups[i].lookup (clazz);
if (o != null) {
return o;
}
}
return null;
}
/* Lookups the first item that matches given template.
* @param template the template to check
* @return item or null
*/
public final Item lookupItem(Template template) {
beforeLookup (template);
Lookup[] lookups = this.lookups;
for (int i = 0; i < lookups.length; i++) {
Item o = lookups[i].lookupItem (template);
if (o != null) {
return o;
}
}
return null;
}
/* The general lookup method.
* @param template the template describing the services we are looking for
* @return object containing the results
*/
public final synchronized Result lookup (Lookup.Template template) {
R r;
if (results != null) {
Reference ref = (Reference)results.get (template);
r = ref == null ? null : (R)ref.get ();
if (r != null ) {
return r;
}
} else {
results = new HashMap ();
}
r = new R (template);
results.put (template, new java.lang.ref.SoftReference (r));
return r;
}
/** Unregisters a template from the has map.
*/
private final synchronized void unregisterTemplate (Template template) {
if (results == null) return;
Reference ref = (Reference)results.remove (template);
if (ref != null && ref.get () != null) {
// seems like there is a reference to a result for this template
// thta is still alive
results.put (template, ref);
}
}
/** Result of a lookup request. Allows access to single object
* that was found (not too useful) and also to all objects found
* (more useful).
*/
private final class R extends WaitableResult implements LookupListener {
/** list of listeners added */
private javax.swing.event.EventListenerList listeners;
/** template for this result */
private Lookup.Template template;
/** all results */
private Lookup.Result[] results;
/** collection of Objects */
private Collection[] cache;
/** Constructor.
*/
public R (Lookup.Template t) {
template = t;
}
/** When garbage collected, remove the template from the has map.
*/
protected void finalize () {
unregisterTemplate (template);
}
/** initializes the results
*/
private Result[] initResults () {
if (results != null) return results;
Result[] arr = new Result[lookups.length];
for (int i = 0; i < arr.length; i++) {
arr[i] = lookups[i].lookup (template);
arr[i].addLookupListener (this);
}
cache = new Collection[3];
results = arr;
return arr;
}
/** Called when there is a change in the list of proxied lookups.
* @param added set of added lookups
* @param remove set of removed lookups
* @param current array of current lookups
*/
protected void lookupChange (
Set added, Set removed, Lookup[] old, Lookup[] current
) {
synchronized (this) {
if (results == null) {
// not computed yet, do not need to do anything
return;
}
// map (Lookup, Lookup.Result)
HashMap map = new HashMap (old.length * 2);
for (int i = 0; i < old.length; i++) {
if (removed.contains (old[i])) {
// removed lookup
results[i].removeLookupListener (this);
} else {
// remember the association
map.put (old[i], results[i]);
}
}
Lookup.Result[] arr = new Lookup.Result[current.length];
for (int i = 0; i < current.length; i++) {
if (added.contains (current[i])) {
// new lookup
arr[i] = current[i].lookup (template);
arr[i].addLookupListener (this);
} else {
// old lookup
arr[i] = (Lookup.Result)map.get (current[i]);
if (arr[i] == null) {
// assert
throw new IllegalStateException ();
}
}
}
// remember the new results
results = arr;
}
}
/** Just delegates.
*/
public void addLookupListener (LookupListener l) {
if (listeners == null) {
synchronized (this) {
if (listeners == null) {
listeners = new EventListenerList ();
}
}
}
listeners.add (LookupListener.class, l);
}
/** Just delegates.
*/
public void removeLookupListener (LookupListener l) {
if (listeners != null) {
listeners.remove (LookupListener.class, l);
}
}
/** Access to all instances in the result.
* @return collection of all instances
*/
public java.util.Collection allInstances () {
return computeResult (0);
}
/** Classes of all results. Set of the most concreate classes
* that are registered in the system.
* @return set of Class objects
*/
public java.util.Set allClasses () {
return (java.util.Set)computeResult (1);
}
/** All registered items. The collection of all pairs of
* ii and their classes.
* @return collection of Lookup.Item
*/
public java.util.Collection allItems () {
return computeResult (2);
}
/** Computes results from proxied lookups.
* @param indexToCache 0 = allInstances, 1 = allClasses, 2 = allItems
* @return the collection or set of the objects
*/
private java.util.Collection computeResult (int indexToCache) {
// results to use
Lookup.Result[] arr = myBeforeLookup ();
// if the call to beforeLookup resulted in deletion of caches
synchronized (this) {
if (cache != null && cache[indexToCache] != null) {
return cache[indexToCache];
}
}
// initialize the collection to hold result
Collection ll;
if (indexToCache == 1) {
ll = new HashSet ();
} else {
ll = new ArrayList (arr.length * 2);
}
// fill the collection
for (int i = 0; i < arr.length; i++) {
switch (indexToCache) {
case 0:
ll.addAll (arr[i].allInstances ());
break;
case 1:
ll.addAll (arr[i].allClasses ());
break;
case 2:
ll.addAll (arr[i].allItems ());
break;
}
}
synchronized (this) {
if (arr == results && cache != null) {
// updates the results, if the results have not been
// changed during the computation of allInstances
cache[indexToCache] = ll;
}
}
return ll;
}
/** When the result changes, fire the event.
*/
public void resultChanged (LookupEvent ev) {
// clear cached instances
synchronized (this) {
cache = null;
if (listeners == null) return;
}
Object[] arr = listeners.getListenerList ();
if (arr.length == 0) {
return;
}
ev = new LookupEvent (this);
AbstractLookup.notifyListeners(arr, ev);
}
/** Implementation of my before lookup.
* @return results to work on.
*/
private Lookup.Result[] myBeforeLookup () {
ProxyLookup.this.beforeLookup (template);
Lookup.Result[] arr;
synchronized (this) {
arr = initResults ();
}
// 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);
}
}
return arr;
}
/** Used by proxy results to synchronize before lookup.
*/
protected void beforeLookup(Lookup.Template t) {
if (t.getType () == template.getType ()) {
myBeforeLookup ();
}
}
}
}