1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration.event;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Iterator;
23 import java.util.LinkedList;
24
25 /***
26 * <p>
27 * A base class for objects that can generate configuration events.
28 * </p>
29 * <p>
30 * This class implements functionality for managing a set of event listeners
31 * that can be notified when an event occurs. It can be extended by
32 * configuration classes that support the event machanism. In this case these
33 * classes only need to call the <code>fireEvent()</code> method when an event
34 * is to be delivered to the registered listeners.
35 * </p>
36 * <p>
37 * Adding and removing event listeners can happen concurrently to manipulations
38 * on a configuration that cause events. The operations are synchronized.
39 * </p>
40 * <p>
41 * With the <code>detailEvents</code> property the number of detail events can
42 * be controlled. Some methods in configuration classes are implemented in a way
43 * that they call other methods that can generate their own events. One example
44 * is the <code>setProperty()</code> method that can be implemented as a
45 * combination of <code>clearProperty()</code> and <code>addProperty()</code>.
46 * With <code>detailEvents</code> set to <b>true</b>, all involved methods
47 * will generate events (i.e. listeners will receive property set events,
48 * property clear events, and property add events). If this mode is turned off
49 * (which is the default), detail events are suppressed, so only property set
50 * events will be received. Note that the number of received detail events may
51 * differ for different configuration implementations.
52 * <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code>
53 * for instance has a custom implementation of <code>setProperty()</code>,
54 * which does not generate any detail events.
55 * </p>
56 * <p>
57 * In addition to "normal" events, error events are supported. Such
58 * events signal an internal problem that occurred during access of properties.
59 * For them a special listener interface exists:
60 * <code>{@link ConfigurationErrorListener}</code>. There is another set of
61 * methods dealing with event listeners of this type. The
62 * <code>fireError()</code> method can be used by derived classes to send
63 * notifications about errors to registered observers.
64 * </p>
65 *
66 * @author <a
67 * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
68 * Configuration team</a>
69 * @version $Id: EventSource.java 495918 2007-01-13 16:33:02Z oheger $
70 * @since 1.3
71 */
72 public class EventSource
73 {
74 /*** A collection for the registered event listeners. */
75 private Collection listeners;
76
77 /*** A collection for the registered error listeners.*/
78 private Collection errorListeners;
79
80 /*** A counter for the detail events. */
81 private int detailEvents;
82
83 /***
84 * Creates a new instance of <code>EventSource</code>.
85 */
86 public EventSource()
87 {
88 initListeners();
89 }
90
91 /***
92 * Adds a configuration listener to this object.
93 *
94 * @param l the listener to add
95 */
96 public void addConfigurationListener(ConfigurationListener l)
97 {
98 doAddListener(listeners, l);
99 }
100
101 /***
102 * Removes the specified event listener so that it does not receive any
103 * further events caused by this object.
104 *
105 * @param l the listener to be removed
106 * @return a flag whether the event listener was found
107 */
108 public boolean removeConfigurationListener(ConfigurationListener l)
109 {
110 return doRemoveListener(listeners, l);
111 }
112
113 /***
114 * Returns a collection with all configuration event listeners that are
115 * currently registered at this object.
116 *
117 * @return a collection with the registered
118 * <code>ConfigurationListener</code>s (this collection is a snapshot
119 * of the currently registered listeners; manipulating it has no effect
120 * on this event source object)
121 */
122 public Collection getConfigurationListeners()
123 {
124 return doGetListeners(listeners);
125 }
126
127 /***
128 * Removes all registered configuration listeners.
129 */
130 public void clearConfigurationListeners()
131 {
132 doClearListeners(listeners);
133 }
134
135 /***
136 * Returns a flag whether detail events are enabled.
137 *
138 * @return a flag if detail events are generated
139 */
140 public boolean isDetailEvents()
141 {
142 synchronized (listeners)
143 {
144 return detailEvents > 0;
145 }
146 }
147
148 /***
149 * Determines whether detail events should be generated. If enabled, some
150 * methods can generate multiple update events. Note that this method
151 * records the number of calls, i.e. if for instance
152 * <code>setDetailEvents(false)</code> was called three times, you will
153 * have to invoke the method as often to enable the details.
154 *
155 * @param enable a flag if detail events should be enabled or disabled
156 */
157 public void setDetailEvents(boolean enable)
158 {
159 synchronized (listeners)
160 {
161 if (enable)
162 {
163 detailEvents++;
164 }
165 else
166 {
167 detailEvents--;
168 }
169 }
170 }
171
172 /***
173 * Adds a new configuration error listener to this object. This listener
174 * will then be notified about internal problems.
175 *
176 * @param l the listener to register (must not be <b>null</b>)
177 * @since 1.4
178 */
179 public void addErrorListener(ConfigurationErrorListener l)
180 {
181 doAddListener(errorListeners, l);
182 }
183
184 /***
185 * Removes the specified error listener so that it does not receive any
186 * further events caused by this object.
187 *
188 * @param l the listener to remove
189 * @return a flag whether the listener could be found and removed
190 * @since 1.4
191 */
192 public boolean removeErrorListener(ConfigurationErrorListener l)
193 {
194 return doRemoveListener(errorListeners, l);
195 }
196
197 /***
198 * Removes all registered error listeners.
199 *
200 * @since 1.4
201 */
202 public void clearErrorListeners()
203 {
204 doClearListeners(errorListeners);
205 }
206
207 /***
208 * Returns a collection with all configuration error listeners that are
209 * currently registered at this object.
210 *
211 * @return a collection with the registered
212 * <code>ConfigurationErrorListener</code>s (this collection is a
213 * snapshot of the currently registered listeners; it cannot be manipulated)
214 * @since 1.4
215 */
216 public Collection getErrorListeners()
217 {
218 return doGetListeners(errorListeners);
219 }
220
221 /***
222 * Creates an event object and delivers it to all registered event
223 * listeners. The method will check first if sending an event is allowed
224 * (making use of the <code>detailEvents</code> property), and if
225 * listeners are registered.
226 *
227 * @param type the event's type
228 * @param propName the name of the affected property (can be <b>null</b>)
229 * @param propValue the value of the affected property (can be <b>null</b>)
230 * @param before the before update flag
231 */
232 protected void fireEvent(int type, String propName, Object propValue,
233 boolean before)
234 {
235 Collection listenersToCall = null;
236
237 synchronized (listeners)
238 {
239 if (detailEvents >= 0 && listeners.size() > 0)
240 {
241
242
243 listenersToCall = new ArrayList(listeners);
244 }
245 }
246
247 if (listenersToCall != null)
248 {
249 ConfigurationEvent event = createEvent(type, propName, propValue,
250 before);
251 for (Iterator it = listenersToCall.iterator(); it.hasNext();)
252 {
253 ((ConfigurationListener) it.next()).configurationChanged(event);
254 }
255 }
256 }
257
258 /***
259 * Creates a <code>ConfigurationEvent</code> object based on the passed in
260 * parameters. This is called by <code>fireEvent()</code> if it decides
261 * that an event needs to be generated.
262 *
263 * @param type the event's type
264 * @param propName the name of the affected property (can be <b>null</b>)
265 * @param propValue the value of the affected property (can be <b>null</b>)
266 * @param before the before update flag
267 * @return the newly created event object
268 */
269 protected ConfigurationEvent createEvent(int type, String propName,
270 Object propValue, boolean before)
271 {
272 return new ConfigurationEvent(this, type, propName, propValue, before);
273 }
274
275 /***
276 * Creates an error event object and delivers it to all registered error
277 * listeners.
278 *
279 * @param type the event's type
280 * @param propName the name of the affected property (can be <b>null</b>)
281 * @param propValue the value of the affected property (can be <b>null</b>)
282 * @param ex the <code>Throwable</code> object that caused this error
283 * event
284 * @since 1.4
285 */
286 protected void fireError(int type, String propName, Object propValue,
287 Throwable ex)
288 {
289 Collection listenersToCall = null;
290
291 synchronized (errorListeners)
292 {
293 if (errorListeners.size() > 0)
294 {
295
296
297 listenersToCall = new ArrayList(errorListeners);
298 }
299 }
300
301 if (listenersToCall != null)
302 {
303 ConfigurationErrorEvent event = createErrorEvent(type, propName,
304 propValue, ex);
305 for (Iterator it = listenersToCall.iterator(); it.hasNext();)
306 {
307 ((ConfigurationErrorListener) it.next())
308 .configurationError(event);
309 }
310 }
311 }
312
313 /***
314 * Creates a <code>ConfigurationErrorEvent</code> object based on the
315 * passed in parameters. This is called by <code>fireError()</code> if it
316 * decides that an event needs to be generated.
317 *
318 * @param type the event's type
319 * @param propName the name of the affected property (can be <b>null</b>)
320 * @param propValue the value of the affected property (can be <b>null</b>)
321 * @param ex the <code>Throwable</code> object that caused this error
322 * event
323 * @return the event object
324 * @since 1.4
325 */
326 protected ConfigurationErrorEvent createErrorEvent(int type,
327 String propName, Object propValue, Throwable ex)
328 {
329 return new ConfigurationErrorEvent(this, type, propName, propValue, ex);
330 }
331
332 /***
333 * Overrides the <code>clone()</code> method to correctly handle so far
334 * registered event listeners. This implementation ensures that the clone
335 * will have empty event listener lists, i.e. the listeners registered at an
336 * <code>EventSource</code> object will not be copied.
337 *
338 * @return the cloned object
339 * @throws CloneNotSupportedException if cloning is not allowed
340 * @since 1.4
341 */
342 protected Object clone() throws CloneNotSupportedException
343 {
344 EventSource copy = (EventSource) super.clone();
345 copy.initListeners();
346 return copy;
347 }
348
349 /***
350 * Adds a new listener object to a listener collection. This is done in a
351 * synchronized block. The listener must not be <b>null</b>.
352 *
353 * @param listeners the collection with the listeners
354 * @param l the listener object
355 */
356 private static void doAddListener(Collection listeners, Object l)
357 {
358 if (l == null)
359 {
360 throw new IllegalArgumentException("Listener must not be null!");
361 }
362 synchronized (listeners)
363 {
364 listeners.add(l);
365 }
366 }
367
368 /***
369 * Removes an event listener from a listener collection. This is done in a
370 * synchronized block.
371 *
372 * @param listeners the collection with the listeners
373 * @param l the listener object
374 * @return a flag whether the listener could be found and removed
375 */
376 private static boolean doRemoveListener(Collection listeners, Object l)
377 {
378 synchronized (listeners)
379 {
380 return listeners.remove(l);
381 }
382 }
383
384 /***
385 * Removes all entries from the given list of event listeners.
386 *
387 * @param listeners the collection with the listeners
388 */
389 private static void doClearListeners(Collection listeners)
390 {
391 synchronized (listeners)
392 {
393 listeners.clear();
394 }
395 }
396
397 /***
398 * Returns an unmodifiable snapshot of the given event listener collection.
399 *
400 * @param listeners the collection with the listeners
401 * @return a snapshot of the listeners collection
402 */
403 private static Collection doGetListeners(Collection listeners)
404 {
405 synchronized (listeners)
406 {
407 return Collections.unmodifiableCollection(new ArrayList(listeners));
408 }
409 }
410
411 /***
412 * Initializes the collections for storing registered event listeners.
413 */
414 private void initListeners()
415 {
416 listeners = new LinkedList();
417 errorListeners = new LinkedList();
418 }
419 }