/* * 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.IOException; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.ref.*; import java.util.*; import org.openide.util.Lookup; import org.openide.util.LookupListener; import org.openide.util.LookupEvent; import org.openide.util.WeakSet; /** Implementation of the lookup from OpenAPIs that is based on the * introduction of Item. This class should provide the default way * of how to store (Class, Object) pairs in the lookups. It offers * protected methods for subclasses to register the pairs. * <p>Serializable since 3.27. * @author Jaroslav Tulach * @since 1.9 */ public class AbstractLookup extends Lookup implements Serializable { static final long serialVersionUID = 5L; /** lock for initialization of the map */ private Content treeLock; /** the tree that registers all items */ private InheritanceTree tree; /** true - we are modifying the tree */ private transient boolean usingTree; // do not serialize as true: #32040 /** Map (Class, WeakSet<Result>) of all listeners that are waiting in * changes in class Class */ private transient Map reg; /** count of items in to lookup */ private int count; /** Constructor to create this lookup and associate it with given * Content. The content than allows the creator to invoke protected * methods which are not accessible for any other user of the lookup. * * @param content the content to assciate with * * @since 1.25 */ public AbstractLookup (Content content) { treeLock = content; content.attach (this); } public String toString() { if (getClass() == AbstractLookup.class && treeLock.getClass() == InstanceContent.class) { return "AbstractLookup" + lookup(new Lookup.Template(Object.class)).allInstances(); // NOI18N } else { return super.toString(); } } /** Default constructor for subclasses that do not need to provide a content */ protected AbstractLookup () { this (new Content ()); } /** Getter for the tree object. */ private InheritanceTree getTree () { if (tree != null) { return tree; } synchronized (treeLock) { if (tree == null) { // ok, I am the thread that is going to initialize the map tree = new InheritanceTree (); initialize (); } } return tree; } /** Should be called under synchronized(this) before * setting usingTree to true. It raises exception if * usingTree is already set to true - the check is intended to * assure that we are not trying to simultaneously modify the tree * while traversing it. */ private void checkForTreeModification() { if (usingTree) { usingTree = false; throw new IllegalStateException("You are trying to modify lookup from lookup query!"); // NOI18N } } /** Method for subclasses to initialize them selves. */ protected void initialize () { treeLock.initialize (); } /** Notifies subclasses that a query is about to be processed. * @param template the template */ protected void beforeLookup (Template template) { treeLock.beforeLookup (template); } /** The method to add instance to the lookup with. * @param pair class/instance pair */ protected final void addPair (Pair pair) { HashSet toNotify = new HashSet (); InheritanceTree t = getTree (); // manipulation with map must be synchronized on this synchronized (this) { checkForTreeModification(); try { usingTree = true; ArrayList affected = new ArrayList (); if (t.add (pair, affected)) { // if the pair is newly added and was not there before collectListenersForList (toNotify, affected); pair.index = count++; } } finally { usingTree = false; } } notifyListeners (toNotify); } /** Remove instance. * @param pair class/instance pair */ protected final void removePair (Pair pair) { HashSet toNotify; synchronized (this) { if (tree == null) { // nothing needs to be done return; } toNotify = new HashSet (); ArrayList affected = new ArrayList (); tree.remove (pair, affected); collectListenersForList (toNotify, affected); } notifyListeners (toNotify); } /** Changes all pairs in the lookup to new values. * @param collection the collection of (Pair) objects */ protected final void setPairs (Collection collection) { HashSet toNotify = new HashSet (27); InheritanceTree t = getTree (); // manipulation with tree must be synchronized on this synchronized (this) { checkForTreeModification(); try { usingTree = true; // map between the Items and their indexes (Integer) HashMap shouldBeThere = new HashMap (collection.size () * 2); count = 0; Iterator it = collection.iterator (); ArrayList arr = new ArrayList (); while (it.hasNext ()) { Pair item = (Pair)it.next (); if (t.add (item, arr)) { // the item has not been there yet collectListenersForList (toNotify, arr); } // remeber the item, because it should not be removed shouldBeThere.put (item, new Info (count++, arr)); arr.clear (); } ArrayList modified = new ArrayList (27); // deletes all objects that should not be there and t.retainAll (shouldBeThere, modified); // collect listeners collectListenersForList (toNotify, modified); /* // check consistency Enumeration en = t.lookup (java.lang.Object.class); boolean[] max = new boolean[count]; int mistake = -1; while (en.hasMoreElements ()) { Pair item = (Pair)en.nextElement (); if (max[item.index]) { mistake = item.index; } max[item.index] = true; } if (mistake != -1) { System.err.println ("Mistake at: " + mistake); tree.print (System.err, true); } */ } finally { usingTree = false; } } notifyListeners (toNotify); } private synchronized final void writeObject(ObjectOutputStream oos) throws IOException { // #32040: don't write half-made changes oos.defaultWriteObject(); } /** Lookups an object of given interface. This is the simplest method * for the lookuping, if more registered objects implement the given * class any of them can be returned. * * @param clazz class of the object we are searching for * @return the object implementing given class or null if no such * has been found */ public final Object lookup (Class clazz) { Lookup.Item item = lookupItem (new Lookup.Template (clazz)); return item == null ? null : item.getInstance (); } /** Lookups just one item. * @param template a template for what to find * @return item or null */ public final Lookup.Item lookupItem (Lookup.Template template) { AbstractLookup.this.beforeLookup (template); InheritanceTree t = getTree (); // manipulation with map must be synchronized synchronized (this) { checkForTreeModification(); try { usingTree = true; Enumeration en = t.lookup (template.getType ()); int smallest = InheritanceTree.unsorted (en) ? Integer.MAX_VALUE : Integer.MIN_VALUE; Pair res = null; while (en.hasMoreElements ()) { Pair item = (Pair)en.nextElement (); if (matches (template, item)) { if (smallest == Integer.MIN_VALUE) { // ok, sorted enumeration the first that matches is fine return item; } else { // check for the smallest item if (smallest > item.index) { smallest = item.index; res = item; } } } } return res; } finally { usingTree = false; } } } /** The general lookup method. * @param template the template describing the services we are looking for * @return object containing the results */ public synchronized final Lookup.Result lookup (Lookup.Template template) { R result = new R (template); if (reg == null) { reg = new HashMap (11); } WeakSet ws = (WeakSet)reg.get (template.getType ()); if (ws == null) { ws = new WeakSet(11); reg.put (template.getType (), ws); } ws.add(result); return result; } /** Collects all listeners that should be interested in change in the set * of classes. */ private void collectListenersForList (HashSet allAffectedResults, ArrayList list) { if (list.size () == 1) { // probably the most common case collectListeners (allAffectedResults, (Class)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 (HashSet allAffectedResults, Class c) { if (reg == null) { return; } while (c != null) { Set l = (Set)reg.get (c); if (l != null && !l.isEmpty ()) { Iterator it = l.iterator (); while (it.hasNext ()) { R result = (R)it.next(); allAffectedResults.add (result); } } c = c.getSuperclass (); } } /** * Call resultChanged on all listeners. * @param listeners array of listeners in the format used by * javax.swing.EventListenerList. It means that there are Class * objects on even positions and the listeners on odd positions * @param ev the event to fire */ static void notifyListeners(final Object []listeners, final LookupEvent ev) { for (int i = listeners.length - 1; i >= 0; i -= 2) { LookupListener ll = (LookupListener)listeners[i]; try { ll.resultChanged(ev); } catch (RuntimeException e) { // Such as e.g. occurred in #32040. Do not halt other things. e.printStackTrace(); } } } /** Notify change to all Results in the set. */ private static void notifyListeners (HashSet allAffectedResults) { if (allAffectedResults.isEmpty ()) { return; } Iterator it = allAffectedResults.iterator (); while (it.hasNext ()) { R result = (R)it.next (); result.fireStateChanged (); } } /** A method that defines matching between Item and Template. * @param item the item to match * @return true if item matches the template requirements, false if not */ static boolean matches (Template t, Pair item) { String id = t.getId (); if (id != null && !item.getId ().equals (id)) { return false; } Object instance = t.getInstance (); if (instance != null && !item.creatorOf (instance)) { return false; } return true; } /** * Compares the array elements for equality. * @return true if all elements in the arrays are equal * (by calling equals(Object x) method) */ private static boolean compareArrays(Object[]a, Object []b) { // handle null values if (a == null) { return (b == null); } else { if (b == null) { return false; } } if (a.length != b.length) { return false; } for (int i = 0; i < a.length; i++) { // handle null values for individual elements if (a[i] == null) { if (b[i] != null) { return false; } // both are null --> ok, take next continue; } else { if (b[i] == null) { return false; } } // perform the comparison if (! a[i].equals(b[i])) { return false; } } return true; } /** Extension to the default lookup item that offers additional information * for the data structures use in AbstractLookup */ public static abstract class Pair extends Lookup.Item implements Serializable { private static final long serialVersionUID = 1L; /** possition of this item in the lookup, manipulated in addPair, removePair, setPairs methods */ int index = -1; /** For use by subclasses. */ protected Pair () {} /** Tests whether this item can produce object * of class c. */ protected abstract boolean instanceOf (Class c); /** Method that can test whether an instance of a class has been created * by this item. * * @param obj the instance * @return if the item has already create an instance and it is the same * as obj. */ protected abstract boolean creatorOf (Object obj); } /** Result based on one instance returned. */ private final class R extends WaitableResult { private Template template; /** temporary caches */ private Set classesCache; private Collection instancesCache; private Collection itemsCache; /** listeners on the results */ private ArrayList listeners; R (Template template) { this.template = template; } /** Delete all cached values, the template changed. */ public void fireStateChanged () { Collection previousItems = itemsCache; classesCache = null; instancesCache = null; itemsCache = null; if (previousItems != null) { Object[] previousArray = previousItems.toArray (); Object[] newArray = allItems ().toArray (); if (compareArrays ( previousArray, newArray )) { // do not fire any change if nothing has been changed return; } } LookupListener[] arr; synchronized (this) { if (listeners == null) return; arr = (LookupListener[])listeners.toArray ( new LookupListener[listeners.size ()] ); } final LookupListener[] ll = arr; final LookupEvent ev = new LookupEvent (this); for (int i = 0; i < ll.length; i++) { ll[i].resultChanged(ev); } } /** Ok, register listeners to all classes and super classes. */ public synchronized void addLookupListener (LookupListener l) { if (listeners == null) { listeners = new ArrayList (); } listeners.add (l); } /** Ok, register listeners to all classes and super classes. */ public synchronized void removeLookupListener (LookupListener l) { listeners.remove (l); } public Collection allInstances () { Collection s = instancesCache; if (s != null) { return s; } s = new ArrayList (allItems ().size ()); Iterator it = allItems ().iterator (); while (it.hasNext ()) { Item item = (Item)it.next (); Object obj = item.getInstance (); if (obj != null) { s.add (obj); } } instancesCache = s; return s; } /** Set of all classes. * */ public Set allClasses () { Set s = classesCache; if (s != null) { return s; } s = new HashSet (); Iterator it = allItems ().iterator (); while (it.hasNext ()) { Item item = (Item)it.next (); Class clazz = item.getType (); if (clazz != null) { s.add (clazz); } } classesCache = s; return s; } /** Items are stored directly in the allItems. */ public Collection allItems () { AbstractLookup.this.beforeLookup (template); if (itemsCache != null) { return itemsCache; } InheritanceTree t = getTree (); synchronized (AbstractLookup.this) { checkForTreeModification(); try { usingTree = true; // manipulation with the tree must be synchronized Enumeration en = t.lookup (template.getType ()); // InheritanceTree is comparator for AbstractLookup.Pairs TreeSet items = new TreeSet (t); while (en.hasMoreElements ()) { Pair i = (Pair)en.nextElement (); if (matches (template, i)) { items.add (i); } } // create a correctly sorted copy using the tree as the comparator itemsCache = Collections.unmodifiableList(new ArrayList(items)); return itemsCache; } finally { usingTree = false; } } } /** Used by proxy results to synchronize before lookup. */ protected void beforeLookup(Lookup.Template t) { if (t.getType () == template.getType ()) { AbstractLookup.this.beforeLookup (t); } } /* Do not need to implement it, the default way is ok. public boolean equals(java.lang.Object obj) { return obj == this; } */ } /** A class that can be used by the creator of the AbstractLookup to * control its content. It can be passed to AbstractLookup constructor * and used to add and remove pairs. * * @since 1.25 */ public static class Content extends Object implements Serializable { private static final long serialVersionUID = 1L; // one of them is always null (except attach stage) /** abstract lookup we are connected to */ private AbstractLookup al = null; private transient ArrayList earlyPairs = new ArrayList(3); /** A lookup attaches to this object. */ final synchronized void attach (AbstractLookup al) { if (this.al == null) { this.al = al; // we must just add no override! Pair[] p = (Pair[]) earlyPairs.toArray(new Pair[earlyPairs.size()]); for (int i = 0; i<p.length; i++) { addPair(p[i]); } earlyPairs = null; } else { throw new IllegalStateException ("Trying to use content for " + al + " but it is already used for " + this.al); // NOI18N } } /** The method to add instance to the lookup with. * @param pair class/instance pair */ public final void addPair (Pair pair) { AbstractLookup a = al; if (a != null) { a.addPair (pair); } else { earlyPairs.add(pair); } } /** Remove instance. * @param pair class/instance pair */ public final void removePair (Pair pair) { AbstractLookup a = al; if (a != null) { a.removePair (pair); } else { earlyPairs.remove(pair); } } /** Changes all pairs in the lookup to new values. * @param c the collection of (Pair) objects */ public final void setPairs (Collection c) { AbstractLookup a = al; if (a != null) { a.setPairs (c); } else { earlyPairs.clear(); earlyPairs.addAll(c); } } // make protected if needed... /** Called when the lookup if first used. */ /*protected*/ void initialize () { } /** Notifies subclasses that a query is about to be processed. * @param template the template */ /*protected*/ void beforeLookup (Template template) { } } // end of R (result) /** Just a holder for index & modified values. */ final static class Info extends Object { public int index; public ArrayList modified; public Info (int i, ArrayList m) { index = i; modified = (ArrayList)m.clone (); } } }