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