| package org.apache.felix.dm.tracker; |
| /* |
| * Copyright (c) OSGi Alliance (2007, 2008). All Rights Reserved. |
| * |
| * Licensed 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. |
| */ |
| |
| import java.util.ArrayList; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| |
| /** |
| * 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. |
| * |
| * @ThreadSafe |
| * @version $Revision: 5871 $ |
| * @since 1.4 |
| */ |
| abstract class AbstractTracked { |
| /* set this to true to compile in debug messages */ |
| private static final boolean DEBUG = false; |
| |
| /** |
| * Ordered Map of tracked items to customized objects. An ordered map is used to |
| * provide a predictable order in the getTracked() method. |
| * |
| * @GuardedBy this |
| */ |
| private LinkedHashMap 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. |
| * |
| * 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 adding; |
| |
| /** |
| * true if the tracked object is closed. |
| * |
| * 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. |
| * |
| * 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. |
| * |
| * 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 initial; |
| |
| /** |
| * AbstractTracked constructor. |
| */ |
| AbstractTracked() { |
| this.tracked = new LinkedHashMap(); |
| trackingCount = 0; |
| adding = new ArrayList(6); |
| initial = new LinkedList(); |
| closed = false; |
| } |
| |
| void setTracked(LinkedHashMap map) { |
| this.tracked = map; |
| } |
| |
| /** |
| * Set initial list of items into tracker before events begin to be |
| * received. |
| * |
| * 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</code> |
| * entries in the list are ignored. |
| * @GuardedBy this |
| */ |
| void setInitial(Object[] list) { |
| if (list == null) { |
| return; |
| } |
| int size = list.length; |
| for (int i = 0; i < size; i++) { |
| Object item = list[i]; |
| 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. |
| * |
| * 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) { |
| Object 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 Object item, final Object related) { |
| final Object 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 Object item, final Object related) { |
| if (DEBUG) { |
| System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$ |
| } |
| Object 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 { |
| boolean needToCallback = false; |
| 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 */ |
| needToCallback = true; /* marrs: invoke added callback */ |
| } |
| } |
| else { |
| becameUntracked = true; |
| } |
| } |
| if (needToCallback) { |
| customizerAdded(item, related, object); |
| } |
| } |
| /* |
| * 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 Object item, final Object related) { |
| final Object 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(); |
| } |
| |
| /** |
| * 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 |
| */ |
| Object getCustomizedObject(final Object item) { |
| return tracked.get(item); |
| } |
| |
| /** |
| * Return the list of tracked items. |
| * |
| * @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 |
| */ |
| Object[] getTracked(final Object[] 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</code> object. |
| * |
| * 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. |
| * |
| * @GuardedBy this |
| * @return The tracking count for this object. |
| */ |
| int getTrackingCount() { |
| return trackingCount; |
| } |
| |
| /** |
| * 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</code> if |
| * the item is not to be tracked. |
| */ |
| abstract Object customizerAdding(final Object item, final Object related); |
| |
| /** marrs: Call the specific customizer added method. */ |
| abstract void customizerAdded(final Object item, final Object related, final Object object); |
| |
| /** |
| * 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 Object item, final Object related, |
| final Object 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 Object item, final Object related, |
| final Object object); |
| } |