View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.configuration.event;
    18
    19  import java.util.ArrayList;
    20  import java.util.Collection;
    21  import java.util.Collections;
    22  import java.util.Iterator;
    23  import java.util.LinkedList;
    24
    25  /***
    26   * <p>
    27   * A base class for objects that can generate configuration events.
    28   * </p>
    29   * <p>
    30   * This class implements functionality for managing a set of event listeners
    31   * that can be notified when an event occurs. It can be extended by
    32   * configuration classes that support the event machanism. In this case these
    33   * classes only need to call the <code>fireEvent()</code> method when an event
    34   * is to be delivered to the registered listeners.
    35   * </p>
    36   * <p>
    37   * Adding and removing event listeners can happen concurrently to manipulations
    38   * on a configuration that cause events. The operations are synchronized.
    39   * </p>
    40   * <p>
    41   * With the <code>detailEvents</code> property the number of detail events can
    42   * be controlled. Some methods in configuration classes are implemented in a way
    43   * that they call other methods that can generate their own events. One example
    44   * is the <code>setProperty()</code> method that can be implemented as a
    45   * combination of <code>clearProperty()</code> and <code>addProperty()</code>.
    46   * With <code>detailEvents</code> set to <b>true</b>, all involved methods
    47   * will generate events (i.e. listeners will receive property set events,
    48   * property clear events, and property add events). If this mode is turned off
    49   * (which is the default), detail events are suppressed, so only property set
    50   * events will be received. Note that the number of received detail events may
    51   * differ for different configuration implementations.
    52   * <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code>
    53   * for instance has a custom implementation of <code>setProperty()</code>,
    54   * which does not generate any detail events.
    55   * </p>
    56   * <p>
    57   * In addition to &quot;normal&quot; events, error events are supported. Such
    58   * events signal an internal problem that occurred during access of properties.
    59   * For them a special listener interface exists:
    60   * <code>{@link ConfigurationErrorListener}</code>. There is another set of
    61   * methods dealing with event listeners of this type. The
    62   * <code>fireError()</code> method can be used by derived classes to send
    63   * notifications about errors to registered observers.
    64   * </p>
    65   *
    66   * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a>
    67   * @version $Id: EventSource.java 561230 2007-07-31 04:17:09Z rahul $
    68   * @since 1.3
    69   */
    70  public class EventSource
    71  {
    72      /*** A collection for the registered event listeners. */
    73      private Collection listeners;
    74
    75      /*** A collection for the registered error listeners.*/
    76      private Collection errorListeners;
    77
    78      /*** A counter for the detail events. */
    79      private int detailEvents;
    80
    81      /***
    82       * Creates a new instance of <code>EventSource</code>.
    83       */
    84      public EventSource()
    85      {
    86          initListeners();
    87      }
    88
    89      /***
    90       * Adds a configuration listener to this object.
    91       *
    92       * @param l the listener to add
    93       */
    94      public void addConfigurationListener(ConfigurationListener l)
    95      {
    96          doAddListener(listeners, l);
    97      }
    98
    99      /***
    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 }