Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.openide.util.lookup.ProxyLookup Maven / Gradle / Ivy
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
*
* 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.
*/
package org.openide.util.lookup;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.Executor;
import javax.swing.event.EventListenerList;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
/** Implementation of lookup that can delegate to others.
*
* @author Jaroslav Tulach
* @since 1.9
*/
public class ProxyLookup extends Lookup {
/** data representing the state of the lookup */
private ImmutableInternalData data;
/** Create a proxy to some other lookups.
* @param lookups the initial delegates
*/
public ProxyLookup(Lookup... lookups) {
data = ImmutableInternalData.EMPTY.setLookupsNoFire(lookups, true);
}
/**
* Create a lookup initially proxying to no others.
* Permits serializable subclasses.
* @since 3.27
*/
protected ProxyLookup() {
data = ImmutableInternalData.EMPTY;
}
@Override
public synchronized String toString() {
return "ProxyLookup(class=" + getClass() + ")->" + Arrays.asList(getData().getLookups(false)); // NOI18N
}
/** Getter for the delegates.
* @return the array of lookups we delegate to
* @since 1.19
*/
protected final Lookup[] getLookups() {
synchronized (ProxyLookup.this) {
return getData().getLookups(true);
}
}
private Set identityHashSet(Collection current) {
Map map = new IdentityHashMap();
for (Lookup lookup : current) {
map.put(lookup, null);
}
return map.keySet();
}
/**
* Changes the delegates.
*
* @param lookups the new lookups to delegate to
* @since 1.19 protected
*/
protected final void setLookups(Lookup... lookups) {
setLookups(null, lookups);
}
/**
* Changes the delegates immediatelly, notifies the listeners in provided
* executor, potentially later.
*
* @param lookups the new lookups to delegate to
* @param notifyIn executor to deliver the notification to listeners or null
* @since 7.16
*/
protected final void setLookups(Executor notifyIn, Lookup... lookups) {
Collection> arr;
Set newL;
Set current;
Lookup[] old;
Map toRemove = new IdentityHashMap();
Map toAdd = new IdentityHashMap();
ImmutableInternalData orig;
synchronized (ProxyLookup.this) {
orig = getData();
ImmutableInternalData newData = getData().setLookupsNoFire(lookups, false);
if (newData == getData()) {
return;
}
arr = setData(newData, lookups, toAdd, toRemove);
}
// better to do this later than in synchronized block
for (Map.Entry e : toRemove.entrySet()) {
e.getKey().removeLookupListener(e.getValue());
}
for (Map.Entry e : toAdd.entrySet()) {
e.getKey().addLookupListener(e.getValue());
}
// this cannot be done from the synchronized block
final ArrayList evAndListeners = new ArrayList();
for (Reference ref : arr) {
R> r = ref.get();
if (r != null) {
r.collectFires(evAndListeners);
}
}
class Notify implements Runnable {
public void run() {
Iterator it = evAndListeners.iterator();
while (it.hasNext()) {
LookupEvent ev = (LookupEvent)it.next();
LookupListener l = (LookupListener)it.next();
l.resultChanged(ev);
}
}
}
Notify n = new Notify();
if (notifyIn == null) {
n.run();
} else {
notifyIn.execute(n);
}
}
/** 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 setLookups
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) {
}
public final T lookup(Class clazz) {
beforeLookup(new Template(clazz));
Lookup[] tmpLkps;
synchronized (ProxyLookup.this) {
tmpLkps = getData().getLookups(false);
}
for (int i = 0; i < tmpLkps.length; i++) {
T o = tmpLkps[i].lookup(clazz);
if (o != null) {
return o;
}
}
return null;
}
@Override
public final Item lookupItem(Template template) {
beforeLookup(template);
Lookup[] tmpLkps;
synchronized (ProxyLookup.this) {
tmpLkps = getData().getLookups(false);
}
for (int i = 0; i < tmpLkps.length; i++) {
Item o = tmpLkps[i].lookupItem(template);
if (o != null) {
return o;
}
}
return null;
}
@SuppressWarnings("unchecked")
private static R convertResult(R r) {
return (R)r;
}
public final Result lookup(Lookup.Template template) {
synchronized (ProxyLookup.this) {
ImmutableInternalData[] res = { null };
R newR = getData().findResult(this, res, template);
setData(res[0], getData().getLookups(false), null, null);
return newR;
}
}
/** Unregisters a template from the has map.
*/
private final void unregisterTemplate(Template> template) {
synchronized (ProxyLookup.this) {
ImmutableInternalData id = getData();
if (id == null) {
return;
}
setData(id.removeTemplate(this, template), getData().getLookups(false), null, null);
}
}
private ImmutableInternalData getData() {
assert Thread.holdsLock(this);
return data;
}
private Collection> setData(
ImmutableInternalData newData, Lookup[] current,
Map toAdd, Map toRemove
) {
assert Thread.holdsLock(ProxyLookup.this);
assert newData != null;
ImmutableInternalData previous = this.getData();
if (previous == newData) {
return Collections.emptyList();
}
if (newData.isEmpty()) {
this.setData(newData);
// no affected results => exit
return Collections.emptyList();
}
Collection> arr = newData.references();
Set removed = identityHashSet(previous.getLookupsList());
Set currentSet = identityHashSet(Arrays.asList(current));
Set newL = identityHashSet(currentSet);
removed.removeAll(currentSet); // current contains just those lookups that have disappeared
newL.removeAll(previous.getLookupsList()); // really new lookups
for (Reference ref : arr) {
R> r = ref.get();
if (r != null) {
r.lookupChange(newData, current, previous, newL, removed, toAdd, toRemove);
if (this.getData() != previous) {
// the data were changed by an re-entrant call
// skip any other change processing, as it is not needed
// anymore
}
}
}
for (Reference ref : arr) {
R> r = ref.get();
if (r != null) {
r.data = newData;
}
}
this.setData(newData);
return arr;
}
private void setData(ImmutableInternalData data) {
this.data = data;
}
/** 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 static final class R extends WaitableResult {
/** weak listener & result */
private final WeakResult weakL;
/** list of listeners added */
private javax.swing.event.EventListenerList listeners;
/** collection of Objects */
private Collection[] cache;
/** associated lookup */
private ImmutableInternalData data;
/** Constructor.
*/
public R(ProxyLookup proxy, Lookup.Template t) {
this.weakL = new WeakResult(proxy, this, t);
}
private ProxyLookup proxy() {
return weakL.result.proxy;
}
@SuppressWarnings("unchecked")
private Result[] newResults(int len) {
return new Result[len];
}
@Override
protected void finalize() {
weakL.result.run();
}
/** initializes the results
*/
private Result[] initResults() {
BIG_LOOP: for (;;) {
Lookup[] myLkps;
ImmutableInternalData current;
synchronized (proxy()) {
if (weakL.getResults() != null) {
return weakL.getResults();
}
myLkps = data.getLookups(false);
current = data;
}
Result[] arr = newResults(myLkps.length);
for (int i = 0; i < arr.length; i++) {
arr[i] = myLkps[i].lookup(weakL.result.template);
}
synchronized (proxy()) {
if (current != data) {
continue;
}
Lookup[] currentLkps = data.getLookups(false);
if (currentLkps.length != myLkps.length) {
continue BIG_LOOP;
}
for (int i = 0; i < currentLkps.length; i++) {
if (currentLkps[i] != myLkps[i]) {
continue BIG_LOOP;
}
}
// some other thread might compute the result mean while.
// if not finish the computation yourself
if (weakL.getResults() != null) {
return weakL.getResults();
}
for (int i = 0; i < arr.length; i++) {
arr[i].addLookupListener(weakL);
}
weakL.setResults(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
*/
final void lookupChange(
ImmutableInternalData newData, Lookup[] current, ImmutableInternalData oldData,
Set added, Set removed,
Map toAdd, Map toRemove
) {
if (weakL.getResults() == null) {
// not computed yet, do not need to do anything
return;
}
Lookup[] old = oldData.getLookups(false);
// map (Lookup, Lookup.Result)
Map> map = new IdentityHashMap>(old.length * 2);
for (int i = 0; i < old.length; i++) {
if (removed.contains(old[i])) {
// removed lookup
if (toRemove != null) {
toRemove.put(weakL.getResults()[i], weakL);
}
} else {
// remember the association
map.put(old[i], weakL.getResults()[i]);
}
}
Lookup.Result[] arr = newResults(current.length);
for (int i = 0; i < current.length; i++) {
if (added.contains(current[i])) {
// new lookup
arr[i] = current[i].lookup(weakL.result.template);
if (toAdd != null) {
toAdd.put(arr[i], weakL);
}
} else {
// old lookup
arr[i] = map.get(current[i]);
if (arr[i] == null) {
// assert
throw new IllegalStateException();
}
}
}
// remember the new results
weakL.setResults(arr);
}
/** Just delegates.
*/
public void addLookupListener(LookupListener l) {
synchronized (proxy()) {
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
*/
@SuppressWarnings("unchecked")
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
*/
@SuppressWarnings("unchecked")
@Override
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
*/
@SuppressWarnings("unchecked")
@Override
public java.util.Collection extends Item> 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 (proxy()) {
if (getCache() != null) {
Collection result = getCache()[indexToCache];
if (result != null) {
return result;
}
}
}
// initialize the collection to hold result
Collection compute;
Collection ret;
if (indexToCache == 1) {
HashSet s = new HashSet();
compute = s;
ret = Collections.unmodifiableSet(s);
} else {
List l = new ArrayList(arr.length * 2);
compute = l;
ret = Collections.unmodifiableList(l);
}
// fill the collection
for (int i = 0; i < arr.length; i++) {
switch (indexToCache) {
case 0:
compute.addAll(arr[i].allInstances());
break;
case 1:
compute.addAll(arr[i].allClasses());
break;
case 2:
compute.addAll(arr[i].allItems());
break;
default:
assert false : "Wrong index: " + indexToCache;
}
}
synchronized (proxy()) {
if (getCache() == null) {
// initialize the cache to indicate this result is in use
setCache(new Collection[3]);
}
if (arr == weakL.getResults()) {
// updates the results, if the results have not been
// changed during the computation of allInstances
getCache()[indexToCache] = ret;
}
}
return ret;
}
/** When the result changes, fire the event.
*/
public void resultChanged(LookupEvent ev) {
collectFires(null);
}
protected void collectFires(Collection evAndListeners) {
// clear cached instances
Collection oldItems;
Collection oldInstances;
synchronized (proxy()) {
if (getCache() == null) {
// nobody queried the result yet
return;
}
oldInstances = getCache()[0];
oldItems = getCache()[2];
if (listeners == null || listeners.getListenerCount() == 0) {
// clear the cache
setCache(new Collection[3]);
return;
}
// ignore events if they arrive as a result of call to allItems
// or allInstances, bellow...
setCache(null);
}
boolean modified = true;
if (oldItems != null) {
Collection newItems = allItems();
if (oldItems.equals(newItems)) {
modified = false;
}
} else {
if (oldInstances != null) {
Collection newInstances = allInstances();
if (oldInstances.equals(newInstances)) {
modified = false;
}
} else {
synchronized (proxy()) {
if (getCache() == null) {
// we have to initialize the cache
// to show that the result has been initialized
setCache(new Collection[3]);
}
}
}
}
if (modified) {
LookupEvent ev = new LookupEvent(this);
AbstractLookup.notifyListeners(listeners.getListenerList(), ev, evAndListeners);
}
}
/** Implementation of my before lookup.
* @return results to work on.
*/
private Lookup.Result[] myBeforeLookup() {
Template template = weakL.result.template;
proxy().beforeLookup(template);
Lookup.Result[] 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() == weakL.result.template.getType()) {
myBeforeLookup();
}
}
private Collection[] getCache() {
return cache;
}
private void setCache(Collection[] cache) {
assert Thread.holdsLock(proxy());
this.cache = cache;
}
}
private static final class WeakRef extends WeakReference implements Runnable {
final WeakResult result;
final ProxyLookup proxy;
final Template template;
public WeakRef(R r, WeakResult result, ProxyLookup proxy, Template template) {
super(r);
this.result = result;
this.template = template;
this.proxy = proxy;
}
public void run() {
result.removeListeners();
proxy.unregisterTemplate(template);
}
}
private static final class WeakResult extends WaitableResult implements LookupListener, Runnable {
/** all results */
private Lookup.Result[] results;
private final WeakRef result;
public WeakResult(ProxyLookup proxy, R r, Template t) {
this.result = new WeakRef(r, this, proxy, t);
}
final void removeListeners() {
Lookup.Result[] arr = this.getResults();
if (arr == null) {
return;
}
for(int i = 0; i < arr.length; i++) {
arr[i].removeLookupListener(this);
}
}
protected void beforeLookup(Lookup.Template t) {
R r = result.get();
if (r != null) {
r.beforeLookup(t);
} else {
removeListeners();
}
}
protected void collectFires(Collection evAndListeners) {
R> r = result.get();
if (r != null) {
r.collectFires(evAndListeners);
} else {
removeListeners();
}
}
public void addLookupListener(LookupListener l) {
assert false;
}
public void removeLookupListener(LookupListener l) {
assert false;
}
public Collection allInstances() {
assert false;
return null;
}
public void resultChanged(LookupEvent ev) {
R r = result.get();
if (r != null) {
r.resultChanged(ev);
} else {
removeListeners();
}
}
@Override
public Collection extends Item> allItems() {
assert false;
return null;
}
@Override
public Set> allClasses() {
assert false;
return null;
}
public void run() {
removeListeners();
}
private Lookup.Result[] getResults() {
return results;
}
private void setResults(Lookup.Result[] results) {
this.results = results;
}
} // 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> results) {
if (results.size() == 0 && lkp == EMPTY_ARR) {
return EMPTY;
}
if (results.size() == 1) {
Entry> e = results.entrySet().iterator().next();
return new SingleInternalData(lkp, e.getKey(), e.getValue());
}
return new RealInternalData(lkp, results);
}
protected abstract boolean isEmpty();
protected abstract Map> getResults();
protected abstract Object getRawLookups();
final Collection> references() {
return getResults().values();
}
final ImmutableInternalData removeTemplate(ProxyLookup proxy, Template template) {
if (getResults().containsKey(template)) {
HashMap> c = new HashMap>(getResults());
Reference ref = c.remove(template);
if (ref != null && ref.get() != null) {
// seems like there is a reference to a result for this template
// thta is still alive
return this;
}
return create(getRawLookups(), c);
} else {
return this;
}
}
R findResult(ProxyLookup proxy, ImmutableInternalData[] newData, Template template) {
assert Thread.holdsLock(proxy);
Map> map = getResults();
Reference ref = map.get(template);
R r = (ref == null) ? null : ref.get();
if (r != null) {
newData[0] = this;
return convertResult(r);
}
HashMap> res = new HashMap>(map);
R newR = new R(proxy, template);
res.put(template, new java.lang.ref.SoftReference(newR));
newR.data = newData[0] = create(getRawLookups(), res);
return newR;
}
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++) {
if (lookups[i] != previous[i]) {
break;
}
same++;
}
if (same == previous.length) {
return this;
}
}
}
if (lookups.length == 1) {
l = lookups[0];
assert l != null : "Cannot assign null delegate";
} else {
if (lookups.length == 0) {
l = EMPTY_ARR;
} else {
l = lookups.clone();
}
}
if (isEmpty() && l == EMPTY_ARR) {
return this;
}
return create(l, getResults());
}
final Lookup[] getLookups(boolean clone) {
Object l = this.getRawLookups();
if (l instanceof Lookup) {
return new Lookup[] { (Lookup)l };
} else {
Lookup[] arr = (Lookup[])l;
if (clone) {
arr = arr.clone();
}
return arr;
}
}
final List getLookupsList() {
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 result;
public SingleInternalData(Object lookups, Template> template, Reference result) {
this.lookups = lookups;
this.template = template;
this.result = result;
}
protected final boolean isEmpty() {
return false;
}
protected Map> getResults() {
return Collections.singletonMap(template, result);
}
protected Object getRawLookups() {
return lookups;
}
}
private static final class RealInternalData extends ImmutableInternalData {
/** lookups to delegate to (either Lookup or array of Lookups) */
private final Object lookups;
/** map of templates to currently active results */
private final Map> results;
public RealInternalData(Object lookups, Map> results) {
this.results = results;
this.lookups = lookups;
}
protected final boolean isEmpty() {
return false;
}
protected Map> getResults() {
boolean strict = false;
assert strict = true;
return strict ? Collections.unmodifiableMap(results) : results;
}
protected Object getRawLookups() {
return lookups;
}
}
private static final class EmptyInternalData extends ImmutableInternalData {
EmptyInternalData() {
}
protected final boolean isEmpty() {
return true;
}
protected Map> getResults() {
return Collections.emptyMap();
}
@Override
protected Object getRawLookups() {
return EMPTY_ARR;
}
} // end of EmptyInternalData
}