blob: 9aa0bd47e7bfecab36a0f0d89d1bb695efcbd779 [file] [log] [blame]
Richard S. Hall2532cf82010-03-24 09:51:11 +00001/*
Carsten Ziegeler3314f912014-07-30 07:22:32 +00002 * Copyright (c) OSGi Alliance (2007, 2013). All Rights Reserved.
Richard S. Hall2532cf82010-03-24 09:51:11 +00003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package org.osgi.util.tracker;
18
19import java.util.ArrayList;
20import java.util.HashMap;
21import java.util.LinkedList;
22import java.util.List;
23import java.util.Map;
24
25/**
26 * Abstract class to track items. If a Tracker is reused (closed then reopened),
27 * then a new AbstractTracked object is used. This class acts a map of tracked
28 * item -> customized object. Subclasses of this class will act as the listener
29 * object for the tracker. This class is used to synchronize access to the
30 * tracked items. This is not a public class. It is only for use by the
31 * implementation of the Tracker class.
32 *
Richard S. Halld0dca9b2011-05-18 14:52:16 +000033 * @param <S> The tracked item. It is the key.
34 * @param <T> The value mapped to the tracked item.
35 * @param <R> The reason the tracked item is being tracked or untracked.
Richard S. Hall2532cf82010-03-24 09:51:11 +000036 * @ThreadSafe
Carsten Ziegeler3314f912014-07-30 07:22:32 +000037 * @author $Id: 5988d793936c25139421a95bad2d3cd96e2ab355 $
Richard S. Hall2532cf82010-03-24 09:51:11 +000038 * @since 1.4
39 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +000040abstract class AbstractTracked<S, T, R> {
Richard S. Hall2532cf82010-03-24 09:51:11 +000041 /* set this to true to compile in debug messages */
42 static final boolean DEBUG = false;
43
44 /**
45 * Map of tracked items to customized objects.
46 *
47 * @GuardedBy this
48 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +000049 private final Map<S, T> tracked;
Richard S. Hall2532cf82010-03-24 09:51:11 +000050
51 /**
52 * Modification count. This field is initialized to zero and incremented by
53 * modified.
54 *
55 * @GuardedBy this
56 */
57 private int trackingCount;
58
59 /**
60 * List of items in the process of being added. This is used to deal with
61 * nesting of events. Since events may be synchronously delivered, events
62 * can be nested. For example, when processing the adding of a service and
63 * the customizer causes the service to be unregistered, notification to the
64 * nested call to untrack that the service was unregistered can be made to
65 * the track method.
66 *
67 * Since the ArrayList implementation is not synchronized, all access to
68 * this list must be protected by the same synchronized object for
69 * thread-safety.
70 *
71 * @GuardedBy this
72 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +000073 private final List<S> adding;
Richard S. Hall2532cf82010-03-24 09:51:11 +000074
75 /**
76 * true if the tracked object is closed.
77 *
78 * This field is volatile because it is set by one thread and read by
79 * another.
80 */
81 volatile boolean closed;
82
83 /**
84 * Initial list of items for the tracker. This is used to correctly process
85 * the initial items which could be modified before they are tracked. This
86 * is necessary since the initial set of tracked items are not "announced"
87 * by events and therefore the event which makes the item untracked could be
88 * delivered before we track the item.
89 *
90 * An item must not be in both the initial and adding lists at the same
91 * time. An item must be moved from the initial list to the adding list
92 * "atomically" before we begin tracking it.
93 *
94 * Since the LinkedList implementation is not synchronized, all access to
95 * this list must be protected by the same synchronized object for
96 * thread-safety.
97 *
98 * @GuardedBy this
99 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000100 private final LinkedList<S> initial;
Richard S. Hall2532cf82010-03-24 09:51:11 +0000101
102 /**
103 * AbstractTracked constructor.
104 */
105 AbstractTracked() {
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000106 tracked = new HashMap<S, T>();
Richard S. Hall2532cf82010-03-24 09:51:11 +0000107 trackingCount = 0;
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000108 adding = new ArrayList<S>(6);
109 initial = new LinkedList<S>();
Richard S. Hall2532cf82010-03-24 09:51:11 +0000110 closed = false;
111 }
112
113 /**
114 * Set initial list of items into tracker before events begin to be
115 * received.
116 *
117 * This method must be called from Tracker's open method while synchronized
118 * on this object in the same synchronized block as the add listener call.
119 *
Richard S. Halldfd78a42012-05-11 20:19:02 +0000120 * @param list The initial list of items to be tracked. {@code null} entries
121 * in the list are ignored.
Richard S. Hall2532cf82010-03-24 09:51:11 +0000122 * @GuardedBy this
123 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000124 void setInitial(S[] list) {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000125 if (list == null) {
126 return;
127 }
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000128 for (S item : list) {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000129 if (item == null) {
130 continue;
131 }
132 if (DEBUG) {
133 System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
134 }
135 initial.add(item);
136 }
137 }
138
139 /**
140 * Track the initial list of items. This is called after events can begin to
141 * be received.
142 *
143 * This method must be called from Tracker's open method while not
144 * synchronized on this object after the add listener call.
145 *
146 */
147 void trackInitial() {
148 while (true) {
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000149 S item;
Richard S. Hall2532cf82010-03-24 09:51:11 +0000150 synchronized (this) {
151 if (closed || (initial.size() == 0)) {
152 /*
153 * if there are no more initial items
154 */
155 return; /* we are done */
156 }
157 /*
158 * move the first item from the initial list to the adding list
159 * within this synchronized block.
160 */
161 item = initial.removeFirst();
162 if (tracked.get(item) != null) {
163 /* if we are already tracking this item */
164 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000165 System.out.println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000166 }
167 continue; /* skip this item */
168 }
169 if (adding.contains(item)) {
170 /*
171 * if this item is already in the process of being added.
172 */
173 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000174 System.out.println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000175 }
176 continue; /* skip this item */
177 }
178 adding.add(item);
179 }
180 if (DEBUG) {
181 System.out.println("AbstractTracked.trackInitial: " + item); //$NON-NLS-1$
182 }
183 trackAdding(item, null); /*
184 * Begin tracking it. We call trackAdding
185 * since we have already put the item in the
186 * adding list.
187 */
188 }
189 }
190
191 /**
192 * Called by the owning Tracker object when it is closed.
193 */
194 void close() {
195 closed = true;
196 }
197
198 /**
199 * Begin to track an item.
200 *
201 * @param item Item to be tracked.
202 * @param related Action related object.
203 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000204 void track(final S item, final R related) {
205 final T object;
Richard S. Hall2532cf82010-03-24 09:51:11 +0000206 synchronized (this) {
207 if (closed) {
208 return;
209 }
210 object = tracked.get(item);
211 if (object == null) { /* we are not tracking the item */
212 if (adding.contains(item)) {
213 /* if this item is already in the process of being added. */
214 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000215 System.out.println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000216 }
217 return;
218 }
219 adding.add(item); /* mark this item is being added */
Richard S. Halldfd78a42012-05-11 20:19:02 +0000220 } else { /* we are currently tracking this item */
Richard S. Hall2532cf82010-03-24 09:51:11 +0000221 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000222 System.out.println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000223 }
224 modified(); /* increment modification count */
225 }
226 }
227
228 if (object == null) { /* we are not tracking the item */
229 trackAdding(item, related);
Richard S. Halldfd78a42012-05-11 20:19:02 +0000230 } else {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000231 /* Call customizer outside of synchronized region */
232 customizerModified(item, related, object);
233 /*
234 * If the customizer throws an unchecked exception, it is safe to
235 * let it propagate
236 */
237 }
238 }
239
240 /**
241 * Common logic to add an item to the tracker used by track and
242 * trackInitial. The specified item must have been placed in the adding list
243 * before calling this method.
244 *
245 * @param item Item to be tracked.
246 * @param related Action related object.
247 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000248 private void trackAdding(final S item, final R related) {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000249 if (DEBUG) {
250 System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
251 }
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000252 T object = null;
Richard S. Hall2532cf82010-03-24 09:51:11 +0000253 boolean becameUntracked = false;
254 /* Call customizer outside of synchronized region */
255 try {
256 object = customizerAdding(item, related);
257 /*
258 * If the customizer throws an unchecked exception, it will
259 * propagate after the finally
260 */
Richard S. Halldfd78a42012-05-11 20:19:02 +0000261 } finally {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000262 synchronized (this) {
263 if (adding.remove(item) && !closed) {
264 /*
265 * if the item was not untracked during the customizer
266 * callback
267 */
268 if (object != null) {
269 tracked.put(item, object);
270 modified(); /* increment modification count */
271 notifyAll(); /* notify any waiters */
272 }
Richard S. Halldfd78a42012-05-11 20:19:02 +0000273 } else {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000274 becameUntracked = true;
275 }
276 }
277 }
278 /*
279 * The item became untracked during the customizer callback.
280 */
281 if (becameUntracked && (object != null)) {
282 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000283 System.out.println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000284 }
285 /* Call customizer outside of synchronized region */
286 customizerRemoved(item, related, object);
287 /*
288 * If the customizer throws an unchecked exception, it is safe to
289 * let it propagate
290 */
291 }
292 }
293
294 /**
295 * Discontinue tracking the item.
296 *
297 * @param item Item to be untracked.
298 * @param related Action related object.
299 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000300 void untrack(final S item, final R related) {
301 final T object;
Richard S. Hall2532cf82010-03-24 09:51:11 +0000302 synchronized (this) {
303 if (initial.remove(item)) { /*
304 * if this item is already in the list
305 * of initial references to process
306 */
307 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000308 System.out.println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000309 }
310 return; /*
311 * we have removed it from the list and it will not be
312 * processed
313 */
314 }
315
316 if (adding.remove(item)) { /*
317 * if the item is in the process of
318 * being added
319 */
320 if (DEBUG) {
Richard S. Halldfd78a42012-05-11 20:19:02 +0000321 System.out.println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
Richard S. Hall2532cf82010-03-24 09:51:11 +0000322 }
323 return; /*
324 * in case the item is untracked while in the process of
325 * adding
326 */
327 }
328 object = tracked.remove(item); /*
329 * must remove from tracker before
330 * calling customizer callback
331 */
332 if (object == null) { /* are we actually tracking the item */
333 return;
334 }
335 modified(); /* increment modification count */
336 }
337 if (DEBUG) {
338 System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
339 }
340 /* Call customizer outside of synchronized region */
341 customizerRemoved(item, related, object);
342 /*
343 * If the customizer throws an unchecked exception, it is safe to let it
344 * propagate
345 */
346 }
347
348 /**
349 * Returns the number of tracked items.
350 *
351 * @return The number of tracked items.
352 *
353 * @GuardedBy this
354 */
355 int size() {
356 return tracked.size();
357 }
358
359 /**
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000360 * Returns if the tracker is empty.
361 *
362 * @return Whether the tracker is empty.
363 *
364 * @GuardedBy this
365 * @since 1.5
366 */
367 boolean isEmpty() {
368 return tracked.isEmpty();
369 }
370
371 /**
Richard S. Hall2532cf82010-03-24 09:51:11 +0000372 * Return the customized object for the specified item
373 *
374 * @param item The item to lookup in the map
375 * @return The customized object for the specified item.
376 *
377 * @GuardedBy this
378 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000379 T getCustomizedObject(final S item) {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000380 return tracked.get(item);
381 }
382
383 /**
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000384 * Copy the tracked items into an array.
Richard S. Hall2532cf82010-03-24 09:51:11 +0000385 *
386 * @param list An array to contain the tracked items.
387 * @return The specified list if it is large enough to hold the tracked
388 * items or a new array large enough to hold the tracked items.
389 * @GuardedBy this
390 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000391 S[] copyKeys(final S[] list) {
Richard S. Hall2532cf82010-03-24 09:51:11 +0000392 return tracked.keySet().toArray(list);
393 }
394
395 /**
396 * Increment the modification count. If this method is overridden, the
397 * overriding method MUST call this method to increment the tracking count.
398 *
399 * @GuardedBy this
400 */
401 void modified() {
402 trackingCount++;
403 }
404
405 /**
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000406 * Returns the tracking count for this {@code ServiceTracker} object.
Richard S. Hall2532cf82010-03-24 09:51:11 +0000407 *
408 * The tracking count is initialized to 0 when this object is opened. Every
409 * time an item is added, modified or removed from this object the tracking
410 * count is incremented.
411 *
412 * @GuardedBy this
413 * @return The tracking count for this object.
414 */
415 int getTrackingCount() {
416 return trackingCount;
417 }
418
419 /**
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000420 * Copy the tracked items and associated values into the specified map.
421 *
Richard S. Halldfd78a42012-05-11 20:19:02 +0000422 * @param <M> Type of {@code Map} to hold the tracked items and associated
423 * values.
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000424 * @param map The map into which to copy the tracked items and associated
425 * values. This map must not be a user provided map so that user code
426 * is not executed while synchronized on this.
427 * @return The specified map.
428 * @GuardedBy this
429 * @since 1.5
430 */
Richard S. Halldfd78a42012-05-11 20:19:02 +0000431 <M extends Map<? super S, ? super T>> M copyEntries(final M map) {
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000432 map.putAll(tracked);
433 return map;
434 }
435
436 /**
Richard S. Hall2532cf82010-03-24 09:51:11 +0000437 * Call the specific customizer adding method. This method must not be
438 * called while synchronized on this object.
439 *
440 * @param item Item to be tracked.
441 * @param related Action related object.
Richard S. Halldfd78a42012-05-11 20:19:02 +0000442 * @return Customized object for the tracked item or {@code null} if the
443 * item is not to be tracked.
Richard S. Hall2532cf82010-03-24 09:51:11 +0000444 */
Richard S. Halld0dca9b2011-05-18 14:52:16 +0000445 abstract T customizerAdding(final S item, final R related);
Richard S. Hall2532cf82010-03-24 09:51:11 +0000446
447 /**
448 * Call the specific customizer modified method. This method must not be
449 * called while synchronized on this object.
450 *
451 * @param item Tracked item.
452 * @param related Action related object.
453 * @param object Customized object for the tracked item.
454 */
Richard S. Halldfd78a42012-05-11 20:19:02 +0000455 abstract void customizerModified(final S item, final R related, final T object);
Richard S. Hall2532cf82010-03-24 09:51:11 +0000456
457 /**
458 * Call the specific customizer removed method. This method must not be
459 * called while synchronized on this object.
460 *
461 * @param item Tracked item.
462 * @param related Action related object.
463 * @param object Customized object for the tracked item.
464 */
Richard S. Halldfd78a42012-05-11 20:19:02 +0000465 abstract void customizerRemoved(final S item, final R related, final T object);
Richard S. Hall2532cf82010-03-24 09:51:11 +0000466}