blob: 29140e6b4013e1cdba50a72e6edd87ab339967b4 [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 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:
* <P>
* 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.
* <P>
* 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.
* <P>
* 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<ArrayList<Class>> {
private static final long serialVersionUID = 1L;
/** the root item (represents Object) */
private transient Node object;
/** Map of queried interfaces.
* <p>Type: <code>Map&lt;Class, (Collection&lt;AbstractLookup.Pair&gt; | AbstractLookup.Pair)&gt;</code>
*/
private transient Map<Class,Object> interfaces;
/** Map (Class, ReferenceToResult) of all listeners that are waiting in
* changes in class Class
*/
private transient Map<Class,ReferenceToResult> 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<Class,Object>();
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<Class> 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<Class> 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<Class> 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 <T> Enumeration<Pair<T>> lookup(Class<T> 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<Pair> 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<Class,Node> nodes = new HashMap<Class,Node>(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<Pair> 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<Node> reparent = null;
if (n.children == null) {
n.children = new ArrayList<Node>();
} 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<Node>();
}
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<Pair> 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 <code>true<code> 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<Class> notify) {
boolean retained = false;
if ((node.items != null) && (retain != null)) {
Iterator<Pair> 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<Pair> nodeToEnum(Node n) {
if (n.children == null) {
// create a simple enumeration because we do not have children
Enumeration<Pair> 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<Class> affected) {
if (interfaces == null) {
return true;
}
Iterator<Map.Entry<Class,Object>> it = interfaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<Class,Object> entry = it.next();
Class<?> iface = entry.getKey();
if (item.instanceOf(iface)) {
Object value = entry.getValue();
if (value instanceof Collection) {
Collection<Object> set = (Collection<Object>) value;
if (!set.add(item)) {
// item is already there, probably (if everything is correct) is registered in
// all other ifaces too, so stop additional testing
return false;
}
} else {
// there is just one pair right now
if (value.equals(item)) {
// item is there => stop processing (same as above)
return false;
}
// otherwise replace the single item with ArrayList
ArrayList<Object> ll = new ArrayList<Object>(3);
ll.add(value);
ll.add(item);
entry.setValue(ll);
}
affected.add(iface);
}
}
return true;
}
/** Removes interface.
* @param item item to register
* @param affected list of classes that were affected
*/
@SuppressWarnings("unchecked")
private void removeInterface(AbstractLookup.Pair item, Collection affected) {
if (interfaces == null) {
return;
}
Iterator it = interfaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue();
if (value instanceof Collection) {
Collection set = (Collection) value;
if (set.remove(item)) {
if (set.size() == 1) {
// if there is just one item remaining change to single item mode
entry.setValue(set.iterator().next());
}
// adds the Class the item was register to into affected
affected.add(entry.getKey());
}
} else {
// single item value
if (value.equals(item)) {
// Emptied -> remove.
it.remove();
affected.add(entry.getKey());
}
}
}
}
/** Retains some items.
* @param retainItems items to retain and their mapping to index numbers
* (AbstractLookup.Pair -> AbstractLookup.Info)
* @param affected list of classes that were affected
*/
@SuppressWarnings("unchecked")
private void retainAllInterface(Map retainItems, Collection affected) {
if (interfaces == null) {
return;
}
Iterator it = interfaces.entrySet().iterator();
while (it.hasNext()) {
Map.Entry entry = (Map.Entry) it.next();
Object value = entry.getValue();
HashMap<?,?> retain = new HashMap(retainItems);
Iterator elems;
boolean multi = value instanceof Collection;
if (multi) {
// collection mode
elems = ((Collection) value).iterator();
} else {
// single item mode
elems = Collections.singleton(value).iterator();
}
boolean changed = false;
boolean reordered = false;
while (elems.hasNext()) {
AbstractLookup.Pair p = (AbstractLookup.Pair) elems.next();
AbstractLookup.Info n = (AbstractLookup.Info) retain.remove(p);
if (n == null) {
if (multi) {
// remove it
elems.remove();
}
changed = true;
} else {
if (p.getIndex() != n.index) {
// improve the index
p.setIndex(null, n.index);
// affected.addAll ((ArrayList)n.transaction);
reordered = true;
}
}
}
if (reordered && value instanceof List) {
// if reordered, than update the order in the collection
List l = (List) value;
Collections.sort(l, ALPairComparator.DEFAULT);
}
if (changed) {
if (multi) {
Collection c = (Collection) value;
if (c.size() == 1) {
// back to single item mode
entry.setValue(c.iterator().next());
}
} else {
// remove in single mode => remove completely
it.remove();
}
// adds the Class the item was register to into affected
affected.add(entry.getKey());
}
}
}
/** Searches for a clazz between interfaces.
* @param clazz class to search for
* @return enumeration of Items
*/
@SuppressWarnings("unchecked")
private Enumeration<Pair> searchInterface(final Class<?> clazz) {
if (interfaces == null) {
// first call for interface, only initialize
interfaces = new WeakHashMap();
}
Object obj = interfaces.get(clazz);
if (obj == null) {
// set of items
AbstractLookup.Pair one = null;
ArrayList items = null;
Enumeration en = lookup(Object.class);
while (en.hasMoreElements()) {
AbstractLookup.Pair it = (AbstractLookup.Pair) en.nextElement();
if (it.instanceOf(clazz)) {
// ok, this item implements given clazz
if (one == null) {
one = it;
} else {
if (items == null) {
items = new ArrayList(3);
items.add(one);
}
items.add(it);
}
}
}
if ((items == null) && (one != null)) {
// single item mode
interfaces.put(clazz, one);
return singletonEn(one);
} else {
if (items == null) {
items = new ArrayList(2);
}
interfaces.put(clazz, items);
return Collections.enumeration(items);
}
} else {
if (obj instanceof Collection) {
return Collections.enumeration((Collection) obj);
} else {
// single item mode
return singletonEn((Pair)obj);
}
}
}
/** Extracts a node from an iterator, returning null if no next element found
*/
private static Node extractNode(Iterator it) {
while (it.hasNext()) {
Node n = (Node) it.next();
if (n.get() == null) {
it.remove();
} else {
return n;
}
}
return null;
}
/** Prints debug info about the node.
* @param n node to print
* @param sp spaces to add
* @param out where
* @param instances print also instances
*/
private static void printNode(Node n, String sp, java.io.PrintStream out, boolean instances) {
int i;
Iterator it;
Class type = n.getType();
out.print(sp);
out.println("Node for: " + type + "\t" + ((type == null) ? null : type.getClassLoader())); // NOI18N
if (n.items != null) {
i = 0;
it = new ArrayList<Pair>(n.items).iterator();
while (it.hasNext()) {
AbstractLookup.Pair p = (AbstractLookup.Pair) it.next();
out.print(sp);
out.print(" item (" + i++ + "): ");
out.print(p); // NOI18N
out.print(" id: " + Integer.toHexString(System.identityHashCode(p))); // NOI18N
out.print(" index: "); // NOI18N
out.print(p.getIndex());
if (instances) {
out.print(" I: " + p.getInstance());
}
out.println();
}
}
if (n.children != null) {
i = 0;
it = n.children.iterator();
while (it.hasNext()) {
Node ch = (Node) it.next();
printNode(ch, sp + " ", out, instances); // NOI18N
}
}
}
@Override
public <T> Lookup.Result<T> findResult(Lookup.Template<T> t) {
if (reg == null) {
return null;
}
ReferenceToResult prev = reg.get(t.getType());
if (prev != null) {
AbstractLookup.ReferenceIterator it = new AbstractLookup.ReferenceIterator(prev);
while (it.next()) {
if (it.current().template.equals(t)) {
return (Lookup.Result<T>) it.current().getResult();
}
}
}
return null;
}
public ReferenceToResult registerReferenceToResult(ReferenceToResult<?> newRef) {
if (reg == null) {
reg = new HashMap<Class,ReferenceToResult>();
}
Class<? extends Object> clazz = newRef.template.getType();
// initialize the data structures if not yet
lookup(clazz);
// newRef will be the new head of the list
return reg.put(clazz, newRef);
}
public ReferenceToResult cleanUpResult(Lookup.Template templ) {
collectListeners(null, templ.getType());
return (reg == null) ? null : reg.get(templ.getType());
}
public ArrayList<Class> beginTransaction(int ensure) {
return new ArrayList<Class>();
}
public void endTransaction(ArrayList<Class> list, Set<AbstractLookup.R> allAffectedResults) {
if (list.size() == 1) {
// probably the most common case
collectListeners(allAffectedResults, list.get(0));
} else {
Iterator it = list.iterator();
while (it.hasNext()) {
collectListeners(allAffectedResults, (Class) it.next());
}
}
}
/** Notifies all listeners that are interested in changes in this class.
* Should be called from synchronized places.
* @param allAffectedResults adds Results into this set
* @param c the class that has changed
*/
private void collectListeners(Set<AbstractLookup.R> allAffectedResults, Class c) {
if (reg == null) {
return;
}
while (c != null) {
ReferenceToResult first = reg.get(c);
ReferenceIterator it = new ReferenceIterator(first);
while (it.next()) {
AbstractLookup.R result = it.current().getResult();
if (allAffectedResults != null) {
// add result
allAffectedResults.add(result);
}
}
if (first != it.first()) {
if (it.first() == null) {
// we do not need have more results on this object
reg.remove(c);
} else {
// move the head of the list
reg.put(c, it.first());
}
}
c = c.getSuperclass();
}
if (reg.isEmpty()) {
// clean up the list of all results if we do not need them anymore
reg = null;
}
}
/** Node in the tree.
*/
static final class Node extends WeakReference<Class> implements Serializable {
static final long serialVersionUID = 3L;
/** children nodes */
public ArrayList<Node> children;
/** list of items assigned to this node (suspect to be subclasses) */
public Collection<Pair> items;
/** Constructor.
*/
public Node(Class clazz) {
super(clazz);
}
/** Returns true if the object was deserialized also clears the serialized flag.
* @return true if so.
*/
public boolean deserialized() {
if ((items == null) || items instanceof LinkedHashSet) {
return false;
}
if (items.isEmpty()) {
items = null;
} else {
items = new LinkedHashSet<Pair>(items);
}
return true;
}
/** Marks this item as being deserialized.
*/
public void markDeserialized() {
if (items == null || items == Collections.EMPTY_LIST) {
items = Collections.emptyList();
} else {
items = Collections.synchronizedCollection(items);
}
}
/** Getter for the type associated with this node.
*/
public Class<?> getType() {
Class<?> c = get();
// if garbage collected, then return a garbage
return (c == null) ? Void.TYPE : c;
}
/** Checks whether a node can represent an class.
*/
public boolean accepts(Class<?> clazz) {
if (getType() == Object.class) {
return true;
}
return getType().isAssignableFrom(clazz);
}
/** Checks whether item is instance of this node.
*/
public boolean accepts(AbstractLookup.Pair<?> item) {
if (getType() == Object.class) {
// Object.class
return true;
}
return item.instanceOf(getType());
}
/** Assings an item to this node.
* @param item the item
* @return true if item has been added as new
*/
public boolean assignItem(InheritanceTree tree, AbstractLookup.Pair<?> item) {
if ((items == null) || (items == Collections.EMPTY_LIST)) {
items = new LinkedHashSet<Pair>();
items.add(item);
return true;
}
if (items.contains(item)) {
Iterator<Pair> it = items.iterator();
Pair old;
for (;;) {
old = it.next();
if (item.equals(old)) {
break;
}
}
if (old != item) {
// replace the items there
item.setIndex(tree, old.getIndex());
}
it.remove();
items.add(item);
return false;
}
items.add(item);
return true;
}
private Object writeReplace() {
return new R(this);
}
@Override
public String toString() {
return "Node for " + get();
}
}
// End of class Node.
private static final class R implements Serializable {
static final long serialVersionUID = 1L;
private static ClassLoader l;
private String clazzName;
private transient Class<?> clazz;
private ArrayList<Node> children;
private Collection<Pair> items;
public R(Node n) {
this.clazzName = n.getType().getName();
this.children = n.children;
if (n.items instanceof LinkedHashSet || (n.items == null)) {
this.items = n.items;
} else {
this.items = new LinkedHashSet<Pair>(n.items);
}
}
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
ois.defaultReadObject();
if (l == null) {
l = Lookup.getDefault().lookup(ClassLoader.class);
}
clazz = Class.forName(clazzName, false, l);
}
private Object readResolve() throws ObjectStreamException {
Node n = new Node(clazz);
n.children = children;
n.items = items;
n.markDeserialized();
return n;
}
}
// end of R
static Enumeration<Object> arrayEn(Object[] object) {
return Collections.enumeration(Arrays.asList(object));
}
static <T> Enumeration<T> singletonEn(T object) {
return Collections.enumeration(Collections.singleton(object));
}
static <T> Enumeration<T> emptyEn() {
return Collections.enumeration(Collections.<T>emptyList());
}
/** Just a marker class to be able to do instanceof and find out
* that this enumeration is not sorted
*/
private static final class NeedsSortEnum extends LinkedList<Node>
implements Enumeration<Pair> {
private Enumeration<Pair> en;
public NeedsSortEnum(Node n) {
add(n);
}
private boolean ensureNext() {
for (;;) {
if (en != null && en.hasMoreElements()) {
return true;
}
if (isEmpty()) {
return false;
}
Node n2 = poll();
if (n2.children != null) {
addAll(n2.children);
}
if (n2.items != null && !n2.items.isEmpty()) {
en = Collections.enumeration(n2.items);
}
}
}
public boolean hasMoreElements() {
return ensureNext();
}
public Pair nextElement() {
if (!ensureNext()) {
throw new NoSuchElementException();
}
return en.nextElement();
}
}
// end of NeedsSortEnum
}