blob: a312b51662210d7cb71900e00dc388cba6676fc7 [file] [log] [blame]
Marcel Offermansfaaed472010-09-08 10:07:32 +00001package org.apache.felix.dm.tracker;
Marcel Offermansc854d1a2009-11-24 15:41:41 +00002/*
3 * Copyright (c) OSGi Alliance (2007, 2008). All Rights Reserved.
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18import java.util.ArrayList;
19import java.util.HashMap;
20import java.util.LinkedList;
21import java.util.List;
22import java.util.Map;
23
24/**
25 * Abstract class to track items. If a Tracker is reused (closed then reopened),
26 * then a new AbstractTracked object is used. This class acts a map of tracked
27 * item -> customized object. Subclasses of this class will act as the listener
28 * object for the tracker. This class is used to synchronize access to the
29 * tracked items. This is not a public class. It is only for use by the
30 * implementation of the Tracker class.
31 *
32 * @ThreadSafe
33 * @version $Revision: 5871 $
34 * @since 1.4
35 */
36abstract class AbstractTracked {
37 /* set this to true to compile in debug messages */
38 private static final boolean DEBUG = false;
39
40 /**
41 * Map of tracked items to customized objects.
42 *
43 * @GuardedBy this
44 */
45 private final Map tracked;
46
47 /**
48 * Modification count. This field is initialized to zero and incremented by
49 * modified.
50 *
51 * @GuardedBy this
52 */
53 private int trackingCount;
54
55 /**
56 * List of items in the process of being added. This is used to deal with
57 * nesting of events. Since events may be synchronously delivered, events
58 * can be nested. For example, when processing the adding of a service and
59 * the customizer causes the service to be unregistered, notification to the
60 * nested call to untrack that the service was unregistered can be made to
61 * the track method.
62 *
63 * Since the ArrayList implementation is not synchronized, all access to
64 * this list must be protected by the same synchronized object for
65 * thread-safety.
66 *
67 * @GuardedBy this
68 */
69 private final List adding;
70
71 /**
72 * true if the tracked object is closed.
73 *
74 * This field is volatile because it is set by one thread and read by
75 * another.
76 */
77 volatile boolean closed;
78
79 /**
80 * Initial list of items for the tracker. This is used to correctly process
81 * the initial items which could be modified before they are tracked. This
82 * is necessary since the initial set of tracked items are not "announced"
83 * by events and therefore the event which makes the item untracked could be
84 * delivered before we track the item.
85 *
86 * An item must not be in both the initial and adding lists at the same
87 * time. An item must be moved from the initial list to the adding list
88 * "atomically" before we begin tracking it.
89 *
90 * Since the LinkedList implementation is not synchronized, all access to
91 * this list must be protected by the same synchronized object for
92 * thread-safety.
93 *
94 * @GuardedBy this
95 */
96 private final LinkedList initial;
97
98 /**
99 * AbstractTracked constructor.
100 */
101 AbstractTracked() {
102 tracked = new HashMap();
103 trackingCount = 0;
104 adding = new ArrayList(6);
105 initial = new LinkedList();
106 closed = false;
107 }
108
109 /**
110 * Set initial list of items into tracker before events begin to be
111 * received.
112 *
113 * This method must be called from Tracker's open method while synchronized
114 * on this object in the same synchronized block as the add listener call.
115 *
116 * @param list The initial list of items to be tracked. <code>null</code>
117 * entries in the list are ignored.
118 * @GuardedBy this
119 */
120 void setInitial(Object[] list) {
121 if (list == null) {
122 return;
123 }
124 int size = list.length;
125 for (int i = 0; i < size; i++) {
126 Object item = list[i];
127 if (item == null) {
128 continue;
129 }
130 if (DEBUG) {
131 System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
132 }
133 initial.add(item);
134 }
135 }
136
137 /**
138 * Track the initial list of items. This is called after events can begin to
139 * be received.
140 *
141 * This method must be called from Tracker's open method while not
142 * synchronized on this object after the add listener call.
143 *
144 */
145 void trackInitial() {
146 while (true) {
147 Object item;
148 synchronized (this) {
149 if (closed || (initial.size() == 0)) {
150 /*
151 * if there are no more initial items
152 */
153 return; /* we are done */
154 }
155 /*
156 * move the first item from the initial list to the adding list
157 * within this synchronized block.
158 */
159 item = initial.removeFirst();
160 if (tracked.get(item) != null) {
161 /* if we are already tracking this item */
162 if (DEBUG) {
163 System.out
164 .println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
165 }
166 continue; /* skip this item */
167 }
168 if (adding.contains(item)) {
169 /*
170 * if this item is already in the process of being added.
171 */
172 if (DEBUG) {
173 System.out
174 .println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
175 }
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 */
204 void track(final Object item, final Object related) {
205 final Object object;
206 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) {
215 System.out
216 .println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
217 }
218 return;
219 }
220 adding.add(item); /* mark this item is being added */
221 }
222 else { /* we are currently tracking this item */
223 if (DEBUG) {
224 System.out
225 .println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
226 }
227 modified(); /* increment modification count */
228 }
229 }
230
231 if (object == null) { /* we are not tracking the item */
232 trackAdding(item, related);
233 }
234 else {
235 /* Call customizer outside of synchronized region */
236 customizerModified(item, related, object);
237 /*
238 * If the customizer throws an unchecked exception, it is safe to
239 * let it propagate
240 */
241 }
242 }
243
244 /**
245 * Common logic to add an item to the tracker used by track and
246 * trackInitial. The specified item must have been placed in the adding list
247 * before calling this method.
248 *
249 * @param item Item to be tracked.
250 * @param related Action related object.
251 */
252 private void trackAdding(final Object item, final Object related) {
253 if (DEBUG) {
254 System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
255 }
256 Object object = null;
257 boolean becameUntracked = false;
258 /* Call customizer outside of synchronized region */
259 try {
260 object = customizerAdding(item, related);
261 /*
262 * If the customizer throws an unchecked exception, it will
263 * propagate after the finally
264 */
265 }
266 finally {
267 boolean needToCallback = false;
268 synchronized (this) {
269 if (adding.remove(item) && !closed) {
270 /*
271 * if the item was not untracked during the customizer
272 * callback
273 */
274 if (object != null) {
275 tracked.put(item, object);
276 modified(); /* increment modification count */
277 notifyAll(); /* notify any waiters */
278 needToCallback = true; /* marrs: invoke added callback */
279 }
280 }
281 else {
282 becameUntracked = true;
283 }
284 }
285 if (needToCallback) {
286 customizerAdded(item, related, object);
287 }
288 }
289 /*
290 * The item became untracked during the customizer callback.
291 */
292 if (becameUntracked && (object != null)) {
293 if (DEBUG) {
294 System.out
295 .println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
296 }
297 /* Call customizer outside of synchronized region */
298 customizerRemoved(item, related, object);
299 /*
300 * If the customizer throws an unchecked exception, it is safe to
301 * let it propagate
302 */
303 }
304 }
305
306 /**
307 * Discontinue tracking the item.
308 *
309 * @param item Item to be untracked.
310 * @param related Action related object.
311 */
312 void untrack(final Object item, final Object related) {
313 final Object object;
314 synchronized (this) {
315 if (initial.remove(item)) { /*
316 * if this item is already in the list
317 * of initial references to process
318 */
319 if (DEBUG) {
320 System.out
321 .println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
322 }
323 return; /*
324 * we have removed it from the list and it will not be
325 * processed
326 */
327 }
328
329 if (adding.remove(item)) { /*
330 * if the item is in the process of
331 * being added
332 */
333 if (DEBUG) {
334 System.out
335 .println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
336 }
337 return; /*
338 * in case the item is untracked while in the process of
339 * adding
340 */
341 }
342 object = tracked.remove(item); /*
343 * must remove from tracker before
344 * calling customizer callback
345 */
346 if (object == null) { /* are we actually tracking the item */
347 return;
348 }
349 modified(); /* increment modification count */
350 }
351 if (DEBUG) {
352 System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
353 }
354 /* Call customizer outside of synchronized region */
355 customizerRemoved(item, related, object);
356 /*
357 * If the customizer throws an unchecked exception, it is safe to let it
358 * propagate
359 */
360 }
361
362 /**
363 * Returns the number of tracked items.
364 *
365 * @return The number of tracked items.
366 *
367 * @GuardedBy this
368 */
369 int size() {
370 return tracked.size();
371 }
372
373 /**
374 * Return the customized object for the specified item
375 *
376 * @param item The item to lookup in the map
377 * @return The customized object for the specified item.
378 *
379 * @GuardedBy this
380 */
381 Object getCustomizedObject(final Object item) {
382 return tracked.get(item);
383 }
384
385 /**
386 * Return the list of tracked items.
387 *
388 * @param list An array to contain the tracked items.
389 * @return The specified list if it is large enough to hold the tracked
390 * items or a new array large enough to hold the tracked items.
391 * @GuardedBy this
392 */
393 Object[] getTracked(final Object[] list) {
394 return tracked.keySet().toArray(list);
395 }
396
397 /**
398 * Increment the modification count. If this method is overridden, the
399 * overriding method MUST call this method to increment the tracking count.
400 *
401 * @GuardedBy this
402 */
403 void modified() {
404 trackingCount++;
405 }
406
407 /**
408 * Returns the tracking count for this <code>ServiceTracker</code> object.
409 *
410 * The tracking count is initialized to 0 when this object is opened. Every
411 * time an item is added, modified or removed from this object the tracking
412 * count is incremented.
413 *
414 * @GuardedBy this
415 * @return The tracking count for this object.
416 */
417 int getTrackingCount() {
418 return trackingCount;
419 }
420
421 /**
422 * Call the specific customizer adding method. This method must not be
423 * called while synchronized on this object.
424 *
425 * @param item Item to be tracked.
426 * @param related Action related object.
427 * @return Customized object for the tracked item or <code>null</code> if
428 * the item is not to be tracked.
429 */
430 abstract Object customizerAdding(final Object item, final Object related);
431
432 /** marrs: Call the specific customizer added method. */
433 abstract void customizerAdded(final Object item, final Object related, final Object object);
434
435 /**
436 * Call the specific customizer modified method. This method must not be
437 * called while synchronized on this object.
438 *
439 * @param item Tracked item.
440 * @param related Action related object.
441 * @param object Customized object for the tracked item.
442 */
443 abstract void customizerModified(final Object item, final Object related,
444 final Object object);
445
446 /**
447 * Call the specific customizer removed method. This method must not be
448 * called while synchronized on this object.
449 *
450 * @param item Tracked item.
451 * @param related Action related object.
452 * @param object Customized object for the tracked item.
453 */
454 abstract void customizerRemoved(final Object item, final Object related,
455 final Object object);
456}