001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.commons.configuration.event;
018
019 import java.util.ArrayList;
020 import java.util.Collection;
021 import java.util.Collections;
022 import java.util.Iterator;
023 import java.util.LinkedList;
024
025 /**
026 * <p>
027 * A base class for objects that can generate configuration events.
028 * </p>
029 * <p>
030 * This class implements functionality for managing a set of event listeners
031 * that can be notified when an event occurs. It can be extended by
032 * configuration classes that support the event machanism. In this case these
033 * classes only need to call the <code>fireEvent()</code> method when an event
034 * is to be delivered to the registered listeners.
035 * </p>
036 * <p>
037 * Adding and removing event listeners can happen concurrently to manipulations
038 * on a configuration that cause events. The operations are synchronized.
039 * </p>
040 * <p>
041 * With the <code>detailEvents</code> property the number of detail events can
042 * be controlled. Some methods in configuration classes are implemented in a way
043 * that they call other methods that can generate their own events. One example
044 * is the <code>setProperty()</code> method that can be implemented as a
045 * combination of <code>clearProperty()</code> and <code>addProperty()</code>.
046 * With <code>detailEvents</code> set to <b>true</b>, all involved methods
047 * will generate events (i.e. listeners will receive property set events,
048 * property clear events, and property add events). If this mode is turned off
049 * (which is the default), detail events are suppressed, so only property set
050 * events will be received. Note that the number of received detail events may
051 * differ for different configuration implementations.
052 * <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code>
053 * for instance has a custom implementation of <code>setProperty()</code>,
054 * which does not generate any detail events.
055 * </p>
056 * <p>
057 * In addition to "normal" events, error events are supported. Such
058 * events signal an internal problem that occurred during access of properties.
059 * For them a special listener interface exists:
060 * <code>{@link ConfigurationErrorListener}</code>. There is another set of
061 * methods dealing with event listeners of this type. The
062 * <code>fireError()</code> method can be used by derived classes to send
063 * notifications about errors to registered observers.
064 * </p>
065 *
066 * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
067 * @version $Id: EventSource.java 561230 2007-07-31 04:17:09Z rahul $
068 * @since 1.3
069 */
070 public class EventSource
071 {
072 /** A collection for the registered event listeners. */
073 private Collection listeners;
074
075 /** A collection for the registered error listeners.*/
076 private Collection errorListeners;
077
078 /** A counter for the detail events. */
079 private int detailEvents;
080
081 /**
082 * Creates a new instance of <code>EventSource</code>.
083 */
084 public EventSource()
085 {
086 initListeners();
087 }
088
089 /**
090 * Adds a configuration listener to this object.
091 *
092 * @param l the listener to add
093 */
094 public void addConfigurationListener(ConfigurationListener l)
095 {
096 doAddListener(listeners, l);
097 }
098
099 /**
100 * Removes the specified event listener so that it does not receive any
101 * further events caused by this object.
102 *
103 * @param l the listener to be removed
104 * @return a flag whether the event listener was found
105 */
106 public boolean removeConfigurationListener(ConfigurationListener l)
107 {
108 return doRemoveListener(listeners, l);
109 }
110
111 /**
112 * Returns a collection with all configuration event listeners that are
113 * currently registered at this object.
114 *
115 * @return a collection with the registered
116 * <code>ConfigurationListener</code>s (this collection is a snapshot
117 * of the currently registered listeners; manipulating it has no effect
118 * on this event source object)
119 */
120 public Collection getConfigurationListeners()
121 {
122 return doGetListeners(listeners);
123 }
124
125 /**
126 * Removes all registered configuration listeners.
127 */
128 public void clearConfigurationListeners()
129 {
130 doClearListeners(listeners);
131 }
132
133 /**
134 * Returns a flag whether detail events are enabled.
135 *
136 * @return a flag if detail events are generated
137 */
138 public boolean isDetailEvents()
139 {
140 synchronized (listeners)
141 {
142 return detailEvents > 0;
143 }
144 }
145
146 /**
147 * Determines whether detail events should be generated. If enabled, some
148 * methods can generate multiple update events. Note that this method
149 * records the number of calls, i.e. if for instance
150 * <code>setDetailEvents(false)</code> was called three times, you will
151 * have to invoke the method as often to enable the details.
152 *
153 * @param enable a flag if detail events should be enabled or disabled
154 */
155 public void setDetailEvents(boolean enable)
156 {
157 synchronized (listeners)
158 {
159 if (enable)
160 {
161 detailEvents++;
162 }
163 else
164 {
165 detailEvents--;
166 }
167 }
168 }
169
170 /**
171 * Adds a new configuration error listener to this object. This listener
172 * will then be notified about internal problems.
173 *
174 * @param l the listener to register (must not be <b>null</b>)
175 * @since 1.4
176 */
177 public void addErrorListener(ConfigurationErrorListener l)
178 {
179 doAddListener(errorListeners, l);
180 }
181
182 /**
183 * Removes the specified error listener so that it does not receive any
184 * further events caused by this object.
185 *
186 * @param l the listener to remove
187 * @return a flag whether the listener could be found and removed
188 * @since 1.4
189 */
190 public boolean removeErrorListener(ConfigurationErrorListener l)
191 {
192 return doRemoveListener(errorListeners, l);
193 }
194
195 /**
196 * Removes all registered error listeners.
197 *
198 * @since 1.4
199 */
200 public void clearErrorListeners()
201 {
202 doClearListeners(errorListeners);
203 }
204
205 /**
206 * Returns a collection with all configuration error listeners that are
207 * currently registered at this object.
208 *
209 * @return a collection with the registered
210 * <code>ConfigurationErrorListener</code>s (this collection is a
211 * snapshot of the currently registered listeners; it cannot be manipulated)
212 * @since 1.4
213 */
214 public Collection getErrorListeners()
215 {
216 return doGetListeners(errorListeners);
217 }
218
219 /**
220 * Creates an event object and delivers it to all registered event
221 * listeners. The method will check first if sending an event is allowed
222 * (making use of the <code>detailEvents</code> property), and if
223 * listeners are registered.
224 *
225 * @param type the event's type
226 * @param propName the name of the affected property (can be <b>null</b>)
227 * @param propValue the value of the affected property (can be <b>null</b>)
228 * @param before the before update flag
229 */
230 protected void fireEvent(int type, String propName, Object propValue, boolean before)
231 {
232 Collection listenersToCall = null;
233
234 synchronized (listeners)
235 {
236 if (detailEvents >= 0 && listeners.size() > 0)
237 {
238 // Copy listeners to another collection so that manipulating
239 // the listener list during event delivery won't cause problems
240 listenersToCall = new ArrayList(listeners);
241 }
242 }
243
244 if (listenersToCall != null)
245 {
246 ConfigurationEvent event = createEvent(type, propName, propValue, before);
247 for (Iterator it = listenersToCall.iterator(); it.hasNext();)
248 {
249 ((ConfigurationListener) it.next()).configurationChanged(event);
250 }
251 }
252 }
253
254 /**
255 * Creates a <code>ConfigurationEvent</code> object based on the passed in
256 * parameters. This is called by <code>fireEvent()</code> if it decides
257 * that an event needs to be generated.
258 *
259 * @param type the event's type
260 * @param propName the name of the affected property (can be <b>null</b>)
261 * @param propValue the value of the affected property (can be <b>null</b>)
262 * @param before the before update flag
263 * @return the newly created event object
264 */
265 protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before)
266 {
267 return new ConfigurationEvent(this, type, propName, propValue, before);
268 }
269
270 /**
271 * Creates an error event object and delivers it to all registered error
272 * listeners.
273 *
274 * @param type the event's type
275 * @param propName the name of the affected property (can be <b>null</b>)
276 * @param propValue the value of the affected property (can be <b>null</b>)
277 * @param ex the <code>Throwable</code> object that caused this error event
278 * @since 1.4
279 */
280 protected void fireError(int type, String propName, Object propValue, Throwable ex)
281 {
282 Collection listenersToCall = null;
283
284 synchronized (errorListeners)
285 {
286 if (errorListeners.size() > 0)
287 {
288 // Copy listeners to another collection so that manipulating
289 // the listener list during event delivery won't cause problems
290 listenersToCall = new ArrayList(errorListeners);
291 }
292 }
293
294 if (listenersToCall != null)
295 {
296 ConfigurationErrorEvent event = createErrorEvent(type, propName, propValue, ex);
297 for (Iterator it = listenersToCall.iterator(); it.hasNext();)
298 {
299 ((ConfigurationErrorListener) it.next()).configurationError(event);
300 }
301 }
302 }
303
304 /**
305 * Creates a <code>ConfigurationErrorEvent</code> object based on the
306 * passed in parameters. This is called by <code>fireError()</code> if it
307 * decides that an event needs to be generated.
308 *
309 * @param type the event's type
310 * @param propName the name of the affected property (can be <b>null</b>)
311 * @param propValue the value of the affected property (can be <b>null</b>)
312 * @param ex the <code>Throwable</code> object that caused this error
313 * event
314 * @return the event object
315 * @since 1.4
316 */
317 protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex)
318 {
319 return new ConfigurationErrorEvent(this, type, propName, propValue, ex);
320 }
321
322 /**
323 * Overrides the <code>clone()</code> method to correctly handle so far
324 * registered event listeners. This implementation ensures that the clone
325 * will have empty event listener lists, i.e. the listeners registered at an
326 * <code>EventSource</code> object will not be copied.
327 *
328 * @return the cloned object
329 * @throws CloneNotSupportedException if cloning is not allowed
330 * @since 1.4
331 */
332 protected Object clone() throws CloneNotSupportedException
333 {
334 EventSource copy = (EventSource) super.clone();
335 copy.initListeners();
336 return copy;
337 }
338
339 /**
340 * Adds a new listener object to a listener collection. This is done in a
341 * synchronized block. The listener must not be <b>null</b>.
342 *
343 * @param listeners the collection with the listeners
344 * @param l the listener object
345 */
346 private static void doAddListener(Collection listeners, Object l)
347 {
348 if (l == null)
349 {
350 throw new IllegalArgumentException("Listener must not be null!");
351 }
352 synchronized (listeners)
353 {
354 listeners.add(l);
355 }
356 }
357
358 /**
359 * Removes an event listener from a listener collection. This is done in a
360 * synchronized block.
361 *
362 * @param listeners the collection with the listeners
363 * @param l the listener object
364 * @return a flag whether the listener could be found and removed
365 */
366 private static boolean doRemoveListener(Collection listeners, Object l)
367 {
368 synchronized (listeners)
369 {
370 return listeners.remove(l);
371 }
372 }
373
374 /**
375 * Removes all entries from the given list of event listeners.
376 *
377 * @param listeners the collection with the listeners
378 */
379 private static void doClearListeners(Collection listeners)
380 {
381 synchronized (listeners)
382 {
383 listeners.clear();
384 }
385 }
386
387 /**
388 * Returns an unmodifiable snapshot of the given event listener collection.
389 *
390 * @param listeners the collection with the listeners
391 * @return a snapshot of the listeners collection
392 */
393 private static Collection doGetListeners(Collection listeners)
394 {
395 synchronized (listeners)
396 {
397 return Collections.unmodifiableCollection(new ArrayList(listeners));
398 }
399 }
400
401 /**
402 * Initializes the collections for storing registered event listeners.
403 */
404 private void initListeners()
405 {
406 listeners = new LinkedList();
407 errorListeners = new LinkedList();
408 }
409 }