org.openide.util.lookup.InheritanceTree Maven / Gradle / Ivy
/*
* 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 org.openide.util.Lookup;
import org.openide.util.lookup.AbstractLookup.Pair;
import org.openide.util.lookup.AbstractLookup.ReferenceIterator;
import org.openide.util.lookup.AbstractLookup.ReferenceToResult;
import java.io.*;
import java.lang.ref.WeakReference;
import java.util.*;
/** A tree to represent classes with inheritance. Description of the
* data structure by Petr Nejedly:
*
* So pretend I'm Lookup implementation. I've got a bunch of Items (e.g.
* setPairs() method),
* didn't do anything on them yet (no startup penalty) so I know nothing
* about them.
* Then I'll be asked for all instances implementing given interface or a
* class. I surely need
* to check all the Items now, as I don't know anything abou them. I surely
* don't want to call
* Item.getClass() as it will dismiss the whole effort. So all I have is
* Item.instanceOf()
* and I'll call it on every Item. I'll cache results, so the next time
* you'll ask me for
* the same interface/class, I'll answer immediatelly. But what if you ask
* me for another
* interface/class? I'll have to scan all Items for it again, unless I can
* be sure some
* of them can't implement it. The only source of this knowledge are the
* previous questions
* and my rulings on them. Here the algorithm have to be split into two
* paths. If you
* previously asked me for interfaces only, I'll have no hint for
* subsequent queries,
* but if you asked me for a class in history, and then for another class
* and these classes
* are not in inheritance relation (I can check hierarchy of lookup
* arguments, because
* they are already resolved/loaded) I can tell that those returned in
* previous query can't
* implement the newly asked class (they are in different hierarchy branch)
* and I need to
* ask less Items.
*
* So if we use mostly classes for asking for services (and it is a trend
* to use
* abstract classes for this purpose in IDE anyway), this could be usable.
*
* The data structure for separating the Items based on previous queries is
* simple
* tree, with every node tagged with one class. The tree's root is,
* naturally,
* java.lang.Object, is marked invited and initially contains all the
* Items.
* For every class query, the missing part of class hierarchy tree is
* created,
* the node of the class looked up is marked as invited and all Items from
* nearest
* invited parent (sperclass) are dragged to this node. The result are then
* all
* Items from this node and all the nodes deeper in hierarchy. Because it
* may
* be too complicated to walk through the children nodes, the results could
* be
* cached in the map.
* For interface lookup, there is a little hint in reality (interfaces
* and superinterfaces), but it would be harder to exploit it, so we could
* fall-back
* to walking through all the Items and cache results.
*
*
* @author Jaroslav Tulach
*/
final class InheritanceTree extends Object
implements Serializable, AbstractLookup.Storage> {
private static final long serialVersionUID = 1L;
/** the root item (represents Object) */
private transient Node object;
/** Map of queried interfaces.
* Type: Map<Class, (Collection<AbstractLookup.Pair> | AbstractLookup.Pair)>
*/
private transient Map interfaces;
/** Map (Class, ReferenceToResult) of all listeners that are waiting in
* changes in class Class
*/
private transient Map reg;
/** Constructor
*/
public InheritanceTree() {
object = new Node(java.lang.Object.class);
}
private void writeObject(ObjectOutputStream oos) throws IOException {
oos.writeObject(object);
if (interfaces != null) {
Iterator it = interfaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry e = (Map.Entry) it.next();
Class c = (Class) e.getKey();
oos.writeObject(c.getName());
Object o = e.getValue();
if (!(o instanceof Collection) && !(o instanceof AbstractLookup.Pair)) {
throw new ClassCastException(String.valueOf(o));
}
oos.writeObject(o);
}
}
oos.writeObject(null);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
object = (Node) ois.readObject();
interfaces = new WeakHashMap();
String clazz;
ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class);
while ((clazz = (String) ois.readObject()) != null) {
Object o = ois.readObject();
if (!(o instanceof Collection) && !(o instanceof AbstractLookup.Pair)) {
throw new ClassCastException(String.valueOf(o));
}
Class c = Class.forName(clazz, false, l);
interfaces.put(c, o);
}
}
/** Adds an item into the tree.
* @param item to add
* @return true if the Item has been added for the first time or false if some other
* item equal to this one already existed in the lookup
*/
public boolean add(AbstractLookup.Pair> item, ArrayList affected) {
Node node = registerClass(object, item);
affected.add(node.getType());
if (node.assignItem(this, item)) {
// this is the first item added to n.items
// ok, we have to test interfaces too
} else {
// equal item is already there => stop processing
return false;
}
boolean registeredAsInterface = registerInterface(item, affected);
return registeredAsInterface;
}
/** Removes an item.
*/
public void remove(AbstractLookup.Pair item, ArrayList affected) {
Node n = removeClass(object, item);
if (n != null) {
affected.add(n.getType());
}
removeInterface(item, affected);
}
/** Removes all items that are not present in the provided collection.
* @param retain collection of Pairs to keep them in
* @param notify set of Classes that has possibly changed
*/
public void retainAll(Map retain, ArrayList notify) {
retainAllInterface(retain, notify);
retainAllClasses(object, retain, notify);
}
/** Queries for instances of given class.
* @param clazz the class to check
* @return enumeration of Item
* @see #unsorted
*/
@SuppressWarnings("unchecked")
public Enumeration> lookup(Class clazz) {
if ((clazz != null) && clazz.isInterface()) {
return (Enumeration)searchInterface(clazz);
} else {
return (Enumeration)searchClass(object, clazz);
}
}
/** A method to check whether the enumeration returned from
* lookup method is sorted or is not
* @param en enumeration to check
* @return true if it is unsorted and needs to be sorted to find
* pair with smallest index
*/
public static boolean unsorted(Enumeration en) {
return en instanceof NeedsSortEnum;
}
/** Prints debug messages.
* @param out stream to output to
* @param instances print also instances of the
*/
public void print(java.io.PrintStream out, boolean instances) {
printNode(object, "", out, instances); // NOI18N
}
//
// methods to work on classes which are not interfaces
//
/** Searches the subtree and register the item where necessary.
* @return the node that should contain the item
*/
private static Node registerClass(Node n, AbstractLookup.Pair item) {
if (!n.accepts(item)) {
return null;
}
if (n.children != null) {
Iterator it = n.children.iterator();
for (;;) {
Node ch = extractNode(it);
if (ch == null) {
break;
}
Node result = registerClass(ch, item);
if (result != null) {
// it is in subclass, in case of classes, it cannot
// be any other class
return result;
}
}
}
// ok, nobody of our subclasses wants the class, I'll take it
return n;
}
/** Removes the item from the tree of objects.
* @return most narrow class that this item was removed from
*/
private static Node removeClass(Node n, AbstractLookup.Pair item) {
if (!n.accepts(item)) {
return null;
}
if ((n.items != null) && n.items.remove(item)) {
// this node really contains the item
return n;
}
if (n.children != null) {
Iterator it = n.children.iterator();
for (;;) {
Node ch = extractNode(it);
if (ch == null) {
break;
}
Node result = removeClass(ch, item);
/* Can't remove completely otherwise the system forgets we are listening to this class
// If the children node was emptied, remove it if possible.
if (((ch.items == null) || ch.items.isEmpty()) && ((ch.children == null) || ch.children.isEmpty())) {
it.remove();
}
*/
if (result != null) {
// it is in subclass, in case of classes, it cannot
// be any other class
return result;
}
}
}
// nobody found
return null;
}
/** Finds a node that represents a class.
* @param n node to search from
* @param clazz the clazz to find
* @return node that represents clazz in the tree or null if the clazz is not
* represented under the node n
*/
private Node classToNode(final Node n, final Class> clazz) {
if (!n.accepts(clazz)) {
// nothing from us
return null;
}
if (n.getType() == clazz) {
// we have found what we need
return n;
}
if (n.children != null) {
// have to proceed to children
Iterator it = n.children.iterator();
for (;;) {
final Node ch = extractNode(it);
if (ch == null) {
break;
}
Node found = classToNode(ch, clazz);
if ((found != null) && ch.deserialized()) {
class VerifyJob implements AbstractLookup.ISE.Job {
private AbstractLookup.Pair>[] pairs;
private boolean[] answers;
public VerifyJob(Collection items) {
if (items != null) {
pairs = items.toArray(new AbstractLookup.Pair[0]);
}
}
public void before() {
// make sure the node is converted into deserialized state
ch.deserialized();
if (pairs != null) {
answers = new boolean[pairs.length];
for (int i = 0; i < pairs.length; i++) {
answers[i] = pairs[i].instanceOf(clazz);
}
}
}
public void inside() {
if (pairs != null) {
for (int i = 0; i < pairs.length; i++) {
if (answers[i]) {
ch.assignItem(InheritanceTree.this, pairs[i]);
n.items.remove(pairs[i]);
}
}
}
if (n.children != null) {
// consolidate all nodes that represent the same class
HashMap nodes = new HashMap(n.children.size() * 3);
Iterator child = n.children.iterator();
while (child.hasNext()) {
Node node = extractNode(child);
if (node == null) {
continue;
}
Node prev = nodes.put(node.getType(), node);
if (prev != null) {
child.remove();
nodes.put(node.getType(), prev);
// mark as being deserialized
prev.markDeserialized();
if (prev.children == null) {
prev.children = node.children;
} else {
if (node.children != null) {
prev.children.addAll(node.children);
}
}
if (node.items != null) {
Iterator items = node.items.iterator();
while (items.hasNext()) {
AbstractLookup.Pair item = (AbstractLookup.Pair) items.next();
prev.assignItem(InheritanceTree.this, item);
}
}
}
}
}
}
}
VerifyJob verify = new VerifyJob(n.items);
try {
verify.before();
} catch (AbstractLookup.ISE ex) {
// mark deserialized again
ch.markDeserialized();
ex.registerJob(verify);
throw ex;
}
verify.inside();
found = classToNode(ch, clazz);
}
if (found != null) {
// class found in one of subnodes
return found;
}
}
}
class TwoJobs implements AbstractLookup.ISE.Job {
private AbstractLookup.Pair[] pairs;
private boolean[] answers;
private Node newNode;
public void before() {
// have to create new subnode and possibly reparent one of my own
// but all changes can be done only if we will not be interrupted from
// outside - e.g. instanceOf methods will not throw exception
// first of all let's compute the answers to method instanceOf
AbstractLookup.Pair[] arr = null;
boolean[] boolArr = null;
if (n.items != null) {
arr = new AbstractLookup.Pair[n.items.size()];
boolArr = new boolean[n.items.size()];
int i = 0;
Iterator it = n.items.iterator();
while (it.hasNext()) {
AbstractLookup.Pair> item = it.next();
arr[i] = item;
boolArr[i] = item.instanceOf(clazz);
i++;
}
}
pairs = arr;
answers = boolArr;
}
public void inside() {
// test if the query has not chagned since
if (pairs != null) {
if (!Arrays.equals(n.items.toArray(), pairs)) {
// ok, let try once more
return;
}
}
internal();
}
public void internal() {
ArrayList reparent = null;
if (n.children == null) {
n.children = new ArrayList();
} else {
// scan thru all my nodes if some of them are not a subclass
// of clazz => then they would need to become child of newNode
Iterator it = n.children.iterator();
for (;;) {
Node r = extractNode(it);
if (r == null) {
break;
}
if (clazz.isAssignableFrom(r.getType())) {
if (reparent == null) {
reparent = new ArrayList();
}
reparent.add(r);
it.remove();
}
}
}
newNode = new Node(clazz);
n.children.add(newNode);
if (reparent != null) {
// reassing reparent node as a child of newNode
newNode.children = reparent;
}
// now take all my items that are instances of that class and
// reasign them
if (n.items != null) {
Iterator it = n.items.iterator();
int i = 0;
while (it.hasNext()) {
AbstractLookup.Pair item = (AbstractLookup.Pair) it.next();
if (answers[i]) { // answers[i] is precomputed value of item.instanceOf (clazz))
it.remove();
newNode.assignItem(InheritanceTree.this, pairs[i]);
}
i++;
}
}
}
}
TwoJobs j = new TwoJobs();
try {
j.before();
} catch (AbstractLookup.ISE ex) {
// ok, it is not possible to call instanceOf now, let's
// schedule it for later
// so register recovery job
ex.registerJob(j);
throw ex;
}
j.internal();
// newNode represents my clazz
return j.newNode;
}
/** Search for a requested class.
* @return enumeration of Pair
*/
private Enumeration searchClass(Node n, Class> clazz) {
if (clazz != null) {
n = classToNode(n, clazz);
}
if (n == null) {
// not for us
return emptyEn();
} else {
return nodeToEnum(n);
}
}
/** Retains all classes. Removes nodes which items and children are emptied, works
* recursivelly from specified root node.
* @param node root node from which to start to process the tree
* @param retain a map from (Item, AbstractLookup.Info) that describes which items to retain
* and witch integer to assign them
* @param notify collection of classes will be changed
* @return true if some items were changed and node items and children are emptied,
* those nodes, excluding root, will be removed from tree */
private boolean retainAllClasses(Node node, Map retain, Collection notify) {
boolean retained = false;
if ((node.items != null) && (retain != null)) {
Iterator it = node.items.iterator();
while (it.hasNext()) {
AbstractLookup.Pair> item = it.next();
AbstractLookup.Info n = (AbstractLookup.Info) retain.remove(item);
if (n == null) {
// remove this item, it should not be there
it.remove();
retained = true;
} else {
// change the index
if (item.getIndex() != n.index) {
item.setIndex(null, n.index);
// notify.addAll ((ArrayList)n.transaction);
}
}
}
if (retained && (notify != null)) {
// type of this node has been changed
notify.add(node.getType());
}
}
if (node.children != null) {
for (Iterator it = node.children.iterator();;) {
Node ch = extractNode(it);
if (ch == null) {
break;
}
boolean result = retainAllClasses(ch, retain, notify);
if (result) {
// The children node was emptied and has no children -> remove it.
it.remove();
}
}
}
return retained && node.items.isEmpty() && ((node.children == null) || node.children.isEmpty());
}
/** A method that creates enumeration of all items under given node.
*
* @param n node to create enumeration for
* @return enumeration of Pairs
*/
private static Enumeration nodeToEnum(Node n) {
if (n.children == null) {
// create a simple enumeration because we do not have children
Enumeration e;
if (n.items == null) {
e = emptyEn();
} else {
e = Collections.enumeration(n.items);
}
return e;
}
// create enumeration of Items
return new NeedsSortEnum(n);
}
//
// Methods to work on interfaces
//
/** Registers an item with interfaces.
* @param item item to register
* @param affected list of classes that were affected
* @return false if similar item has already been registered
*/
@SuppressWarnings("unchecked")
private boolean registerInterface(AbstractLookup.Pair> item, Collection affected) {
if (interfaces == null) {
return true;
}
Iterator> it = interfaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = it.next();
Class> iface = entry.getKey();
if (item.instanceOf(iface)) {
Object value = entry.getValue();
if (value instanceof Collection) {
Collection