All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.util.lookup.AbstractLookup Maven / Gradle / Ivy

There is a newer version: RELEASE230
Show newest version
/*
 * 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 java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.Lookup;
import org.openide.util.LookupEvent;
import org.openide.util.LookupListener;
import org.openide.util.lookup.implspi.ActiveQueue;


/** 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.
 * 

Serializable since 3.27. * @author Jaroslav Tulach * @since 1.9 */ public class AbstractLookup extends Lookup implements Serializable { static final Logger LOG = Logger.getLogger(AbstractLookup.class.getName()); static final long serialVersionUID = 5L; /** lock for initialization of the maps of lookups */ private static final Object treeLock = new Object(); /** the tree that registers all items (or Integer as a treshold size) */ private Object tree; /** 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) { content.attach(this); } /** Constructor for testing purposes that allows specification of storage * as mechanism as well. */ AbstractLookup(Content content, Storage storage) { this(content); this.tree = storage; initialize(); } /** Constructor for testing purposes that allows specification of storage * as mechanism as well. * @param trashhold number of Pair to "remain small" */ AbstractLookup(Content content, Integer trashhold) { this(content); this.tree = trashhold; } /** Default constructor for subclasses that do not need to provide a content */ protected AbstractLookup() { } @Override public String toString() { if (tree instanceof Storage) { return "AbstractLookup" + lookup(new Lookup.Template(Object.class)).allItems(); // NOI18N } else { return super.toString(); } } /** Entres the storage management system. */ @SuppressWarnings("unchecked") private AbstractLookup.Storage enterStorage() { for (;;) { synchronized (treeLock) { if (tree instanceof AbstractLookup.Storage) { if (tree instanceof DelegatingStorage) { // somebody is using the lookup right now DelegatingStorage del = (DelegatingStorage) tree; // check whether there is not access from the same // thread (can throw exception) del.checkForTreeModification(); try { treeLock.wait(); } catch (InterruptedException ex) { // ignore and go on } continue; } else { // ok, tree is initialized and nobody is using it yet tree = new DelegatingStorage((Storage) tree); return (Storage) tree; } } // first time initialization of the tree if (tree instanceof Integer) { tree = new ArrayStorage((Integer) tree); } else { tree = new ArrayStorage(); } } // the tree has not yet been initilized, initialize and go on again initialize(); } } /** Exists tree ownership. */ private AbstractLookup.Storage exitStorage() { synchronized (treeLock) { AbstractLookup.Storage stor = ((DelegatingStorage) tree).exitDelegate(); tree = stor; treeLock.notifyAll(); return stor; } } /** Method for subclasses to initialize them selves. */ protected void initialize() { } /** Notifies subclasses that a query is about to be processed. * @param template the template */ protected void beforeLookup(Template template) { } /** currently a hook for MetaInfServicesLookup to initialize itself * before the result is created. Prevents unwanted change events delivered * later. Possible candidate for a public API, if needed by other lookups. */ void beforeLookupResult(Template template) { } /** The method to add instance to the lookup with. * @param pair class/instance pair */ protected final void addPair(Pair pair) { addPairImpl(pair, null); } /** The method to add instance to the lookup with. * @param pair class/instance pair * @param notifyIn the executor that will handle the notification of events * @since 7.16 */ protected final void addPair(Pair pair, Executor notifyIn) { addPairImpl(pair, notifyIn); } private final void addPairImpl(Pair pair, Executor notifyIn) { HashSet toNotify = new HashSet(); AbstractLookup.Storage t = enterStorage(); Transaction transaction = null; try { transaction = t.beginTransaction(-2); if (t.add(pair, transaction)) { try { pair.setIndex(t, count++); } catch (IllegalStateException ex) { // remove the pair t.remove(pair, transaction); // rethrow the exception throw ex; } // if the pair is newly added and was not there before t.endTransaction(transaction, toNotify); } else { // just finish the process by calling endTransaction t.endTransaction(transaction, new HashSet()); } } finally { exitStorage(); } notifyIn(notifyIn, toNotify); } /** Remove instance. * @param pair class/instance pair */ protected final void removePair(Pair pair) { removePairImpl(pair, null); } /** Remove instance. * @param pair class/instance pair * @param notifyIn the executor that will handle the notification of events * @since 7.16 */ protected final void removePair(Pair pair, Executor notifyIn) { removePairImpl(pair, notifyIn); } private void removePairImpl(Pair pair, Executor notifyIn) { HashSet toNotify = new HashSet(); AbstractLookup.Storage t = enterStorage(); Transaction transaction = null; try { transaction = t.beginTransaction(-1); t.remove(pair, transaction); t.endTransaction(transaction, toNotify); } finally { exitStorage(); } notifyIn(notifyIn, toNotify); } /** Changes all pairs in the lookup to new values. * @param collection the collection of (Pair) objects */ protected final void setPairs(Collection collection) { setPairs(collection, null); } /** Changes all pairs in the lookup to new values, notifies listeners * using provided executor. * * @param collection the collection of (Pair) objects * @param notifyIn the executor that will handle the notification of events * @since 7.16 */ protected final void setPairs(Collection collection, Executor notifyIn) { HashSet listeners = setPairsAndCollectListeners(collection); notifyIn(notifyIn, listeners); } final void notifyIn(Executor notifyIn, final HashSet listeners) { NotifyListeners notify = new NotifyListeners(listeners); if (notify.shallRun()) { if (notifyIn == null) { notify.run(); } else { notifyIn.execute(notify); } } } /** Getter for set of pairs. Package private contract with MetaInfServicesLookup. * @return a LinkedHashSet that can be modified */ final LinkedHashSet> getPairsAsLHS() { AbstractLookup.Storage t = enterStorage(); try { Enumeration> en = t.lookup(Object.class); TreeSet> arr = new TreeSet>(ALPairComparator.DEFAULT); while (en.hasMoreElements()) { Pair item = en.nextElement(); arr.add(item); } return new LinkedHashSet>(arr); } finally { exitStorage(); } } /** Collects listeners without notification. Needed in MetaInfServicesLookup * right now, but maybe will become an API later. */ final HashSet setPairsAndCollectListeners(Collection collection) { HashSet toNotify = new HashSet(27); AbstractLookup.Storage t = enterStorage(); Transaction transaction = null; try { // map between the Items and their indexes (Integer) HashMap,Info> shouldBeThere = new HashMap,Info>(collection.size() * 2); count = 0; Iterator it = collection.iterator(); transaction = t.beginTransaction(collection.size()); while (it.hasNext()) { Pair item = (Pair) it.next(); if (t.add(item, transaction)) { // the item has not been there yet //t.endTransaction(transaction, toNotify); } // remeber the item, because it should not be removed shouldBeThere.put(item, new Info(count++, transaction)); // arr.clear (); } // Object transaction = t.beginTransaction (); // deletes all objects that should not be there and t.retainAll(shouldBeThere, transaction); // collect listeners t.endTransaction(transaction, toNotify); /* // 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 { exitStorage(); } return toNotify; } private final void writeObject(ObjectOutputStream oos) throws IOException { AbstractLookup.Storage s = enterStorage(); try { // #36830: Serializing only InheritanceTree no ArrayStorage s.beginTransaction(Integer.MAX_VALUE); // #32040: don't write half-made changes oos.defaultWriteObject(); } finally { exitStorage(); } } public final T lookup(Class clazz) { Lookup.Item item = lookupItem(new Lookup.Template(clazz)); return (item == null) ? null : item.getInstance(); } @Override public final Lookup.Item lookupItem(Lookup.Template template) { AbstractLookup.this.beforeLookup(template); ArrayList> list = null; AbstractLookup.Storage t = enterStorage(); try { Enumeration> en; try { en = t.lookup(template.getType()); return findSmallest(en, template, false); } catch (AbstractLookup.ISE ex) { // not possible to enumerate the exception, ok, copy it // to create new list = new ArrayList>(); en = t.lookup(null); // this should get all the items without any checks // the checks will be done out side of the storage while (en.hasMoreElements()) { list.add(en.nextElement()); } } } finally { exitStorage(); } return findSmallest(Collections.enumeration(list), template, true); } private static Pair findSmallest(Enumeration> en, Lookup.Template template, boolean deepCheck) { int smallest = InheritanceTree.unsorted(en) ? Integer.MAX_VALUE : Integer.MIN_VALUE; Pair res = null; while (en.hasMoreElements()) { Pair item = en.nextElement(); if (matches(template, item, deepCheck)) { 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.getIndex()) { smallest = item.getIndex(); res = item; } } } } return res; } @Override public final Lookup.Result lookup(Lookup.Template template) { beforeLookupResult(template); for (;;) { AbstractLookup.ISE toRun = null; AbstractLookup.Storage t = enterStorage(); try { Lookup.Result prev = t.findResult(template); if (prev != null) { return prev; } R r = new R(); ReferenceToResult newRef = new ReferenceToResult(r, this, template); newRef.next = t.registerReferenceToResult(newRef); return r; } catch (AbstractLookup.ISE ex) { toRun = ex; } finally { exitStorage(); } toRun.recover(this); // and try again } } /** Notifies listeners. * @param allAffectedResults set of R */ static final class NotifyListeners implements Runnable { private final ArrayList evAndListeners; public NotifyListeners(Set allAffectedResults) { if (allAffectedResults.isEmpty()) { evAndListeners = null; return; } evAndListeners = new ArrayList(); { for (R result : allAffectedResults) { result.collectFires(evAndListeners); } } } public boolean shallRun() { return evAndListeners != null && !evAndListeners.isEmpty(); } public void run() { Iterator it = evAndListeners.iterator(); while (it.hasNext()) { LookupEvent ev = (LookupEvent)it.next(); LookupListener l = (LookupListener)it.next(); try { l.resultChanged(ev); } catch (RuntimeException x) { LOG.log(Level.WARNING, null, x); } } } } /** * Call resultChanged on all listeners. * @param listeners array of listeners * @param ev the event to fire */ static void notifyListeners(Object[] listeners, LookupEvent ev, Collection evAndListeners) { for (int i = listeners.length - 1; i >= 0; i--) { if (! (listeners[i] instanceof LookupListener)) { continue; } LookupListener ll = (LookupListener)listeners[i]; try { if (evAndListeners != null) { if (ll instanceof WaitableResult) { WaitableResult wr = (WaitableResult)ll; wr.collectFires(evAndListeners); } else { evAndListeners.add(ev); evAndListeners.add(ll); } } else { ll.resultChanged(ev); } } catch (CycleError err) { err.add(evAndListeners); throw err; } catch (StackOverflowError err) { CycleError cycle = new CycleError(err.getMessage()); cycle.add(evAndListeners); throw cycle; } catch (ISE ex) { throw ex; } catch (RuntimeException e) { // Such as e.g. occurred in #32040. Do not halt other things. e.printStackTrace(); } } } private static class CycleError extends StackOverflowError { private final Set> print = new HashSet>(); public CycleError(String s) { super(s); } public void add(Collection evAndListeners) { this.print.add(evAndListeners); } @Override public String getMessage() { StringBuilder sb = new StringBuilder(); sb.append("StackOverflowError. There is ").append(print.size()).append(" listeners:"); // NOI18N int round = 0; for (Collection list : print) { sb.append("\nRound ").append(++round).append("\n"); // NOI18N if (list != null) { for (Object o : list) { sb.append("\n ").append(o); // NOI18N if (sb.length() > 10000) { break; } } } else { sb.append("listeners are null"); // NOI18N } } return sb.toString(); } } // end of CycleError /** A method that defines matching between Item and Template. * @param t template providing the criteria * @param item the item to match * @param deepCheck true if type of the pair should be tested, false if it is already has been tested * @return true if item matches the template requirements, false if not */ static boolean matches(Template t, Pair item, boolean deepCheck) { String id = t.getId(); if (id != null && !id.equals(item.getId())) { return false; } Object instance = t.getInstance(); if ((instance != null) && !item.creatorOf(instance)) { return false; } if (deepCheck) { return item.instanceOf(t.getType()); } else { 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; } /** Method to be called when a result is cleared to signal that the list * of all result should be checked for clearing. * @param template the template the result was for * @return true if the hash map with all items has been cleared */ boolean cleanUpResult(Lookup.Template template) { AbstractLookup.Storage t = enterStorage(); try { return t.cleanUpResult(template) == null; } finally { exitStorage(); Thread.yield(); } } /** Storage check for tests. */ static boolean isSimple(AbstractLookup l) { return DelegatingStorage.isSimple((Storage)l.tree); } /** Generic support for listeners, so it can be used in other results * as well. * @param add true to add it, false to modify * @param l listener to modify * @param ref the value of the reference to listener or listener list * @return new value to the reference to listener or list */ @SuppressWarnings("unchecked") static Object modifyListenerList(boolean add, LookupListener l, Object ref) { if (add) { if (ref == null) { return l; } if (ref instanceof LookupListener) { ArrayList arr = new ArrayList(); arr.add(ref); ref = arr; } ((ArrayList) ref).add(l); return ref; } else { // remove if (ref == null) { return null; } if (ref == l) { return null; } if (!(ref instanceof ArrayList)) { return ref; } ArrayList arr = (ArrayList) ref; arr.remove(l); if (arr.size() == 1) { return arr.iterator().next(); } else { return arr; } } } private static ReferenceQueue activeQueue() { return ActiveQueue.queue(); } /** Storage to keep the internal structure of Pairs and to answer * different queries. */ interface Storage { /** Initializes a modification operation by creating an object * that will be passsed to all add, remove, retainAll methods * and should collect enough information about the change to * notify listeners about the transaction later * * @param ensure the amount of items that will appear in the storage * after the modifications (-1 == remove one, -2 == add one, >= 0 * the amount of objects at the end * @return a token to identify the transaction */ public Transaction beginTransaction(int ensure); /** Collects all affected results R that were modified in the * given transaction. * * @param modified place to add results R to * @param transaction the transaction indentification */ public void endTransaction(Transaction transaction, Set modifiedResults); /** Adds an item into the storage. * @param item to add * @param transaction transaction token * @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, Transaction transaction); /** Removes an item. */ public void remove(AbstractLookup.Pair item, Transaction transaction); /** Removes all items that are not present in the provided collection. * @param retain collection of Pairs to keep them in * @param transaction the transaction context */ public void retainAll(Map retain, Transaction transaction); /** Queries for instances of given class. * @param clazz the class to check * @return enumeration of Item * @see #unsorted */ public Enumeration> lookup(Class clazz); /** Registers another reference to a result with the storage. This method * has also a special meaning. * * @param newRef the new reference to remember * @return the previous reference that was kept (null if newRef is the first one) * the applications is expected to link from newRef to this returned * value to form a linked list */ public ReferenceToResult registerReferenceToResult(ReferenceToResult newRef); /** Given the provided template, Do cleanup the results. * @param templ template of a result(s) that should be checked * @return null if all references for this template were cleared or one of them */ public ReferenceToResult cleanUpResult(Lookup.Template templ); public Result findResult(Template template); } /** 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 */ private int index = -1; /** For use by subclasses. */ protected Pair() { } final int getIndex() { return index; } final void setIndex(AbstractLookup.Storage tree, int x) { if (tree == null) { this.index = x; return; } if (this.index == -1) { this.index = x; } else { throw new IllegalStateException("You cannot use " + this + " in more than one AbstractLookup. Prev: " + this.index + " new: " + x); // NOI18N } } /** Tests whether this item can produce object * of class c. *

Typically this will produce the same result as * {@code c.isAssignableFrom(}{@link #getType() getType}{@code ())} * but may avoid loading the concrete type's class in doing so. */ 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. */ static final class R extends WaitableResult { /** reference our result is attached to (do not modify) */ public ReferenceToResult reference; /** listeners on the results or pointer to one listener */ private Object listeners; public R() { } /** Checks whether we have simple behaviour of complex. */ private boolean isSimple() { Storage s = (Storage) reference.lookup.tree; return DelegatingStorage.isSimple(s); } // // Handling cache management for both cases, no caches // for simple (but mark that we needed them, so refresh can // be done in cloneList) and complex when all 3 types // of result are cached // private Object getFromCache(int indx) { if (isSimple()) { return null; } Object maybeArray = reference.caches; if (maybeArray instanceof Object[]) { return ((Object[]) maybeArray)[indx]; } return null; } @SuppressWarnings("unchecked") private Set> getClassesCache() { return (Set>) getFromCache(0); } private void setClassesCache(Set s) { if (isSimple()) { // mark it as being used reference.caches = reference; return; } setReferences(0, s); } @SuppressWarnings("unchecked") private Collection getInstancesCache() { return (Collection) getFromCache(1); } private void setInstancesCache(Collection c) { if (isSimple()) { // mark it as being used reference.caches = reference; return; } setReferences(1, c); } @SuppressWarnings("unchecked") private Pair[] getItemsCache() { return (Pair[]) getFromCache(2); } private void setItemsCache(Collection c) { if (isSimple()) { // mark it as being used reference.caches = reference; return; } setReferences(2, c.toArray(new Pair[c.size()])); } private void setReferences(int index, Object value) { Object obj = reference.caches; Object[] arr; if (obj instanceof Object[]) { arr = (Object[])obj; } else { reference.caches = arr = new Object[3]; } arr[index] = value; } private void clearCaches() { if (reference.caches instanceof Object[]) { reference.caches = new Object[3]; } } /** Ok, register listeners to all classes and super classes. */ public synchronized void addLookupListener(LookupListener l) { listeners = modifyListenerList(true, l, listeners); } /** Ok, register listeners to all classes and super classes. */ public synchronized void removeLookupListener(LookupListener l) { listeners = modifyListenerList(false, l, listeners); } /** Delete all cached values, the template changed. */ protected void collectFires(Collection evAndListeners) { Object[] previousItems = getItemsCache(); clearCaches(); if (previousItems != null) { Object[] newArray = allItemsWithoutBeforeLookup().toArray(); if (compareArrays(previousItems, newArray)) { // do not fire any change if nothing has been changed return; } } LookupListener[] arr; synchronized (this) { if (listeners == null) { return; } if (listeners instanceof LookupListener) { arr = new LookupListener[] { (LookupListener) listeners }; } else { ArrayList l = (ArrayList) listeners; arr = l.toArray(new LookupListener[l.size()]); } } final LookupListener[] ll = arr; final LookupEvent ev = new LookupEvent(this); notifyListeners(ll, ev, evAndListeners); } public Collection allInstances() { return allInstances(true); } protected Collection allInstances(boolean callBeforeLookup) { if (callBeforeLookup) { reference.lookup.beforeLookup(reference.template); } Collection s = getInstancesCache(); if (s != null) { return s; } Collection> items = allItemsWithoutBeforeLookup(); ArrayList list = new ArrayList(items.size()); Iterator> it = items.iterator(); while (it.hasNext()) { Pair item = it.next(); T obj = item.getInstance(); if (reference.template.getType().isInstance(obj)) { list.add(obj); } } s = Collections.unmodifiableList(list); setInstancesCache(s); return s; } /** Set of all classes. * */ @Override public Set> allClasses() { reference.lookup.beforeLookup(reference.template); Set> s = getClassesCache(); if (s != null) { return s; } s = new HashSet>(); for (Pair item : allItemsWithoutBeforeLookup()) { Class clazz = item.getType(); if (clazz != null) { s.add(clazz); } } s = Collections.unmodifiableSet(s); setClassesCache(s); return s; } /** Items are stored directly in the allItems. */ @Override public Collection> allItems() { return allItems(true); } @Override protected Collection> allItems(boolean callBeforeLookup) { if (callBeforeLookup) { reference.lookup.beforeLookup(reference.template); } return allItemsWithoutBeforeLookup(); } /** Implements the search for allItems, but without asking for before lookup */ private Collection> allItemsWithoutBeforeLookup() { Pair[] c = getItemsCache(); if (c != null) { return Collections.unmodifiableList(Arrays.asList(c)); } ArrayList> saferCheck = null; AbstractLookup.Storage t = reference.lookup.enterStorage(); try { try { return Collections.unmodifiableCollection(initItems(t)); } catch (AbstractLookup.ISE ex) { // do less effective evaluation of items outside of the // locked storage saferCheck = new ArrayList>(); Enumeration> en = t.lookup(null); // get all Pairs while (en.hasMoreElements()) { Pair i = en.nextElement(); saferCheck.add(i); } } } finally { reference.lookup.exitStorage(); } return extractPairs(saferCheck); } @SuppressWarnings("unchecked") private Collection> extractPairs(final ArrayList> saferCheck) { TreeSet> items = new TreeSet>(ALPairComparator.DEFAULT); for (Pair i : saferCheck) { if (matches(reference.template, i, false)) { items.add((Pair)i); } } return Collections.unmodifiableCollection(items); } /** Initializes items. */ private Collection> initItems(Storage t) { // manipulation with the tree must be synchronized Enumeration> en = t.lookup(reference.template.getType()); // InheritanceTree is comparator for AbstractLookup.Pairs TreeSet> items = new TreeSet>(ALPairComparator.DEFAULT); while (en.hasMoreElements()) { Pair i = en.nextElement(); if (matches(reference.template, i, false)) { items.add(i); } } // create a correctly sorted copy using the tree as the comparator setItemsCache(items); return items; } /** Used by proxy results to synchronize before lookup. */ protected void beforeLookup(Lookup.Template t) { if (t.getType() == reference.template.getType()) { reference.lookup.beforeLookup(t); } } /* Do not need to implement it, the default way is ok. public boolean equals(java.lang.Object obj) { return obj == this; } */ @Override public String toString() { return super.toString() + " for " + reference.template; } } // end of R /** A class that can be used by the creator of the * {@link AbstractLookup} to * control its content (a kind of * Privileged API * giving creator of the lookup more rights than subsequent users of the lookup). * Typical usage:
     * {@link Content} ic = new {@link InstanceContent#InstanceContent() InstanceContent()};
     * {@link Lookup} lookup = new {@link AbstractLookup#AbstractLookup(org.openide.util.lookup.AbstractLookup.Content) AbstractLookup(ic)};
     * {@link Content#addPair(org.openide.util.lookup.AbstractLookup.Pair) ic.addPair(...)};
     * 
* @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; private transient Object notifyIn; /** Default constructor. */ public Content() { this(null); } /** Creates a content associated with an executor to handle dispatch * of changes. * @param notifyIn the executor to notify changes in * @since 7.16 */ public Content(Executor notifyIn) { this.notifyIn = notifyIn; } /** for testing purposes */ final void attachExecutor(Executor notifyIn) { this.notifyIn = notifyIn; } /** A lookup attaches to this object. */ final synchronized void attach(AbstractLookup al) { if (this.al == null) { this.al = al; ArrayList ep = getEarlyPairs(); if (ep != null) { notifyIn = null; setPairs(ep); } } else { throw new IllegalStateException( "Trying to use content for " + al + " but it is already used for " + this.al ); // NOI18N } } /** The method to add a pair to the associated {@link AbstractLookup}. * Preferably call this method when lookup is already associated with * this content (association is done by passing this object to some * {@link AbstractLookup#AbstractLookup(org.openide.util.lookup.AbstractLookup.Content) AbstractLookup's constructor} * once). * * @param pair class/instance pair * @throws NullPointerException may throw NullPointerException if called * before association with {@link AbstractLookup} */ public final void addPair(Pair pair) { AbstractLookup a = al; Executor e = getExecutor(); if (a != null || e != null) { if (a == null) { throw new NullPointerException( "Adding a pair to Content not connected to Lookup is not supported!" // NOI18N ); } a.addPair(pair, e); } else { if (notifyIn == null) { notifyIn = new ArrayList(3); } getEarlyPairs().add(pair); } } /** Remove instance. * @param pair class/instance pair */ public final void removePair(Pair pair) { AbstractLookup a = al; Executor e = getExecutor(); if (a != null || e != null) { a.removePair(pair, e); } else { if (notifyIn == null) { notifyIn = new ArrayList(3); } getEarlyPairs().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; Executor e = getExecutor(); if (a != null || e != null) { a.setPairs(c, e); } else { notifyIn = new ArrayList(c); } } @SuppressWarnings("unchecked") private ArrayList getEarlyPairs() { Object o = notifyIn; return o instanceof ArrayList ? (ArrayList)o : null; } private Executor getExecutor() { Object o = notifyIn; return o instanceof Executor ? (Executor)o : null; } } // end of Content /** Just a holder for index & modified values. */ final static class Info extends Object { public int index; public Object transaction; public Info(int i, Object t) { index = i; transaction = t; } } /** Reference to a result R */ static final class ReferenceToResult extends WeakReference> implements Runnable { /** next refernece in chain, modified only from AbstractLookup or this */ private ReferenceToResult next; /** the template for the result */ public final Template template; /** the lookup we are attached to */ public final AbstractLookup lookup; /** caches for results */ public Object caches; /** Creates a weak refernece to a new result R in context of lookup * for given template */ private ReferenceToResult(R result, AbstractLookup lookup, Template template) { super(result, activeQueue()); this.template = template; this.lookup = lookup; getResult().reference = this; } /** Returns the result or null */ R getResult() { return get(); } /** Cleans the reference. Implements Runnable interface, do not call * directly. */ public void run() { lookup.cleanUpResult(this.template); } /** Clones the reference list to given Storage. * @param storage storage to clone to */ public void cloneList(AbstractLookup.Storage storage) { ReferenceIterator it = new ReferenceIterator(this); while (it.next()) { ReferenceToResult current = it.current(); ReferenceToResult newRef = current.cloneRef(); newRef.next = storage.registerReferenceToResult(newRef); newRef.caches = current.caches; if (current.caches == current) { current.getResult().initItems(storage); } } } private ReferenceToResult cloneRef() { return new ReferenceToResult(getResult(), lookup, template); } } // end of ReferenceToResult /** Supporting class to iterate over linked list of ReferenceToResult * Use: *
     *  ReferenceIterator it = new ReferenceIterator (this.ref);
     *  while (it.next ()) {
     *    it.current (): // do some work
     *  }
     *  this.ref = it.first (); // remember the first one
     */
    static final class ReferenceIterator extends Object {
        private ReferenceToResult first;
        private ReferenceToResult current;

        /** hard reference to current result, so it is not GCed meanwhile */
        private R currentResult;

        /** Initializes the iterator with first reference.
         */
        public ReferenceIterator(ReferenceToResult first) {
            this.first = first;
        }

        /** Moves the current to next possition */
        public boolean next() {
            ReferenceToResult prev;
            ReferenceToResult ref;

            if (current == null) {
                ref = first;
                prev = null;
            } else {
                prev = current;
                ref = current.next;
            }

            while (ref != null) {
                R result = ref.get();

                if (result == null) {
                    if (prev == null) {
                        // move the head
                        first = ref.next;
                    } else {
                        // skip over this reference
                        prev.next = ref.next;
                    }

                    prev = ref;
                    ref = ref.next;
                } else {
                    // we have found next item
                    currentResult = result;
                    current = ref;

                    return true;
                }
            }

            currentResult = null;
            current = null;

            return false;
        }

        /** Access to current reference.
         */
        public ReferenceToResult current() {
            return current;
        }

        /** Access to reference that is supposed to be the first one.
         */
        public ReferenceToResult first() {
            return first;
        }
    }

    /** Signals that a lookup is being modified from a lookup query.
     *
     * @author  Jaroslav Tulach
     */
    static final class ISE extends IllegalStateException {
        static final long serialVersionUID = 100L;
        
        /** list of jobs to execute. */
        private java.util.List jobs;

        /** @param msg message
         */
        public ISE(String msg) {
            super(msg);
        }

        /** Registers a job to be executed partially out and partially in
         * the lock over storage.
         */
        public void registerJob(Job job) {
            if (jobs == null) {
                jobs = new java.util.ArrayList();
            }

            jobs.add(job);
        }

        /** Executes the jobs outside, and then inside a locked session.
         */
        public void recover(AbstractLookup lookup) {
            if (jobs == null) {
                // no recovery plan, throw itself
                throw this;
            }

            for (Job j : jobs) {
                j.before();
            }

            AbstractLookup.Storage s = lookup.enterStorage();

            try {
                for (Job j : jobs) {
                    j.inside();
                }
            } finally {
                lookup.exitStorage();
            }
        }

        /** A job to be executed partially outside and partially inside
         * the storage lock.
         */
        static interface Job {
            public void before();

            public void inside();
        }
    }
     // end of ISE
}