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.concurrent.CopyOnWriteArrayList;
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 mechanism. In this case these
033     * classes only need to call the {@code fireEvent()} 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} 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()} method that can be implemented as a
045     * combination of {@code clearProperty()} and {@code addProperty()}.
046     * With {@code detailEvents} 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     * {@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}
053     * for instance has a custom implementation of {@code setProperty()},
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     * {@link ConfigurationErrorListener}. There is another set of
061     * methods dealing with event listeners of this type. The
062     * {@code fireError()} 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 1234617 2012-01-22 21:31:01Z oheger $
068     * @since 1.3
069     */
070    public class EventSource
071    {
072        /** A collection for the registered event listeners. */
073        private Collection<ConfigurationListener> listeners;
074    
075        /** A collection for the registered error listeners.*/
076        private Collection<ConfigurationErrorListener> errorListeners;
077    
078        /** A lock object for guarding access to the detail events counter. */
079        private final Object lockDetailEventsCount = new Object();
080    
081        /** A counter for the detail events. */
082        private int detailEvents;
083    
084        /**
085         * Creates a new instance of {@code EventSource}.
086         */
087        public EventSource()
088        {
089            initListeners();
090        }
091    
092        /**
093         * Adds a configuration listener to this object.
094         *
095         * @param l the listener to add
096         */
097        public void addConfigurationListener(ConfigurationListener l)
098        {
099            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    }