    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    }