/*
 * 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.apache.aries.util.tracker.hook;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleEvent;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.SynchronousBundleListener;
import org.osgi.framework.hooks.bundle.EventHook;
import org.osgi.util.tracker.BundleTracker;
import org.osgi.util.tracker.BundleTrackerCustomizer;

/**
 * The Tracked and AbstractTracked inner classes are copied from felix framework 4.0.1.
 *
 * @version $Rev$ $Date$
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class BundleHookBundleTracker<T> extends BundleTracker {

    static {
        Class c = EventHook.class;
    }

    /* set this to true to compile in debug messages */
    static final boolean				DEBUG	= false;

    /**
     * The Bundle Context used by this {@code BundleTracker}.
     */
    protected final BundleContext context;

    /**
     * The {@code BundleTrackerCustomizer} object for this tracker.
     */
    final BundleTrackerCustomizer customizer;
    /**
     * Tracked bundles: {@code Bundle} object -> customized Object and
     * {@code BundleListener} object
     */
    private volatile Tracked tracked;

    /**
     * Accessor method for the current Tracked object. This method is only
     * intended to be used by the unsynchronized methods which do not modify the
     * tracked field.
     *
     * @return The current Tracked object.
     */
    private Tracked tracked() {
        return tracked;
    }

    /**
     * State mask for bundles being tracked. This field contains the ORed values
     * of the bundle states being tracked.
     */
    private final int mask;

    /**
     * BundleHook service registration
     */
    private ServiceRegistration sr;

    /**
     * Create a {@code BundleTracker} for bundles whose state is present in the
     * specified state mask.
     *
     * <p>
     * Bundles whose state is present on the specified state mask will be
     * tracked by this {@code BundleTracker}.
     *
     * @param context The {@code BundleContext} against which the tracking is
     *        done.
     * @param stateMask The bit mask of the {@code OR}ing of the bundle states
     *        to be tracked.
     * @param customizer The customizer object to call when bundles are added,
     *        modified, or removed in this {@code BundleTracker}. If customizer
     *        is {@code null}, then this {@code BundleTracker} will be used as
     *        the {@code BundleTrackerCustomizer} and this {@code BundleTracker}
     *        will call the {@code BundleTrackerCustomizer} methods on itself.
     * @see Bundle#getState()
     */
    public BundleHookBundleTracker(BundleContext context, int stateMask, BundleTrackerCustomizer customizer) {
        super(context, stateMask, customizer);
        this.context = context;
        this.mask = stateMask;
        this.customizer = customizer == null ? this : customizer;
    }

    /**
     * Open this {@code BundleTracker} and begin tracking bundles.
     *
     * <p>
     * Bundle which match the state criteria specified when this
     * {@code BundleTracker} was created are now tracked by this
     * {@code BundleTracker}.
     *
     * @throws java.lang.IllegalStateException If the {@code BundleContext} with
     *         which this {@code BundleTracker} was created is no longer valid.
     * @throws java.lang.SecurityException If the caller and this class do not
     *         have the appropriate
     *         {@code AdminPermission[context bundle,LISTENER]}, and the Java
     *         Runtime Environment supports permissions.
     */
    @Override
    public void open() {
        final Tracked t;
        synchronized (this) {
            if (tracked != null) {
                return;
            }
            t = new Tracked();
            synchronized (t) {
                EventHook hook = new BundleEventHook(t);
                sr = context.registerService(EventHook.class.getName(), hook, null);
                Bundle[] bundles = context.getBundles();
                if (bundles != null) {
                    int length = bundles.length;
                    for (int i = 0; i < length; i++) {
                        int state = bundles[i].getState();
                        if ((state & mask) == 0) {
                            /* null out bundles whose states are not interesting */
                            bundles[i] = null;
                        }
                    }
                    /* set tracked with the initial bundles */
                    t.setInitial(bundles);
                }
            }
            tracked = t;
        }
        /* Call tracked outside of synchronized region */
        tracked.trackInitial(); /* process the initial references */
    }

    /**
     * Close this {@code BundleTracker}.
     *
     * <p>
     * This method should be called when this {@code BundleTracker} should end
     * the tracking of bundles.
     *
     * <p>
     * This implementation calls {@link #getBundles()} to get the list of
     * tracked bundles to remove.
     */
    @Override
    public void close() {
        final Bundle[] bundles;
        final Tracked outgoing;
        synchronized (this) {
            outgoing = tracked;
            if (outgoing == null) {
                return;
            }
            if (DEBUG) {
                System.out.println("BundleTracker.close"); //$NON-NLS-1$
            }
            tracked.close();
            bundles = getBundles();
            tracked = null;
            try {
                sr.unregister();
            } catch (IllegalStateException e) {
				/* In case the context was stopped. */
            }
        }
        if (bundles != null) {
            for (int i = 0; i < bundles.length; i++) {
                outgoing.untrack(bundles[i], null);
            }
        }
    }

    /**
     * Default implementation of the
     * {@code BundleTrackerCustomizer.addingBundle} method.
     *
     * <p>
     * This method is only called when this {@code BundleTracker} has been
     * constructed with a {@code null BundleTrackerCustomizer} argument.
     *
     * <p>
     * This implementation simply returns the specified {@code Bundle}.
     *
     * <p>
     * This method can be overridden in a subclass to customize the object to be
     * tracked for the bundle being added.
     *
     * @param bundle The {@code Bundle} being added to this
     *        {@code BundleTracker} object.
     * @param event The bundle event which caused this customizer method to be
     *        called or {@code null} if there is no bundle event associated with
     *        the call to this method.
     * @return The specified bundle.
     * @see BundleTrackerCustomizer#addingBundle(Bundle, BundleEvent)
     */
    @Override
    public Object addingBundle(Bundle bundle, BundleEvent event) {
        T result = (T) bundle;
        return result;
    }

    /**
     * Default implementation of the
     * {@code BundleTrackerCustomizer.modifiedBundle} method.
     *
     * <p>
     * This method is only called when this {@code BundleTracker} has been
     * constructed with a {@code null BundleTrackerCustomizer} argument.
     *
     * <p>
     * This implementation does nothing.
     *
     * @param bundle The {@code Bundle} whose state has been modified.
     * @param event The bundle event which caused this customizer method to be
     *        called or {@code null} if there is no bundle event associated with
     *        the call to this method.
     * @param object The customized object for the specified Bundle.
     * @see BundleTrackerCustomizer#modifiedBundle(Bundle, BundleEvent, Object)
     */
    @Override
    public void modifiedBundle(Bundle bundle, BundleEvent event, Object object) {
		/* do nothing */
    }

    /**
     * Default implementation of the
     * {@code BundleTrackerCustomizer.removedBundle} method.
     *
     * <p>
     * This method is only called when this {@code BundleTracker} has been
     * constructed with a {@code null BundleTrackerCustomizer} argument.
     *
     * <p>
     * This implementation does nothing.
     *
     * @param bundle The {@code Bundle} being removed.
     * @param event The bundle event which caused this customizer method to be
     *        called or {@code null} if there is no bundle event associated with
     *        the call to this method.
     * @param object The customized object for the specified bundle.
     * @see BundleTrackerCustomizer#removedBundle(Bundle, BundleEvent, Object)
     */
    @Override
    public void removedBundle(Bundle bundle, BundleEvent event, Object object) {
        /* do nothing */
    }

    /**
     * Return an array of {@code Bundle}s for all bundles being tracked by this
     * {@code BundleTracker}.
     *
     * @return An array of {@code Bundle}s or {@code null} if no bundles are
     *         being tracked.
     */
    public Bundle[] getBundles() {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return null;
        }
        synchronized (t) {
            int length = t.size();
            if (length == 0) {
                return null;
            }
            return t.copyKeys(new Bundle[length]);
        }
    }

    /**
     * Returns the customized object for the specified {@code Bundle} if the
     * specified bundle is being tracked by this {@code BundleTracker}.
     *
     * @param bundle The {@code Bundle} being tracked.
     * @return The customized object for the specified {@code Bundle} or
     *         {@code null} if the specified {@code Bundle} is not being
     *         tracked.
     */
    public T getObject(Bundle bundle) {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return null;
        }
        synchronized (t) {
            return t.getCustomizedObject(bundle);
        }
    }

    /**
     * Remove a bundle from this {@code BundleTracker}.
     *
     * The specified bundle will be removed from this {@code BundleTracker} . If
     * the specified bundle was being tracked then the
     * {@code BundleTrackerCustomizer.removedBundle} method will be called for
     * that bundle.
     *
     * @param bundle The {@code Bundle} to be removed.
     */
    public void remove(Bundle bundle) {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return;
        }
        t.untrack(bundle, null);
    }

    /**
     * Return the number of bundles being tracked by this {@code BundleTracker}.
     *
     * @return The number of bundles being tracked.
     */
    public int size() {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return 0;
        }
        synchronized (t) {
            return t.size();
        }
    }

    /**
     * Returns the tracking count for this {@code BundleTracker}.
     *
     * The tracking count is initialized to 0 when this {@code BundleTracker} is
     * opened. Every time a bundle is added, modified or removed from this
     * {@code BundleTracker} the tracking count is incremented.
     *
     * <p>
     * The tracking count can be used to determine if this {@code BundleTracker}
     * has added, modified or removed a bundle by comparing a tracking count
     * value previously collected with the current tracking count value. If the
     * value has not changed, then no bundle has been added, modified or removed
     * from this {@code BundleTracker} since the previous tracking count was
     * collected.
     *
     * @return The tracking count for this {@code BundleTracker} or -1 if this
     *         {@code BundleTracker} is not open.
     */
    public int getTrackingCount() {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return -1;
        }
        synchronized (t) {
            return t.getTrackingCount();
        }
    }

    /**
     * Return a {@code Map} with the {@code Bundle}s and customized objects for
     * all bundles being tracked by this {@code BundleTracker}.
     *
     * @return A {@code Map} with the {@code Bundle}s and customized objects for
     *         all services being tracked by this {@code BundleTracker}. If no
     *         bundles are being tracked, then the returned map is empty.
     * @since 1.5
     */
    public Map<Bundle, T> getTracked() {
        Map<Bundle, T> map = new HashMap<Bundle, T>();
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return map;
        }
        synchronized (t) {
            return t.copyEntries(map);
        }
    }

    /**
     * Return if this {@code BundleTracker} is empty.
     *
     * @return {@code true} if this {@code BundleTracker} is not tracking any
     *         bundles.
     * @since 1.5
     */
    public boolean isEmpty() {
        final Tracked t = tracked();
        if (t == null) { /* if BundleTracker is not open */
            return true;
        }
        synchronized (t) {
            return t.isEmpty();
        }
    }

    private class BundleEventHook implements EventHook {
        private final Tracked tracked;

        private BundleEventHook(Tracked tracked) {
            this.tracked = tracked;
        }

        public void event(BundleEvent bundleEvent, Collection bundleContexts) {
            tracked.bundleChanged(bundleEvent);
        }
    }

    /**
     * Inner class which subclasses AbstractTracked. This class is the
     * {@code SynchronousBundleListener} object for the tracker.
     *
     * @ThreadSafe
     * @since 1.4
     */
    private final class Tracked extends AbstractTracked<Bundle, T, BundleEvent> implements SynchronousBundleListener {
        /**
         * Tracked constructor.
         */
        Tracked() {
            super();
        }

        /**
         * {@code BundleListener} method for the {@code BundleTracker}
         * class. This method must NOT be synchronized to avoid deadlock
         * potential.
         *
         * @param event {@code BundleEvent} object from the framework.
         */
        public void bundleChanged(final BundleEvent event) {
            /*
             * Check if we had a delayed call (which could happen when we
             * close).
             */
            if (closed) {
                return;
            }
            final Bundle bundle = event.getBundle();
            final int state = bundle.getState();
            if (DEBUG) {
                System.out.println("BundleTracker.Tracked.bundleChanged[" + state + "]: " + bundle); //$NON-NLS-1$ //$NON-NLS-2$
            }

            if ((state & mask) != 0) {
                track(bundle, event);
                /*
                 * If the customizer throws an unchecked exception, it is safe
                 * to let it propagate
                 */
            } else {
                untrack(bundle, event);
                /*
                 * If the customizer throws an unchecked exception, it is safe
                 * to let it propagate
                 */
            }
        }

        /**
         * Call the specific customizer adding method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Item to be tracked.
         * @param related Action related object.
         * @return Customized object for the tracked item or {@code null}
         *         if the item is not to be tracked.
         */
        T customizerAdding(final Bundle item, final BundleEvent related) {
            return (T) customizer.addingBundle(item, related);
        }

        /**
         * Call the specific customizer modified method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Tracked item.
         * @param related Action related object.
         * @param object  Customized object for the tracked item.
         */
        void customizerModified(final Bundle item, final BundleEvent related,
                                final T object) {
            customizer.modifiedBundle(item, related, object);
        }

        /**
         * Call the specific customizer removed method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Tracked item.
         * @param related Action related object.
         * @param object  Customized object for the tracked item.
         */
        void customizerRemoved(final Bundle item, final BundleEvent related,
                               final T object) {
            customizer.removedBundle(item, related, object);
        }
    }

    /**
     * Abstract class to track items. If a Tracker is reused (closed then reopened),
     * then a new AbstractTracked object is used. This class acts a map of tracked
     * item -> customized object. Subclasses of this class will act as the listener
     * object for the tracker. This class is used to synchronize access to the
     * tracked items. This is not a public class. It is only for use by the
     * implementation of the Tracker class.
     *
     * @param <S> The tracked item. It is the key.
     * @param <T> The value mapped to the tracked item.
     * @param <R> The reason the tracked item is being tracked or untracked.
     * @version $Id: 79452e6c28683021f2bcf11d3689ec75c6b5642f $
     * @ThreadSafe
     * @since 1.4
     */
    private static abstract class AbstractTracked<S, T, R> {
        /* set this to true to compile in debug messages */
        static final boolean DEBUG = false;

        /**
         * Map of tracked items to customized objects.
         *
         * @GuardedBy this
         */
        private final Map<S, T> tracked;

        /**
         * Modification count. This field is initialized to zero and incremented by
         * modified.
         *
         * @GuardedBy this
         */
        private int trackingCount;

        /**
         * List of items in the process of being added. This is used to deal with
         * nesting of events. Since events may be synchronously delivered, events
         * can be nested. For example, when processing the adding of a service and
         * the customizer causes the service to be unregistered, notification to the
         * nested call to untrack that the service was unregistered can be made to
         * the track method.
         * <p/>
         * Since the ArrayList implementation is not synchronized, all access to
         * this list must be protected by the same synchronized object for
         * thread-safety.
         *
         * @GuardedBy this
         */
        private final List<S> adding;

        /**
         * true if the tracked object is closed.
         * <p/>
         * This field is volatile because it is set by one thread and read by
         * another.
         */
        volatile boolean closed;

        /**
         * Initial list of items for the tracker. This is used to correctly process
         * the initial items which could be modified before they are tracked. This
         * is necessary since the initial set of tracked items are not "announced"
         * by events and therefore the event which makes the item untracked could be
         * delivered before we track the item.
         * <p/>
         * An item must not be in both the initial and adding lists at the same
         * time. An item must be moved from the initial list to the adding list
         * "atomically" before we begin tracking it.
         * <p/>
         * Since the LinkedList implementation is not synchronized, all access to
         * this list must be protected by the same synchronized object for
         * thread-safety.
         *
         * @GuardedBy this
         */
        private final LinkedList<S> initial;

        /**
         * AbstractTracked constructor.
         */
        AbstractTracked() {
            tracked = new HashMap<S, T>();
            trackingCount = 0;
            adding = new ArrayList<S>(6);
            initial = new LinkedList<S>();
            closed = false;
        }

        /**
         * Set initial list of items into tracker before events begin to be
         * received.
         * <p/>
         * This method must be called from Tracker's open method while synchronized
         * on this object in the same synchronized block as the add listener call.
         *
         * @param list The initial list of items to be tracked. {@code null}
         *             entries in the list are ignored.
         * @GuardedBy this
         */
        void setInitial(S[] list) {
            if (list == null) {
                return;
            }
            for (S item : list) {
                if (item == null) {
                    continue;
                }
                if (DEBUG) {
                    System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
                }
                initial.add(item);
            }
        }

        /**
         * Track the initial list of items. This is called after events can begin to
         * be received.
         * <p/>
         * This method must be called from Tracker's open method while not
         * synchronized on this object after the add listener call.
         */
        void trackInitial() {
            while (true) {
                S item;
                synchronized (this) {
                    if (closed || (initial.size() == 0)) {
                        /*
                         * if there are no more initial items
                         */
                        return; /* we are done */
                    }
                    /*
                    * move the first item from the initial list to the adding list
                    * within this synchronized block.
                    */
                    item = initial.removeFirst();
                    if (tracked.get(item) != null) {
                        /* if we are already tracking this item */
                        if (DEBUG) {
                            System.out.println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
                        }
                        continue; /* skip this item */
                    }
                    if (adding.contains(item)) {
                        /*
                         * if this item is already in the process of being added.
                         */
                        if (DEBUG) {
                            System.out.println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
                        }
                        continue; /* skip this item */
                    }
                    adding.add(item);
                }
                if (DEBUG) {
                    System.out.println("AbstractTracked.trackInitial: " + item); //$NON-NLS-1$
                }
                trackAdding(item, null); /*
                                          * Begin tracking it. We call trackAdding
									      * since we have already put the item in the
									      * adding list.
									      */
            }
        }

        /**
         * Called by the owning Tracker object when it is closed.
         */
        void close() {
            closed = true;
        }

        /**
         * Begin to track an item.
         *
         * @param item    Item to be tracked.
         * @param related Action related object.
         */
        void track(final S item, final R related) {
            final T object;
            synchronized (this) {
                if (closed) {
                    return;
                }
                object = tracked.get(item);
                if (object == null) { /* we are not tracking the item */
                    if (adding.contains(item)) {
                        /* if this item is already in the process of being added. */
                        if (DEBUG) {
                            System.out
                                    .println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
                        }
                        return;
                    }
                    adding.add(item); /* mark this item is being added */
                } else { /* we are currently tracking this item */
                    if (DEBUG) {
                        System.out
                                .println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
                    }
                    modified(); /* increment modification count */
                }
            }

            if (object == null) { /* we are not tracking the item */
                trackAdding(item, related);
            } else {
                /* Call customizer outside of synchronized region */
                customizerModified(item, related, object);
                /*
                 * If the customizer throws an unchecked exception, it is safe to
                 * let it propagate
                 */
            }
        }

        /**
         * Common logic to add an item to the tracker used by track and
         * trackInitial. The specified item must have been placed in the adding list
         * before calling this method.
         *
         * @param item    Item to be tracked.
         * @param related Action related object.
         */
        private void trackAdding(final S item, final R related) {
            if (DEBUG) {
                System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
            }
            T object = null;
            boolean becameUntracked = false;
            /* Call customizer outside of synchronized region */
            try {
                object = customizerAdding(item, related);
                /*
                 * If the customizer throws an unchecked exception, it will
                 * propagate after the finally
                 */
            } finally {
                synchronized (this) {
                    if (adding.remove(item) && !closed) {
                        /*
                         * if the item was not untracked during the customizer
                         * callback
                         */
                        if (object != null) {
                            tracked.put(item, object);
                            modified(); /* increment modification count */
                            notifyAll(); /* notify any waiters */
                        }
                    } else {
                        becameUntracked = true;
                    }
                }
            }
            /*
             * The item became untracked during the customizer callback.
             */
            if (becameUntracked && (object != null)) {
                if (DEBUG) {
                    System.out.println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
                }
                /* Call customizer outside of synchronized region */
                customizerRemoved(item, related, object);
                /*
                 * If the customizer throws an unchecked exception, it is safe to
                 * let it propagate
                 */
            }
        }

        /**
         * Discontinue tracking the item.
         *
         * @param item    Item to be untracked.
         * @param related Action related object.
         */
        void untrack(final S item, final R related) {
            final T object;
            synchronized (this) {
                if (initial.remove(item)) { /*
										     * if this item is already in the list
										     * of initial references to process
										     */
                    if (DEBUG) {
                        System.out.println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
                    }
                    return; /*
						     * we have removed it from the list and it will not be
						     * processed
						     */
                }

                if (adding.remove(item)) { /*
										    * if the item is in the process of
										    * being added
										    */
                    if (DEBUG) {
                        System.out.println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
                    }
                    return; /*
						     * in case the item is untracked while in the process of
						     * adding
						     */
                }
                object = tracked.remove(item); /*
											    * must remove from tracker before
											    * calling customizer callback
											    */
                if (object == null) { /* are we actually tracking the item */
                    return;
                }
                modified(); /* increment modification count */
            }
            if (DEBUG) {
                System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
            }
            /* Call customizer outside of synchronized region */
            customizerRemoved(item, related, object);
            /*
             * If the customizer throws an unchecked exception, it is safe to let it
             * propagate
             */
        }

        /**
         * Returns the number of tracked items.
         *
         * @return The number of tracked items.
         * @GuardedBy this
         */
        int size() {
            return tracked.size();
        }

        /**
         * Returns if the tracker is empty.
         *
         * @return Whether the tracker is empty.
         * @GuardedBy this
         * @since 1.5
         */
        boolean isEmpty() {
            return tracked.isEmpty();
        }

        /**
         * Return the customized object for the specified item
         *
         * @param item The item to lookup in the map
         * @return The customized object for the specified item.
         * @GuardedBy this
         */
        T getCustomizedObject(final S item) {
            return tracked.get(item);
        }

        /**
         * Copy the tracked items into an array.
         *
         * @param list An array to contain the tracked items.
         * @return The specified list if it is large enough to hold the tracked
         *         items or a new array large enough to hold the tracked items.
         * @GuardedBy this
         */
        S[] copyKeys(final S[] list) {
            return tracked.keySet().toArray(list);
        }

        /**
         * Increment the modification count. If this method is overridden, the
         * overriding method MUST call this method to increment the tracking count.
         *
         * @GuardedBy this
         */
        void modified() {
            trackingCount++;
        }

        /**
         * Returns the tracking count for this {@code ServiceTracker} object.
         * <p/>
         * The tracking count is initialized to 0 when this object is opened. Every
         * time an item is added, modified or removed from this object the tracking
         * count is incremented.
         *
         * @return The tracking count for this object.
         * @GuardedBy this
         */
        int getTrackingCount() {
            return trackingCount;
        }

        /**
         * Copy the tracked items and associated values into the specified map.
         *
         * @param <M> Type of {@code Map} to hold the tracked items and
         *            associated values.
         * @param map The map into which to copy the tracked items and associated
         *            values. This map must not be a user provided map so that user code
         *            is not executed while synchronized on this.
         * @return The specified map.
         * @GuardedBy this
         * @since 1.5
         */
        <M extends Map<? super S, ? super T>> M copyEntries(final M map) {
            map.putAll(tracked);
            return map;
        }

        /**
         * Call the specific customizer adding method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Item to be tracked.
         * @param related Action related object.
         * @return Customized object for the tracked item or {@code null} if
         *         the item is not to be tracked.
         */
        abstract T customizerAdding(final S item, final R related);

        /**
         * Call the specific customizer modified method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Tracked item.
         * @param related Action related object.
         * @param object  Customized object for the tracked item.
         */
        abstract void customizerModified(final S item, final R related,
                                         final T object);

        /**
         * Call the specific customizer removed method. This method must not be
         * called while synchronized on this object.
         *
         * @param item    Tracked item.
         * @param related Action related object.
         * @param object  Customized object for the tracked item.
         */
        abstract void customizerRemoved(final S item, final R related,
                                        final T object);
    }

}
