View Javadoc

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 &quot;normal&quot; 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 }