/* * 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.io.*; import java.lang.ref.WeakReference; import java.util.*; import org.openide.util.Lookup; import org.openide.util.enums.*; /** 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 Comparator, Serializable { private static final long serialVersionUID = 1L; /** the root item (represents Object) */ private transient Node object; /** Map of queried interfaces. * <p>Type: <code>Map<Class, (Collection<AbstractLookup.Pair> | AbstractLookup.Pair)></code> */ private transient Map interfaces; /** Constructor */ public InheritanceTree () { object = new Node (java.lang.Object.class); } private void writeObject (ObjectOutputStream oos) throws IOException { oos.writeObject(object); 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 = (ClassLoader)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, Collection affected) { Node node = registerClass (object, item); affected.add (node.getType ()); if (node.assignItem (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, Collection 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, Collection 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 */ public Enumeration lookup (Class clazz) { if (clazz.isInterface ()) { return searchInterface (clazz); } else { return 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 SequenceEnumeration; } /** 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); // 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 static Node classToNode (Node n, 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 (;;) { Node ch = extractNode (it); if (ch == null) break; Node found = classToNode (ch, clazz); if (found != null) { // class found in one of subnodes return found; } } } // have to create new subnode and possibly reparent one of my own 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 (); } } } Node 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 (); while (it.hasNext ()) { AbstractLookup.Pair item = (AbstractLookup.Pair)it.next (); if (item.instanceOf (clazz)) { it.remove (); newNode.assignItem (item); } } } // newNode represnts my clazz return newNode; } /** Search for a requested class. * @return enumeration of Pair */ private static Enumeration searchClass (Node n, Class clazz) { n = classToNode (n, clazz); if (n == null) { // not for us return EmptyEnumeration.EMPTY; } 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 static 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 = (AbstractLookup.Pair)it.next (); AbstractLookup.Info n = (AbstractLookup.Info)retain.get (item); if (n == null) { // remove this item, it should not be there it.remove (); retained = true; } else { // change the index if (item.index != n.index) { item.index = n.index; notify.addAll (n.modified); } } } 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 return n.items == null ? EmptyEnumeration.EMPTY : Collections.enumeration (n.items); } // we have found what we need // now we have to just build the enumeration QueueEnumeration en = new QueueEnumeration () { protected void process (Object obj) { Node n2 = (Node)obj; if (n2.children != null) { Object[] nodes = n2.children.toArray (); put (nodes); } } }; // initial node is our current one en.put (n); // convert Node into enumeration of Enumerations of Items AlterEnumeration alt = new AlterEnumeration (en) { protected Object alter (Object obj) { Node n2 = (Node)obj; if (n2.items == null || n2.items.isEmpty ()) { return EmptyEnumeration.EMPTY; } else { return Collections.enumeration (n2.items); } } }; // create enumeration of Items return new SequenceEnumeration (alt); } // // 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 */ private boolean registerInterface (AbstractLookup.Pair item, Collection affected) { if (interfaces == null) { return true; } Iterator it = interfaces.entrySet ().iterator (); while (it.hasNext ()) { Map.Entry entry = (Map.Entry)it.next (); Class iface = (Class)entry.getKey (); if (item.instanceOf (iface)) { Object value = entry.getValue (); if (value instanceof Collection) { Collection set = (Collection)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 ll = new ArrayList (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 */ 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 retain items to retain and their mapping to index numbers * (AbstractLookup.Pair -> AbstractLookup.Info) * @param affected list of classes that were affected */ private void retainAllInterface (Map retain, 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 (); 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.get (p); if (n == null) { if (multi) { // remove it elems.remove (); } changed = true; } else { if (p.index != n.index) { // improve the index p.index = n.index; affected.addAll (n.modified); reordered = true; } } } if (reordered && value instanceof List) { // if reordered, than update the order in the collection List l = (List)value; Collections.sort (l, this); } 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 */ private Enumeration 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 new SingletonEnumeration (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 new SingletonEnumeration (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 (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.index); 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 } } } // // Implementation of comparator for AbstractLookup.Pair // /** Compares two items. */ public int compare(Object obj, Object obj1) { AbstractLookup.Pair i1 = (AbstractLookup.Pair)obj; AbstractLookup.Pair i2 = (AbstractLookup.Pair)obj1; int result = i1.index - i2.index; if (result == 0) { if (i1 != i2) { java.io.ByteArrayOutputStream bs = new java.io.ByteArrayOutputStream (); java.io.PrintStream ps = new java.io.PrintStream (bs); ps.println ( "Please report this exception as issue http://www.netbeans.org/issues/show_bug.cgi?id=13779 " + // NOI18N "Pair1: " + i1 + " pair2: " + i2 + " index1: " + i1.index + " index2: " + i2.index // NOI18N + " item1: " + i1.getInstance () + " item2: " + i2.getInstance () // NOI18N + " id1: " + Integer.toHexString (System.identityHashCode (i1)) // NOI18N + " id2: " + Integer.toHexString (System.identityHashCode (i2)) // NOI18N ); print (ps, false); ps.close (); throw new IllegalStateException (bs.toString ()); } return 0; } return result; } /** Node in the tree. */ static final class Node extends WeakReference implements Serializable { static final long serialVersionUID = 3L; /** children nodes */ public ArrayList children; // List<Node> /** list of items assigned to this node (suspect to be subclasses) */ public ArrayList items; // List<AbstractLookup.Pair> /** Constructor. */ public Node (Class clazz) { super (clazz); } /** Getter for the type associated with this node. */ public Class getType () { Class c = (Class)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 (AbstractLookup.Pair item) { if (items == null) { items = new ArrayList (); items.add (item); return true; } if (items.contains(item)) { int i = items.indexOf(item); AbstractLookup.Pair old = (AbstractLookup.Pair)items.get(i); item.index = old.index; items.remove(old); items.add(item); return false; } items.add (item); return true; } private Object writeReplace () { return new R (this); } } // 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 children; private ArrayList items; public R (Node n) { this.clazzName = n.getType ().getName(); this.children = n.children; this.items = n.items; } private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { ois.defaultReadObject(); if (l == null) { l = (ClassLoader)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; return n; } } // end of R }