    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    }