/*
 * 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.
 * <p>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>(Object.class)).allItems(); // NOI18N
        } else {
            return super.toString();
        }
    }

    /** Entres the storage management system.
     */
    @SuppressWarnings("unchecked")
    private <T> AbstractLookup.Storage<T> 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<T>) tree);

                        return (Storage<T>) 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 <Transaction> void addPairImpl(Pair<?> pair, Executor notifyIn) {
        HashSet<R> toNotify = new HashSet<R>();

        AbstractLookup.Storage<Transaction> 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<R>());
            }
        } 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 <Transaction> void removePairImpl(Pair<?> pair, Executor notifyIn) {
        HashSet<R> toNotify = new HashSet<R>();

        AbstractLookup.Storage<Transaction> 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<? extends Pair> 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<? extends Pair> collection, Executor notifyIn) {
        HashSet<R> listeners = setPairsAndCollectListeners(collection);
        notifyIn(notifyIn, listeners);
    }
    
    final void notifyIn(Executor notifyIn, final HashSet<R> 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<Pair<?>> getPairsAsLHS() {
        AbstractLookup.Storage<?> t = enterStorage();

        try {
            Enumeration<Pair<Object>> en = t.lookup(Object.class);
            TreeSet<Pair<?>> arr = new TreeSet<Pair<?>>(ALPairComparator.DEFAULT);
            while (en.hasMoreElements()) {
                Pair<Object> item = en.nextElement();
                arr.add(item);
            }
            return new LinkedHashSet<Pair<?>>(arr);
        } finally {
            exitStorage();
        }
    }

    /** Collects listeners without notification. Needed in MetaInfServicesLookup
     * right now, but maybe will become an API later.
     */
    final <Transaction> HashSet<R> setPairsAndCollectListeners(Collection<? extends Pair> collection) {
        HashSet<R> toNotify = new HashSet<R>(27);

        AbstractLookup.Storage<Transaction> t = enterStorage();
        Transaction transaction = null;

        try {
            // map between the Items and their indexes (Integer)
            HashMap<Item<?>,Info> shouldBeThere = new HashMap<Item<?>,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> T lookup(Class<T> clazz) {
        Lookup.Item<T> item = lookupItem(new Lookup.Template<T>(clazz));
        return (item == null) ? null : item.getInstance();
    }

    @Override
    public final <T> Lookup.Item<T> lookupItem(Lookup.Template<T> template) {
        AbstractLookup.this.beforeLookup(template);

        ArrayList<Pair<T>> list = null;
        AbstractLookup.Storage<?> t = enterStorage();

        try {
            Enumeration<Pair<T>> 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<Pair<T>>();
                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 <T> Pair<T> findSmallest(Enumeration<Pair<T>> en, Lookup.Template<T> template, boolean deepCheck) {
        int smallest = InheritanceTree.unsorted(en) ? Integer.MAX_VALUE : Integer.MIN_VALUE;
        Pair<T> res = null;

        while (en.hasMoreElements()) {
            Pair<T> 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 <T> Lookup.Result<T> lookup(Lookup.Template<T> template) {
        beforeLookupResult(template);
        for (;;) {
            AbstractLookup.ISE toRun = null;

            AbstractLookup.Storage<?> t = enterStorage();

            try {
                Lookup.Result<T> prev = t.findResult(template);
                if (prev != null) {
                    return prev;
                }
                
                R<T> r = new R<T>();
                ReferenceToResult<T> newRef = new ReferenceToResult<T>(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<Object> evAndListeners;
        
        public NotifyListeners(Set<R> allAffectedResults) {
            if (allAffectedResults.isEmpty()) {
                evAndListeners = null;
                return;
            }

            evAndListeners = new ArrayList<Object>();
            {
                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<Object> 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<Collection<Object>> print = new HashSet<Collection<Object>>();

        public CycleError(String s) {
            super(s);
        }
        
        public void add(Collection<Object> 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<Object> 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
     */
    <T> boolean cleanUpResult(Lookup.Template<T> 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<Object> activeQueue() {
        return ActiveQueue.queue();
    }

    /** Storage to keep the internal structure of Pairs and to answer
     * different queries.
     */
    interface Storage<Transaction> {
        /** 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<R> 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 <T> Enumeration<Pair<T>> lookup(Class<T> 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 <T> Result<T> findResult(Template<T> template);
    }

    /** Extension to the default lookup item that offers additional information
     * for the data structures use in AbstractLookup
     */
    public static abstract class Pair<T> extends Lookup.Item<T> 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.
        * <p>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<T> extends WaitableResult<T> {
        /** reference our result is attached to (do not modify) */
        public ReferenceToResult<T> 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<Class<? extends T>> getClassesCache() {
            return (Set<Class<? extends T>>) 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<T> getInstancesCache() {
            return (Collection<T>) 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<T>[] getItemsCache() {
            return (Pair<T>[]) 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<Object> 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<T> allInstances() {
            return allInstances(true);
        }
        protected Collection<T> allInstances(boolean callBeforeLookup) {
            if (callBeforeLookup) {
                reference.lookup.beforeLookup(reference.template);
            }

            Collection<T> s = getInstancesCache();

            if (s != null) {
                return s;
            }

            Collection<Pair<T>> items = allItemsWithoutBeforeLookup();
            ArrayList<T> list = new ArrayList<T>(items.size());

            Iterator<Pair<T>> it = items.iterator();

            while (it.hasNext()) {
                Pair<T> 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<Class<? extends T>> allClasses() {
            reference.lookup.beforeLookup(reference.template);

            Set<Class<? extends T>> s = getClassesCache();

            if (s != null) {
                return s;
            }

            s = new HashSet<Class<? extends T>>();

            for (Pair<T> item : allItemsWithoutBeforeLookup()) {
                Class<? extends T> 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<? extends Item<T>> allItems() {
            return allItems(true);
        }
        @Override
        protected Collection<? extends Item<T>> 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<Pair<T>> allItemsWithoutBeforeLookup() {
            Pair<T>[] c = getItemsCache();

            if (c != null) {
                return Collections.unmodifiableList(Arrays.asList(c));
            }

            ArrayList<Pair<Object>> 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<Pair<Object>>();

                    Enumeration<Pair<Object>> en = t.lookup(null); // get all Pairs

                    while (en.hasMoreElements()) {
                        Pair<Object> i = en.nextElement();
                        saferCheck.add(i);
                    }
                }
            } finally {
                reference.lookup.exitStorage();
            }
            return extractPairs(saferCheck);
        }

        @SuppressWarnings("unchecked")
        private Collection<Pair<T>> extractPairs(final ArrayList<Pair<Object>> saferCheck) {
            TreeSet<Pair<T>> items = new TreeSet<Pair<T>>(ALPairComparator.DEFAULT);
            for (Pair<Object> i : saferCheck) {
                if (matches(reference.template, i, false)) {
                    items.add((Pair<T>)i);
                }
            }
            return Collections.unmodifiableCollection(items);
        }

        /** Initializes items.
         */
        private Collection<Pair<T>> initItems(Storage<?> t) {
            // manipulation with the tree must be synchronized
            Enumeration<Pair<T>> en = t.lookup(reference.template.getType());

            // InheritanceTree is comparator for AbstractLookup.Pairs
            TreeSet<Pair<T>> items = new TreeSet<Pair<T>>(ALPairComparator.DEFAULT);

            while (en.hasMoreElements()) {
                Pair<T> 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 
     * <a href="http://wiki.apidesign.org/wiki/Privileged_API">Privileged API</a>
     * giving creator of the lookup more rights than subsequent users of the lookup).
     * Typical usage: <pre>
     * {@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(...)};
     * </pre>
     * @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<Pair> 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 <em>content</em> (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<Pair>(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<Pair>(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<? extends Pair> c) {
            AbstractLookup a = al;
            Executor e = getExecutor();
            
            if (a != null || e != null) {
                a.setPairs(c, e);
            } else {
                notifyIn = new ArrayList<Pair>(c);
            }
        }

        @SuppressWarnings("unchecked")
        private ArrayList<Pair> getEarlyPairs() {
            Object o = notifyIn;
            return o instanceof ArrayList ? (ArrayList<Pair>)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<T> extends WeakReference<R<T>> implements Runnable {
        /** next refernece in chain, modified only from AbstractLookup or this */
        private ReferenceToResult<?> next;

        /** the template for the result */
        public final Template<T> 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<T> result, AbstractLookup lookup, Template<T> template) {
            super(result, activeQueue());
            this.template = template;
            this.lookup = lookup;
            getResult().reference = this;
        }

        /** Returns the result or null
         */
        R<T> 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<T> cloneRef() {
            return new ReferenceToResult<T>(getResult(), lookup, template);
        }
    }
     // end of ReferenceToResult

    /** Supporting class to iterate over linked list of ReferenceToResult
     * Use:
     * <PRE>
     *  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<Job> 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<Job>();
            }

            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
}
