| /* |
| * 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<Class, (Collection<AbstractLookup.Pair> | AbstractLookup.Pair)></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 |
| } |