BaseEventSource.java

  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.configuration2.event;

  18. import java.util.Collection;
  19. import java.util.Collections;
  20. import java.util.LinkedList;
  21. import java.util.List;

  22. /**
  23.  * <p>
  24.  * A base class for objects that can generate configuration events.
  25.  * </p>
  26.  * <p>
  27.  * This class implements functionality for managing a set of event listeners that can be notified when an event occurs.
  28.  * It can be extended by configuration classes that support the event mechanism. In this case these classes only need to
  29.  * call the {@code fireEvent()} method when an event is to be delivered to the registered listeners.
  30.  * </p>
  31.  * <p>
  32.  * Adding and removing event listeners can happen concurrently to manipulations on a configuration that cause events.
  33.  * The operations are synchronized.
  34.  * </p>
  35.  * <p>
  36.  * With the {@code detailEvents} property the number of detail events can be controlled. Some methods in configuration
  37.  * classes are implemented in a way that they call other methods that can generate their own events. One example is the
  38.  * {@code setProperty()} method that can be implemented as a combination of {@code clearProperty()} and
  39.  * {@code addProperty()}. With {@code detailEvents} set to <strong>true</strong>, all involved methods will generate events (i.e.
  40.  * listeners will receive property set events, property clear events, and property add events). If this mode is turned
  41.  * off (which is the default), detail events are suppressed, so only property set events will be received. Note that the
  42.  * number of received detail events may differ for different configuration implementations.
  43.  * {@link org.apache.commons.configuration2.BaseHierarchicalConfiguration BaseHierarchicalConfiguration} for instance
  44.  * has a custom implementation of {@code setProperty()}, which does not generate any detail events.
  45.  * </p>
  46.  * <p>
  47.  * In addition to &quot;normal&quot; events, error events are supported. Such events signal an internal problem that
  48.  * occurred during access of properties. They are handled via the regular {@link EventListener} interface, but there are
  49.  * special event types defined by {@link ConfigurationErrorEvent}. The {@code fireError()} method can be used by derived
  50.  * classes to send notifications about errors to registered observers.
  51.  * </p>
  52.  *
  53.  * @since 1.3
  54.  */
  55. public class BaseEventSource implements EventSource {

  56.     /** The list for managing registered event listeners. */
  57.     private EventListenerList eventListeners;

  58.     /** A lock object for guarding access to the detail events counter. */
  59.     private final Object lockDetailEventsCount = new Object();

  60.     /** A counter for the detail events. */
  61.     private int detailEvents;

  62.     /**
  63.      * Creates a new instance of {@code BaseEventSource}.
  64.      */
  65.     public BaseEventSource() {
  66.         initListeners();
  67.     }

  68.     @Override
  69.     public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
  70.         eventListeners.addEventListener(eventType, listener);
  71.     }

  72.     /**
  73.      * Helper method for checking the current counter for detail events. This method checks whether the counter is greater
  74.      * than the passed in limit.
  75.      *
  76.      * @param limit the limit to be compared to
  77.      * @return <strong>true</strong> if the counter is greater than the limit, <strong>false</strong> otherwise
  78.      */
  79.     private boolean checkDetailEvents(final int limit) {
  80.         synchronized (lockDetailEventsCount) {
  81.             return detailEvents > limit;
  82.         }
  83.     }

  84.     /**
  85.      * Removes all registered error listeners.
  86.      *
  87.      * @since 1.4
  88.      */
  89.     public void clearErrorListeners() {
  90.         eventListeners.getRegistrationsForSuperType(ConfigurationErrorEvent.ANY).forEach(eventListeners::removeEventListener);
  91.     }

  92.     /**
  93.      * Removes all registered event listeners.
  94.      */
  95.     public void clearEventListeners() {
  96.         eventListeners.clear();
  97.     }

  98.     /**
  99.      * Overrides the {@code clone()} method to correctly handle so far registered event listeners. This implementation ensures that the clone will have empty
  100.      * event listener lists, i.e. the listeners registered at an {@code BaseEventSource} object will not be copied.
  101.      *
  102.      * @return the cloned object
  103.      * @throws CloneNotSupportedException if the object's class does not support the {@code Cloneable} interface. Subclasses that override the {@code clone}
  104.      *                                    method can also throw this exception to indicate that an instance cannot be cloned.
  105.      * @since 1.4
  106.      */
  107.     @Override
  108.     protected Object clone() throws CloneNotSupportedException {
  109.         final BaseEventSource copy = (BaseEventSource) super.clone();
  110.         copy.initListeners();
  111.         return copy;
  112.     }

  113.     /**
  114.      * Copies all event listener registrations maintained by this object to the specified {@code BaseEventSource} object.
  115.      *
  116.      * @param source the target source for the copy operation (must not be <strong>null</strong>)
  117.      * @throws IllegalArgumentException if the target source is <strong>null</strong>
  118.      * @since 2.0
  119.      */
  120.     public void copyEventListeners(final BaseEventSource source) {
  121.         if (source == null) {
  122.             throw new IllegalArgumentException("Target event source must not be null!");
  123.         }
  124.         source.eventListeners.addAll(eventListeners);
  125.     }

  126.     /**
  127.      * Creates a {@code ConfigurationErrorEvent} object based on the passed in parameters. This is called by
  128.      * {@code fireError()} if it decides that an event needs to be generated.
  129.      *
  130.      * @param type the event's type
  131.      * @param opType the operation type related to this error
  132.      * @param propName the name of the affected property (can be <strong>null</strong>)
  133.      * @param propValue the value of the affected property (can be <strong>null</strong>)
  134.      * @param ex the {@code Throwable} object that caused this error event
  135.      * @return the event object
  136.      */
  137.     protected ConfigurationErrorEvent createErrorEvent(final EventType<? extends ConfigurationErrorEvent> type, final EventType<?> opType,
  138.         final String propName, final Object propValue, final Throwable ex) {
  139.         return new ConfigurationErrorEvent(this, type, opType, propName, propValue, ex);
  140.     }

  141.     /**
  142.      * Creates a {@code ConfigurationEvent} object based on the passed in parameters. This method is called by
  143.      * {@code fireEvent()} if it decides that an event needs to be generated.
  144.      *
  145.      * @param type the event's type
  146.      * @param propName the name of the affected property (can be <strong>null</strong>)
  147.      * @param propValue the value of the affected property (can be <strong>null</strong>)
  148.      * @param before the before update flag
  149.      * @param <T> the type of the event to be created
  150.      * @return the newly created event object
  151.      */
  152.     protected <T extends ConfigurationEvent> ConfigurationEvent createEvent(final EventType<T> type, final String propName, final Object propValue,
  153.         final boolean before) {
  154.         return new ConfigurationEvent(this, type, propName, propValue, before);
  155.     }

  156.     /**
  157.      * Creates an error event object and delivers it to all registered error listeners of a matching type.
  158.      *
  159.      * @param eventType the event's type
  160.      * @param operationType the type of the failed operation
  161.      * @param propertyName the name of the affected property (can be <strong>null</strong>)
  162.      * @param propertyValue the value of the affected property (can be <strong>null</strong>)
  163.      * @param cause the {@code Throwable} object that caused this error event
  164.      * @param <T> the event type
  165.      */
  166.     public <T extends ConfigurationErrorEvent> void fireError(final EventType<T> eventType, final EventType<?> operationType, final String propertyName,
  167.         final Object propertyValue, final Throwable cause) {
  168.         final EventListenerList.EventListenerIterator<T> iterator = eventListeners.getEventListenerIterator(eventType);
  169.         if (iterator.hasNext()) {
  170.             final ConfigurationErrorEvent event = createErrorEvent(eventType, operationType, propertyName, propertyValue, cause);
  171.             while (iterator.hasNext()) {
  172.                 iterator.invokeNext(event);
  173.             }
  174.         }
  175.     }

  176.     /**
  177.      * Creates an event object and delivers it to all registered event listeners. The method checks first if sending an
  178.      * event is allowed (making use of the {@code detailEvents} property), and if listeners are registered.
  179.      *
  180.      * @param type the event's type
  181.      * @param propName the name of the affected property (can be <strong>null</strong>)
  182.      * @param propValue the value of the affected property (can be <strong>null</strong>)
  183.      * @param before the before update flag
  184.      * @param <T> the type of the event to be fired
  185.      */
  186.     protected <T extends ConfigurationEvent> void fireEvent(final EventType<T> type, final String propName, final Object propValue, final boolean before) {
  187.         if (checkDetailEvents(-1)) {
  188.             final EventListenerList.EventListenerIterator<T> it = eventListeners.getEventListenerIterator(type);
  189.             if (it.hasNext()) {
  190.                 final ConfigurationEvent event = createEvent(type, propName, propValue, before);
  191.                 while (it.hasNext()) {
  192.                     it.invokeNext(event);
  193.                 }
  194.             }
  195.         }
  196.     }

  197.     /**
  198.      * Gets a list with all {@code EventListenerRegistrationData} objects currently contained for this event source. This
  199.      * method allows access to all registered event listeners, independent on their type.
  200.      *
  201.      * @return a list with information about all registered event listeners
  202.      */
  203.     public List<EventListenerRegistrationData<?>> getEventListenerRegistrations() {
  204.         return eventListeners.getRegistrations();
  205.     }

  206.     /**
  207.      * Gets a collection with all event listeners of the specified event type that are currently registered at this
  208.      * object.
  209.      *
  210.      * @param eventType the event type object
  211.      * @param <T> the event type
  212.      * @return a collection with the event listeners of the specified event type (this collection is a snapshot of the
  213.      *         currently registered listeners; it cannot be manipulated)
  214.      */
  215.     public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
  216.         final List<EventListener<? super T>> result = new LinkedList<>();
  217.         eventListeners.getEventListeners(eventType).forEach(result::add);
  218.         return Collections.unmodifiableCollection(result);
  219.     }

  220.     /**
  221.      * Initializes the collections for storing registered event listeners.
  222.      */
  223.     private void initListeners() {
  224.         eventListeners = new EventListenerList();
  225.     }

  226.     /**
  227.      * Returns a flag whether detail events are enabled.
  228.      *
  229.      * @return a flag if detail events are generated
  230.      */
  231.     public boolean isDetailEvents() {
  232.         return checkDetailEvents(0);
  233.     }

  234.     @Override
  235.     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
  236.         return eventListeners.removeEventListener(eventType, listener);
  237.     }

  238.     /**
  239.      * Determines whether detail events should be generated. If enabled, some methods can generate multiple update events.
  240.      * Note that this method records the number of calls, i.e. if for instance {@code setDetailEvents(false)} was called
  241.      * three times, you will have to invoke the method as often to enable the details.
  242.      *
  243.      * @param enable a flag if detail events should be enabled or disabled
  244.      */
  245.     public void setDetailEvents(final boolean enable) {
  246.         synchronized (lockDetailEventsCount) {
  247.             if (enable) {
  248.                 detailEvents++;
  249.             } else {
  250.                 detailEvents--;
  251.             }
  252.         }
  253.     }
  254. }