1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. 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 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.concurrent.CopyOnWriteArrayList;
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 mechanism. In this case these
33 * classes only need to call the {@code fireEvent()} 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} 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()} method that can be implemented as a
45 * combination of {@code clearProperty()} and {@code addProperty()}.
46 * With {@code detailEvents} 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 * {@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}
53 * for instance has a custom implementation of {@code setProperty()},
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 * {@link ConfigurationErrorListener}. There is another set of
61 * methods dealing with event listeners of this type. The
62 * {@code fireError()} method can be used by derived classes to send
63 * notifications about errors to registered observers.
64 * </p>
65 *
66 * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
67 * @version $Id: EventSource.java 1234617 2012-01-22 21:31:01Z oheger $
68 * @since 1.3
69 */
70 public class EventSource
71 {
72 /** A collection for the registered event listeners. */
73 private Collection<ConfigurationListener> listeners;
74
75 /** A collection for the registered error listeners.*/
76 private Collection<ConfigurationErrorListener> errorListeners;
77
78 /** A lock object for guarding access to the detail events counter. */
79 private final Object lockDetailEventsCount = new Object();
80
81 /** A counter for the detail events. */
82 private int detailEvents;
83
84 /**
85 * Creates a new instance of {@code EventSource}.
86 */
87 public EventSource()
88 {
89 initListeners();
90 }
91
92 /**
93 * Adds a configuration listener to this object.
94 *
95 * @param l the listener to add
96 */
97 public void addConfigurationListener(ConfigurationListener l)
98 {
99 checkListener(l);
100 listeners.add(l);
101 }
102
103 /**
104 * Removes the specified event listener so that it does not receive any
105 * further events caused by this object.
106 *
107 * @param l the listener to be removed
108 * @return a flag whether the event listener was found
109 */
110 public boolean removeConfigurationListener(ConfigurationListener l)
111 {
112 return listeners.remove(l);
113 }
114
115 /**
116 * Returns a collection with all configuration event listeners that are
117 * currently registered at this object.
118 *
119 * @return a collection with the registered
120 * {@code ConfigurationListener}s (this collection is a snapshot
121 * of the currently registered listeners; manipulating it has no effect
122 * on this event source object)
123 */
124 public Collection<ConfigurationListener> getConfigurationListeners()
125 {
126 return Collections.unmodifiableCollection(new ArrayList<ConfigurationListener>(listeners));
127 }
128
129 /**
130 * Removes all registered configuration listeners.
131 */
132 public void clearConfigurationListeners()
133 {
134 listeners.clear();
135 }
136
137 /**
138 * Returns a flag whether detail events are enabled.
139 *
140 * @return a flag if detail events are generated
141 */
142 public boolean isDetailEvents()
143 {
144 return checkDetailEvents(0);
145 }
146
147 /**
148 * Determines whether detail events should be generated. If enabled, some
149 * methods can generate multiple update events. Note that this method
150 * records the number of calls, i.e. if for instance
151 * {@code setDetailEvents(false)} was called three times, you will
152 * have to invoke the method as often to enable the details.
153 *
154 * @param enable a flag if detail events should be enabled or disabled
155 */
156 public void setDetailEvents(boolean enable)
157 {
158 synchronized (lockDetailEventsCount)
159 {
160 if (enable)
161 {
162 detailEvents++;
163 }
164 else
165 {
166 detailEvents--;
167 }
168 }
169 }
170
171 /**
172 * Adds a new configuration error listener to this object. This listener
173 * will then be notified about internal problems.
174 *
175 * @param l the listener to register (must not be <b>null</b>)
176 * @since 1.4
177 */
178 public void addErrorListener(ConfigurationErrorListener l)
179 {
180 checkListener(l);
181 errorListeners.add(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 errorListeners.remove(l);
195 }
196
197 /**
198 * Removes all registered error listeners.
199 *
200 * @since 1.4
201 */
202 public void clearErrorListeners()
203 {
204 errorListeners.clear();
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}s (this collection is a
213 * snapshot of the currently registered listeners; it cannot be manipulated)
214 * @since 1.4
215 */
216 public Collection<ConfigurationErrorListener> getErrorListeners()
217 {
218 return Collections.unmodifiableCollection(new ArrayList<ConfigurationErrorListener>(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} 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, boolean before)
233 {
234 if (checkDetailEvents(-1))
235 {
236 Iterator<ConfigurationListener> it = listeners.iterator();
237 if (it.hasNext())
238 {
239 ConfigurationEvent event =
240 createEvent(type, propName, propValue, before);
241 while (it.hasNext())
242 {
243 it.next().configurationChanged(event);
244 }
245 }
246 }
247 }
248
249 /**
250 * Creates a {@code ConfigurationEvent} object based on the passed in
251 * parameters. This is called by {@code fireEvent()} if it decides
252 * that an event needs to be generated.
253 *
254 * @param type the event's type
255 * @param propName the name of the affected property (can be <b>null</b>)
256 * @param propValue the value of the affected property (can be <b>null</b>)
257 * @param before the before update flag
258 * @return the newly created event object
259 */
260 protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before)
261 {
262 return new ConfigurationEvent(this, type, propName, propValue, before);
263 }
264
265 /**
266 * Creates an error event object and delivers it to all registered error
267 * listeners.
268 *
269 * @param type the event's type
270 * @param propName the name of the affected property (can be <b>null</b>)
271 * @param propValue the value of the affected property (can be <b>null</b>)
272 * @param ex the {@code Throwable} object that caused this error event
273 * @since 1.4
274 */
275 protected void fireError(int type, String propName, Object propValue, Throwable ex)
276 {
277 Iterator<ConfigurationErrorListener> it = errorListeners.iterator();
278 if (it.hasNext())
279 {
280 ConfigurationErrorEvent event =
281 createErrorEvent(type, propName, propValue, ex);
282 while (it.hasNext())
283 {
284 it.next().configurationError(event);
285 }
286 }
287 }
288
289 /**
290 * Creates a {@code ConfigurationErrorEvent} object based on the
291 * passed in parameters. This is called by {@code fireError()} if it
292 * decides that an event needs to be generated.
293 *
294 * @param type the event's type
295 * @param propName the name of the affected property (can be <b>null</b>)
296 * @param propValue the value of the affected property (can be <b>null</b>)
297 * @param ex the {@code Throwable} object that caused this error
298 * event
299 * @return the event object
300 * @since 1.4
301 */
302 protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex)
303 {
304 return new ConfigurationErrorEvent(this, type, propName, propValue, ex);
305 }
306
307 /**
308 * Overrides the {@code clone()} method to correctly handle so far
309 * registered event listeners. This implementation ensures that the clone
310 * will have empty event listener lists, i.e. the listeners registered at an
311 * {@code EventSource} object will not be copied.
312 *
313 * @return the cloned object
314 * @throws CloneNotSupportedException if cloning is not allowed
315 * @since 1.4
316 */
317 @Override
318 protected Object clone() throws CloneNotSupportedException
319 {
320 EventSource copy = (EventSource) super.clone();
321 copy.initListeners();
322 return copy;
323 }
324
325 /**
326 * Checks whether the specified event listener is not <b>null</b>. If this
327 * is the case, an {@code IllegalArgumentException} exception is thrown.
328 *
329 * @param l the listener to be checked
330 * @throws IllegalArgumentException if the listener is <b>null</b>
331 */
332 private static void checkListener(Object l)
333 {
334 if (l == null)
335 {
336 throw new IllegalArgumentException("Listener must not be null!");
337 }
338 }
339
340 /**
341 * Initializes the collections for storing registered event listeners.
342 */
343 private void initListeners()
344 {
345 listeners = new CopyOnWriteArrayList<ConfigurationListener>();
346 errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>();
347 }
348
349 /**
350 * Helper method for checking the current counter for detail events. This
351 * method checks whether the counter is greater than the passed in limit.
352 *
353 * @param limit the limit to be compared to
354 * @return <b>true</b> if the counter is greater than the limit,
355 * <b>false</b> otherwise
356 */
357 private boolean checkDetailEvents(int limit)
358 {
359 synchronized (lockDetailEventsCount)
360 {
361 return detailEvents > limit;
362 }
363 }
364 }