blob: 1044eba7bc91ec147dc50607f881538bcd328daa [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.openide.util.lookup;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import org.openide.util.Lookup;
import org.openide.util.LookupListener;
import java.util.*;
import org.openide.util.LookupEvent;
/** Allows exclusion of certain instances from lookup.
*
* @author Jaroslav Tulach
*/
final class ExcludingLookup extends org.openide.util.Lookup {
/** the other lookup that we delegate to */
private final Lookup delegate;
/** classes to exclude (Class[]) or just one class (Class) */
private final Object classes;
/** results */
private WeakResult<?> results;
/**
* Creates new Result object with supplied instances parameter.
* @param instances to be used to return from the lookup
*/
ExcludingLookup(Lookup delegate, Class[] classes) {
this.delegate = delegate;
for (Class c : classes) {
if (c == null) {
throw new NullPointerException();
}
}
if (classes.length == 1) {
this.classes = classes[0];
} else {
this.classes = classes;
}
}
@Override
public String toString() {
return "ExcludingLookup: " + delegate + " excludes: " + Arrays.asList(classes()); // NOI18N
}
public <T> Result<T> lookup(Template<T> template) {
if (template == null) {
throw new NullPointerException();
}
if (areSubclassesOfThisClassAlwaysExcluded(template.getType())) {
// empty result
return Lookup.EMPTY.lookup(template);
}
R<T> ret = null;
for (;;) { // at most twice
synchronized (this) {
WeakResult<?> r = results;
WeakResult<?> prev = null;
while (r != null) {
R<?> res = r.result.get();
if (res == null) {
if (prev != null) {
prev.next = r.next;
} else {
results = r.next;
}
} else {
if (template.equals(res.from)) {
@SuppressWarnings("unchecked")
Result<T> old = (Result<T>) res;
return old;
}
}
prev = r;
r = r.next;
}
if (ret != null) {
ret.weak.next = results;
results = ret.weak;
return ret;
}
}
ret = new R<T>(template, delegate.lookup(template));
}
}
public <T> T lookup(Class<T> clazz) {
if (areSubclassesOfThisClassAlwaysExcluded(clazz)) {
return null;
}
T res = delegate.lookup(clazz);
if (isObjectAccessible(clazz, res, 0)) {
return res;
} else {
return null;
}
}
@Override
public <T> Lookup.Item<T> lookupItem(Lookup.Template<T> template) {
if (areSubclassesOfThisClassAlwaysExcluded(template.getType())) {
return null;
}
Lookup.Item<T> retValue = delegate.lookupItem(template);
if (isObjectAccessible(template.getType(), retValue, 2)) {
return retValue;
} else {
return null;
}
}
/** @return true if the instance of class c shall never be returned from this lookup
*/
private boolean areSubclassesOfThisClassAlwaysExcluded(Class<?> c) {
Class<?>[] arr = classes();
for (int i = 0; i < arr.length; i++) {
if (arr[i].isAssignableFrom(c)) {
return true;
}
}
return false;
}
/** Returns the array of classes this lookup filters.
*/
final Class<?>[] classes() {
if (classes instanceof Class[]) {
return (Class[]) classes;
} else {
return new Class[] { (Class) classes };
}
}
/** Does a check whether two classes are accessible (in the super/sub class)
* releation ship without walking thru any of the classes mentioned in the
* barrier.
*/
private static boolean isAccessible(Class<?>[] barriers, Class<?> from, Class<?> to) {
if ((to == null) || !from.isAssignableFrom(to)) {
// no way to reach each other by walking up
return false;
}
for (int i = 0; i < barriers.length; i++) {
if (to == barriers[i]) {
return false;
}
}
if (from == to) {
return true;
}
//
// depth first search
//
if (isAccessible(barriers, from, to.getSuperclass())) {
return true;
}
Class[] interfaces = to.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
if (isAccessible(barriers, from, interfaces[i])) {
return true;
}
}
return false;
}
/** based on type decides whether the class accepts or not anObject
* @param from the base type of the query
* @param to depending on value of type either Object, Class or Item
* @param type 0,1,2 for Object, Class or Item
* @return true if we can access the to from from by walking around the bariers
*/
private final boolean isObjectAccessible(Class from, Object to, int type) {
if (to == null) {
return false;
}
return isObjectAccessible(classes(), from, to, type);
}
/** based on type decides whether the class accepts or not anObject
* @param barriers classes to avoid when testing reachability
* @param from the base type of the query
* @param to depending on value of type either Object, Class or Item
* @param type 0,1,2 for Object, Class or Item
* @return true if we can access the to from from by walking around the bariers
*/
static final boolean isObjectAccessible(Class[] barriers, Class from, Object to, int type) {
if (to == null) {
return false;
}
switch (type) {
case 0:
return isAccessible(barriers, from, to.getClass());
case 1:
return isAccessible(barriers, from, (Class) to);
case 2: {
Item item = (Item) to;
return isAccessible(barriers, from, item.getType());
}
default:
throw new IllegalStateException("Type: " + type);
}
}
/** Filters collection accroding to set of given filters.
*/
final <E, T extends Collection<E>> T filter(
Class<?>[] arr, Class<?> from, T c, int type, T prototype
) {
T ret = null;
// optimistic strategy expecting we will not need to filter
TWICE:
for (;;) {
Iterator<E> it = c.iterator();
BIG:
while (it.hasNext()) {
E res = it.next();
if (!isObjectAccessible(arr, from, res, type)) {
if (ret == null) {
// we need to restart the scanning again
// as there is an active filter
ret = prototype;
continue TWICE;
}
continue BIG;
}
if (ret != null) {
// if we are running the second round from TWICE
ret.add(res);
}
}
// ok, processed
break TWICE;
}
return (ret != null) ? ret : c;
}
/** Delegating result that filters unwanted items and instances.
*/
private final class R<T> extends WaitableResult<T> implements LookupListener {
private Result<T> result;
private WeakResult<T> weak;
private Object listeners;
private Template from;
R(Template from, Result<T> delegate) {
this.from = from;
this.result = delegate;
this.weak = new WeakResult<T>(this, delegate);
}
protected void beforeLookup(Template t) {
if (result instanceof WaitableResult) {
((WaitableResult) result).beforeLookup(t);
}
}
public void addLookupListener(LookupListener l) {
boolean add;
synchronized (this) {
listeners = AbstractLookup.modifyListenerList(true, l, listeners);
add = listeners != null;
}
if (add) {
result.addLookupListener(weak);
}
}
public void removeLookupListener(LookupListener l) {
boolean remove;
synchronized (this) {
listeners = AbstractLookup.modifyListenerList(false, l, listeners);
remove = listeners == null;
}
if (remove) {
result.removeLookupListener(weak);
}
}
@Override
public Collection<? extends T> allInstances() {
return openCol(result.allInstances(), 0);
}
@Override
protected Collection<? extends T> allInstances(boolean ignore) {
return allInstances();
}
private <S> Collection<S> openCol(Collection<S> c, int type) {
return filter(classes(), from.getType(), c, type, new ArrayList<S>(c.size()));
}
@Override
public Set<Class<? extends T>> allClasses() {
return filter(classes(), from.getType(), result.allClasses(), 1, new HashSet<Class<? extends T>>());
}
@Override
public Collection<? extends Item<T>> allItems() {
return openCol(result.allItems(), 2);
}
@Override
public Collection<? extends Item<T>> allItems(boolean ignore) {
return allItems();
}
public void resultChanged(org.openide.util.LookupEvent ev) {
if (ev.getSource() == result) {
collectFires(null);
}
}
protected void collectFires(Collection<Object> evAndListeners) {
LookupListener[] arr;
synchronized (this) {
if (listeners == null) {
return;
}
if (listeners instanceof LookupListener) {
arr = new LookupListener[] { (LookupListener) listeners };
} else {
ArrayList<?> l = (ArrayList<?>) listeners;
arr = l.toArray(new LookupListener[l.size()]);
}
}
final LookupListener[] ll = arr;
final org.openide.util.LookupEvent newev = new org.openide.util.LookupEvent(this);
AbstractLookup.notifyListeners(ll, newev, evAndListeners);
}
} // end of R
private final class WeakResult<T> extends WaitableResult<T> implements LookupListener {
private final Lookup.Result source;
private final Reference<R<T>> result;
/** @GuardedBy(ExcludingLookup.this) */
private WeakResult<?> next;
public WeakResult(R<T> r, Lookup.Result<T> s) {
this.result = new WeakReference<R<T>>(r);
this.source = s;
}
protected void beforeLookup(Lookup.Template t) {
R r = (R)result.get();
if (r != null) {
r.beforeLookup(t);
} else {
source.removeLookupListener(this);
}
}
protected void collectFires(Collection<Object> evAndListeners) {
R<T> r = result.get();
if (r != null) {
r.collectFires(evAndListeners);
} else {
source.removeLookupListener(this);
}
}
public void addLookupListener(LookupListener l) {
assert false;
}
public void removeLookupListener(LookupListener l) {
assert false;
}
public Collection<T> allInstances() {
assert false;
return null;
}
protected Collection<T> allInstances(boolean ignore) {
assert false;
return null;
}
public void resultChanged(LookupEvent ev) {
R r = (R)result.get();
if (r != null) {
r.resultChanged(ev);
} else {
source.removeLookupListener(this);
}
}
@Override
public Collection<? extends Item<T>> allItems() {
assert false;
return null;
}
@Override
protected Collection<? extends Item<T>> allItems(boolean callBeforeLookup) {
return allItems();
}
@Override
public Set<Class<? extends T>> allClasses() {
assert false;
return null;
}
} // end of WeakResult
}