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 */
017package org.apache.commons.configuration.event;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.Iterator;
023import 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 */
070public 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}