blob: 512f66bf23b5886b48bbe5ee4ce1222e86ddd162 [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;
Xander Uiterlinden0aeb7532013-10-25 11:13:16 +000020import java.util.LinkedHashMap;
Marcel Offermansc854d1a2009-11-24 15:41:41 +000021import java.util.LinkedList;
22import java.util.List;
Marcel Offermansc854d1a2009-11-24 15:41:41 +000023
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 /**
Xander Uiterlinden0aeb7532013-10-25 11:13:16 +000041 * Ordered Map of tracked items to customized objects. An ordered map is used to
42 * provide a predictable order in the getTracked() method.
Marcel Offermansc854d1a2009-11-24 15:41:41 +000043 *
44 * @GuardedBy this
45 */
Xander Uiterlinden0aeb7532013-10-25 11:13:16 +000046 private LinkedHashMap tracked;
Marcel Offermansc854d1a2009-11-24 15:41:41 +000047
48 /**
49 * Modification count. This field is initialized to zero and incremented by
50 * modified.
51 *
52 * @GuardedBy this
53 */
54 private int trackingCount;
55
56 /**
57 * List of items in the process of being added. This is used to deal with
58 * nesting of events. Since events may be synchronously delivered, events
59 * can be nested. For example, when processing the adding of a service and
60 * the customizer causes the service to be unregistered, notification to the
61 * nested call to untrack that the service was unregistered can be made to
62 * the track method.
63 *
64 * Since the ArrayList implementation is not synchronized, all access to
65 * this list must be protected by the same synchronized object for
66 * thread-safety.
67 *
68 * @GuardedBy this
69 */
70 private final List adding;
71
72 /**
73 * true if the tracked object is closed.
74 *
75 * This field is volatile because it is set by one thread and read by
76 * another.
77 */
78 volatile boolean closed;
79
80 /**
81 * Initial list of items for the tracker. This is used to correctly process
82 * the initial items which could be modified before they are tracked. This
83 * is necessary since the initial set of tracked items are not "announced"
84 * by events and therefore the event which makes the item untracked could be
85 * delivered before we track the item.
86 *
87 * An item must not be in both the initial and adding lists at the same
88 * time. An item must be moved from the initial list to the adding list
89 * "atomically" before we begin tracking it.
90 *
91 * Since the LinkedList implementation is not synchronized, all access to
92 * this list must be protected by the same synchronized object for
93 * thread-safety.
94 *
95 * @GuardedBy this
96 */
97 private final LinkedList initial;
98
99 /**
100 * AbstractTracked constructor.
101 */
102 AbstractTracked() {
Xander Uiterlinden0aeb7532013-10-25 11:13:16 +0000103 this.tracked = new LinkedHashMap();
Marcel Offermans227dd712011-04-19 07:14:22 +0000104 trackingCount = 0;
105 adding = new ArrayList(6);
106 initial = new LinkedList();
107 closed = false;
108 }
109
Xander Uiterlinden0aeb7532013-10-25 11:13:16 +0000110 void setTracked(LinkedHashMap map) {
Marcel Offermans227dd712011-04-19 07:14:22 +0000111 this.tracked = map;
Marcel Offermansc854d1a2009-11-24 15:41:41 +0000112 }
113
114 /**
115 * Set initial list of items into tracker before events begin to be
116 * received.
117 *
118 * This method must be called from Tracker's open method while synchronized
119 * on this object in the same synchronized block as the add listener call.
120 *
121 * @param list The initial list of items to be tracked. <code>null</code>
122 * entries in the list are ignored.
123 * @GuardedBy this
124 */
125 void setInitial(Object[] list) {
126 if (list == null) {
127 return;
128 }
129 int size = list.length;
130 for (int i = 0; i < size; i++) {
131 Object item = list[i];
132 if (item == null) {
133 continue;
134 }
135 if (DEBUG) {
136 System.out.println("AbstractTracked.setInitial: " + item); //$NON-NLS-1$
137 }
138 initial.add(item);
139 }
140 }
141
142 /**
143 * Track the initial list of items. This is called after events can begin to
144 * be received.
145 *
146 * This method must be called from Tracker's open method while not
147 * synchronized on this object after the add listener call.
148 *
149 */
150 void trackInitial() {
151 while (true) {
152 Object item;
153 synchronized (this) {
154 if (closed || (initial.size() == 0)) {
155 /*
156 * if there are no more initial items
157 */
158 return; /* we are done */
159 }
160 /*
161 * move the first item from the initial list to the adding list
162 * within this synchronized block.
163 */
164 item = initial.removeFirst();
165 if (tracked.get(item) != null) {
166 /* if we are already tracking this item */
167 if (DEBUG) {
168 System.out
169 .println("AbstractTracked.trackInitial[already tracked]: " + item); //$NON-NLS-1$
170 }
171 continue; /* skip this item */
172 }
173 if (adding.contains(item)) {
174 /*
175 * if this item is already in the process of being added.
176 */
177 if (DEBUG) {
178 System.out
179 .println("AbstractTracked.trackInitial[already adding]: " + item); //$NON-NLS-1$
180 }
181 continue; /* skip this item */
182 }
183 adding.add(item);
184 }
185 if (DEBUG) {
186 System.out.println("AbstractTracked.trackInitial: " + item); //$NON-NLS-1$
187 }
188 trackAdding(item, null); /*
189 * Begin tracking it. We call trackAdding
190 * since we have already put the item in the
191 * adding list.
192 */
193 }
194 }
195
196 /**
197 * Called by the owning Tracker object when it is closed.
198 */
199 void close() {
200 closed = true;
201 }
202
203 /**
204 * Begin to track an item.
205 *
206 * @param item Item to be tracked.
207 * @param related Action related object.
208 */
209 void track(final Object item, final Object related) {
210 final Object object;
211 synchronized (this) {
212 if (closed) {
213 return;
214 }
215 object = tracked.get(item);
216 if (object == null) { /* we are not tracking the item */
217 if (adding.contains(item)) {
218 /* if this item is already in the process of being added. */
219 if (DEBUG) {
220 System.out
221 .println("AbstractTracked.track[already adding]: " + item); //$NON-NLS-1$
222 }
223 return;
224 }
225 adding.add(item); /* mark this item is being added */
226 }
227 else { /* we are currently tracking this item */
228 if (DEBUG) {
229 System.out
230 .println("AbstractTracked.track[modified]: " + item); //$NON-NLS-1$
231 }
232 modified(); /* increment modification count */
233 }
234 }
235
236 if (object == null) { /* we are not tracking the item */
237 trackAdding(item, related);
238 }
239 else {
240 /* Call customizer outside of synchronized region */
241 customizerModified(item, related, object);
242 /*
243 * If the customizer throws an unchecked exception, it is safe to
244 * let it propagate
245 */
246 }
247 }
248
249 /**
250 * Common logic to add an item to the tracker used by track and
251 * trackInitial. The specified item must have been placed in the adding list
252 * before calling this method.
253 *
254 * @param item Item to be tracked.
255 * @param related Action related object.
256 */
257 private void trackAdding(final Object item, final Object related) {
258 if (DEBUG) {
259 System.out.println("AbstractTracked.trackAdding: " + item); //$NON-NLS-1$
260 }
261 Object object = null;
262 boolean becameUntracked = false;
263 /* Call customizer outside of synchronized region */
264 try {
265 object = customizerAdding(item, related);
266 /*
267 * If the customizer throws an unchecked exception, it will
268 * propagate after the finally
269 */
270 }
271 finally {
272 boolean needToCallback = false;
273 synchronized (this) {
274 if (adding.remove(item) && !closed) {
275 /*
276 * if the item was not untracked during the customizer
277 * callback
278 */
279 if (object != null) {
280 tracked.put(item, object);
281 modified(); /* increment modification count */
282 notifyAll(); /* notify any waiters */
283 needToCallback = true; /* marrs: invoke added callback */
284 }
285 }
286 else {
287 becameUntracked = true;
288 }
289 }
290 if (needToCallback) {
291 customizerAdded(item, related, object);
292 }
293 }
294 /*
295 * The item became untracked during the customizer callback.
296 */
297 if (becameUntracked && (object != null)) {
298 if (DEBUG) {
299 System.out
300 .println("AbstractTracked.trackAdding[removed]: " + item); //$NON-NLS-1$
301 }
302 /* Call customizer outside of synchronized region */
303 customizerRemoved(item, related, object);
304 /*
305 * If the customizer throws an unchecked exception, it is safe to
306 * let it propagate
307 */
308 }
309 }
310
311 /**
312 * Discontinue tracking the item.
313 *
314 * @param item Item to be untracked.
315 * @param related Action related object.
316 */
317 void untrack(final Object item, final Object related) {
318 final Object object;
319 synchronized (this) {
320 if (initial.remove(item)) { /*
321 * if this item is already in the list
322 * of initial references to process
323 */
324 if (DEBUG) {
325 System.out
326 .println("AbstractTracked.untrack[removed from initial]: " + item); //$NON-NLS-1$
327 }
328 return; /*
329 * we have removed it from the list and it will not be
330 * processed
331 */
332 }
333
334 if (adding.remove(item)) { /*
335 * if the item is in the process of
336 * being added
337 */
338 if (DEBUG) {
339 System.out
340 .println("AbstractTracked.untrack[being added]: " + item); //$NON-NLS-1$
341 }
342 return; /*
343 * in case the item is untracked while in the process of
344 * adding
345 */
346 }
347 object = tracked.remove(item); /*
348 * must remove from tracker before
349 * calling customizer callback
350 */
351 if (object == null) { /* are we actually tracking the item */
352 return;
353 }
354 modified(); /* increment modification count */
355 }
356 if (DEBUG) {
357 System.out.println("AbstractTracked.untrack[removed]: " + item); //$NON-NLS-1$
358 }
359 /* Call customizer outside of synchronized region */
360 customizerRemoved(item, related, object);
361 /*
362 * If the customizer throws an unchecked exception, it is safe to let it
363 * propagate
364 */
365 }
366
367 /**
368 * Returns the number of tracked items.
369 *
370 * @return The number of tracked items.
371 *
372 * @GuardedBy this
373 */
374 int size() {
375 return tracked.size();
376 }
377
378 /**
379 * Return the customized object for the specified item
380 *
381 * @param item The item to lookup in the map
382 * @return The customized object for the specified item.
383 *
384 * @GuardedBy this
385 */
386 Object getCustomizedObject(final Object item) {
387 return tracked.get(item);
388 }
389
390 /**
391 * Return the list of tracked items.
392 *
393 * @param list An array to contain the tracked items.
394 * @return The specified list if it is large enough to hold the tracked
395 * items or a new array large enough to hold the tracked items.
396 * @GuardedBy this
397 */
398 Object[] getTracked(final Object[] list) {
399 return tracked.keySet().toArray(list);
400 }
401
402 /**
403 * Increment the modification count. If this method is overridden, the
404 * overriding method MUST call this method to increment the tracking count.
405 *
406 * @GuardedBy this
407 */
408 void modified() {
409 trackingCount++;
410 }
411
412 /**
413 * Returns the tracking count for this <code>ServiceTracker</code> object.
414 *
415 * The tracking count is initialized to 0 when this object is opened. Every
416 * time an item is added, modified or removed from this object the tracking
417 * count is incremented.
418 *
419 * @GuardedBy this
420 * @return The tracking count for this object.
421 */
422 int getTrackingCount() {
423 return trackingCount;
424 }
425
426 /**
427 * Call the specific customizer adding method. This method must not be
428 * called while synchronized on this object.
429 *
430 * @param item Item to be tracked.
431 * @param related Action related object.
432 * @return Customized object for the tracked item or <code>null</code> if
433 * the item is not to be tracked.
434 */
435 abstract Object customizerAdding(final Object item, final Object related);
436
437 /** marrs: Call the specific customizer added method. */
438 abstract void customizerAdded(final Object item, final Object related, final Object object);
439
440 /**
441 * Call the specific customizer modified method. This method must not be
442 * called while synchronized on this object.
443 *
444 * @param item Tracked item.
445 * @param related Action related object.
446 * @param object Customized object for the tracked item.
447 */
448 abstract void customizerModified(final Object item, final Object related,
449 final Object object);
450
451 /**
452 * Call the specific customizer removed method. This method must not be
453 * called while synchronized on this object.
454 *
455 * @param item Tracked item.
456 * @param related Action related object.
457 * @param object Customized object for the tracked item.
458 */
459 abstract void customizerRemoved(final Object item, final Object related,
460 final Object object);
461}