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.configuration2.event;
018
019import java.util.Collection;
020import java.util.Collections;
021import java.util.LinkedList;
022import java.util.List;
023
024/**
025 * <p>
026 * A base class for objects that can generate configuration events.
027 * </p>
028 * <p>
029 * This class implements functionality for managing a set of event listeners
030 * that can be notified when an event occurs. It can be extended by
031 * configuration classes that support the event mechanism. In this case these
032 * classes only need to call the {@code fireEvent()} method when an event is to
033 * be delivered to the registered listeners.
034 * </p>
035 * <p>
036 * Adding and removing event listeners can happen concurrently to manipulations
037 * on a configuration that cause events. The operations are synchronized.
038 * </p>
039 * <p>
040 * With the {@code detailEvents} property the number of detail events can be
041 * controlled. Some methods in configuration classes are implemented in a way
042 * that they call other methods that can generate their own events. One example
043 * is the {@code setProperty()} method that can be implemented as a combination
044 * of {@code clearProperty()} and {@code addProperty()}. With
045 * {@code detailEvents} set to <b>true</b>, all involved methods will generate
046 * events (i.e. listeners will receive property set events, property clear
047 * events, and property add events). If this mode is turned off (which is the
048 * default), detail events are suppressed, so only property set events will be
049 * received. Note that the number of received detail events may differ for
050 * different configuration implementations.
051 * {@link org.apache.commons.configuration2.BaseHierarchicalConfiguration
052 * BaseHierarchicalConfiguration} for instance has a custom implementation of
053 * {@code setProperty()}, which does not generate any detail events.
054 * </p>
055 * <p>
056 * In addition to &quot;normal&quot; events, error events are supported. Such
057 * events signal an internal problem that occurred during access of properties.
058 * They are handled via the regular {@link EventListener} interface, but there
059 * are special event types defined by {@link ConfigurationErrorEvent}. The
060 * {@code fireError()} method can be used by derived classes to send
061 * notifications about errors to registered observers.
062 * </p>
063 *
064 * @since 1.3
065 */
066public class BaseEventSource implements EventSource
067{
068    /** The list for managing registered event listeners. */
069    private EventListenerList eventListeners;
070
071    /** A lock object for guarding access to the detail events counter. */
072    private final Object lockDetailEventsCount = new Object();
073
074    /** A counter for the detail events. */
075    private int detailEvents;
076
077    /**
078     * Creates a new instance of {@code BaseEventSource}.
079     */
080    public BaseEventSource()
081    {
082        initListeners();
083    }
084
085    /**
086     * Returns a collection with all event listeners of the specified event type
087     * that are currently registered at this object.
088     *
089     * @param eventType the event type object
090     * @param <T> the event type
091     * @return a collection with the event listeners of the specified event type
092     *         (this collection is a snapshot of the currently registered
093     *         listeners; it cannot be manipulated)
094     */
095    public <T extends Event> Collection<EventListener<? super T>> getEventListeners(
096            final EventType<T> eventType)
097    {
098        final List<EventListener<? super T>> result =
099                new LinkedList<>();
100        for (final EventListener<? super T> l : eventListeners
101                .getEventListeners(eventType))
102        {
103            result.add(l);
104        }
105        return Collections.unmodifiableCollection(result);
106    }
107
108    /**
109     * Returns a list with all {@code EventListenerRegistrationData} objects
110     * currently contained for this event source. This method allows access to
111     * all registered event listeners, independent on their type.
112     *
113     * @return a list with information about all registered event listeners
114     */
115    public List<EventListenerRegistrationData<?>> getEventListenerRegistrations()
116    {
117        return eventListeners.getRegistrations();
118    }
119
120    /**
121     * Returns a flag whether detail events are enabled.
122     *
123     * @return a flag if detail events are generated
124     */
125    public boolean isDetailEvents()
126    {
127        return checkDetailEvents(0);
128    }
129
130    /**
131     * Determines whether detail events should be generated. If enabled, some
132     * methods can generate multiple update events. Note that this method
133     * records the number of calls, i.e. if for instance
134     * {@code setDetailEvents(false)} was called three times, you will
135     * have to invoke the method as often to enable the details.
136     *
137     * @param enable a flag if detail events should be enabled or disabled
138     */
139    public void setDetailEvents(final boolean enable)
140    {
141        synchronized (lockDetailEventsCount)
142        {
143            if (enable)
144            {
145                detailEvents++;
146            }
147            else
148            {
149                detailEvents--;
150            }
151        }
152    }
153
154    @Override
155    public <T extends Event> void addEventListener(final EventType<T> eventType,
156            final EventListener<? super T> listener)
157    {
158        eventListeners.addEventListener(eventType, listener);
159    }
160
161    @Override
162    public <T extends Event> boolean removeEventListener(
163            final EventType<T> eventType, final EventListener<? super T> listener)
164    {
165        return eventListeners.removeEventListener(eventType, listener);
166    }
167
168    /**
169     * Removes all registered event listeners.
170     */
171    public void clearEventListeners()
172    {
173        eventListeners.clear();
174    }
175
176    /**
177     * Removes all registered error listeners.
178     *
179     * @since 1.4
180     */
181    public void clearErrorListeners()
182    {
183        for (final EventListenerRegistrationData<? extends ConfigurationErrorEvent> reg : eventListeners
184                .getRegistrationsForSuperType(ConfigurationErrorEvent.ANY))
185        {
186            eventListeners.removeEventListener(reg);
187        }
188    }
189
190    /**
191     * Copies all event listener registrations maintained by this object to the
192     * specified {@code BaseEventSource} object.
193     *
194     * @param source the target source for the copy operation (must not be
195     *        <b>null</b>)
196     * @throws IllegalArgumentException if the target source is <b>null</b>
197     * @since 2.0
198     */
199    public void copyEventListeners(final BaseEventSource source)
200    {
201        if (source == null)
202        {
203            throw new IllegalArgumentException(
204                    "Target event source must not be null!");
205        }
206        source.eventListeners.addAll(eventListeners);
207    }
208
209    /**
210     * Creates an event object and delivers it to all registered event
211     * listeners. The method checks first if sending an event is allowed (making
212     * use of the {@code detailEvents} property), and if listeners are
213     * registered.
214     *
215     * @param type the event's type
216     * @param propName the name of the affected property (can be <b>null</b>)
217     * @param propValue the value of the affected property (can be <b>null</b>)
218     * @param before the before update flag
219     * @param <T> the type of the event to be fired
220     */
221    protected <T extends ConfigurationEvent> void fireEvent(final EventType<T> type,
222            final String propName, final Object propValue, final boolean before)
223    {
224        if (checkDetailEvents(-1))
225        {
226            final EventListenerList.EventListenerIterator<T> it =
227                    eventListeners.getEventListenerIterator(type);
228            if (it.hasNext())
229            {
230                final ConfigurationEvent event =
231                        createEvent(type, propName, propValue, before);
232                while (it.hasNext())
233                {
234                    it.invokeNext(event);
235                }
236            }
237        }
238    }
239
240    /**
241     * Creates a {@code ConfigurationEvent} object based on the passed in
242     * parameters. This method is called by {@code fireEvent()} if it decides
243     * that an event needs to be generated.
244     *
245     * @param type the event's type
246     * @param propName the name of the affected property (can be <b>null</b>)
247     * @param propValue the value of the affected property (can be <b>null</b>)
248     * @param before the before update flag
249     * @param <T> the type of the event to be created
250     * @return the newly created event object
251     */
252    protected <T extends ConfigurationEvent> ConfigurationEvent createEvent(
253            final EventType<T> type, final String propName, final Object propValue, final boolean before)
254    {
255        return new ConfigurationEvent(this, type, propName, propValue, before);
256    }
257
258    /**
259     * Creates an error event object and delivers it to all registered error
260     * listeners of a matching type.
261     *
262     * @param eventType the event's type
263     * @param operationType the type of the failed operation
264     * @param propertyName the name of the affected property (can be
265     *        <b>null</b>)
266     * @param propertyValue the value of the affected property (can be
267     *        <b>null</b>)
268     * @param cause the {@code Throwable} object that caused this error event
269     * @param <T> the event type
270     */
271    public <T extends ConfigurationErrorEvent> void fireError(
272            final EventType<T> eventType, final EventType<?> operationType,
273            final String propertyName, final Object propertyValue, final Throwable cause)
274    {
275        final EventListenerList.EventListenerIterator<T> iterator =
276                eventListeners.getEventListenerIterator(eventType);
277        if (iterator.hasNext())
278        {
279            final ConfigurationErrorEvent event =
280                    createErrorEvent(eventType, operationType, propertyName,
281                            propertyValue, cause);
282            while (iterator.hasNext())
283            {
284                iterator.invokeNext(event);
285            }
286        }
287    }
288
289    /**
290     * Creates a {@code ConfigurationErrorEvent} object based on the passed in
291     * parameters. This is called by {@code fireError()} if it decides that an
292     * event needs to be generated.
293     *
294     * @param type the event's type
295     * @param opType the operation type related to this error
296     * @param propName the name of the affected property (can be <b>null</b>)
297     * @param propValue the value of the affected property (can be <b>null</b>)
298     * @param ex the {@code Throwable} object that caused this error event
299     * @return the event object
300     */
301    protected ConfigurationErrorEvent createErrorEvent(
302            final EventType<? extends ConfigurationErrorEvent> type,
303            final EventType<?> opType, final String propName, final Object propValue, final Throwable ex)
304    {
305        return new ConfigurationErrorEvent(this, type, opType, propName,
306                propValue, ex);
307    }
308
309    /**
310     * Overrides the {@code clone()} method to correctly handle so far
311     * registered event listeners. This implementation ensures that the clone
312     * will have empty event listener lists, i.e. the listeners registered at an
313     * {@code BaseEventSource} object will not be copied.
314     *
315     * @return the cloned object
316     * @throws CloneNotSupportedException if cloning is not allowed
317     * @since 1.4
318     */
319    @Override
320    protected Object clone() throws CloneNotSupportedException
321    {
322        final BaseEventSource copy = (BaseEventSource) super.clone();
323        copy.initListeners();
324        return copy;
325    }
326
327    /**
328     * Initializes the collections for storing registered event listeners.
329     */
330    private void initListeners()
331    {
332        eventListeners = new EventListenerList();
333    }
334
335    /**
336     * Helper method for checking the current counter for detail events. This
337     * method checks whether the counter is greater than the passed in limit.
338     *
339     * @param limit the limit to be compared to
340     * @return <b>true</b> if the counter is greater than the limit,
341     *         <b>false</b> otherwise
342     */
343    private boolean checkDetailEvents(final int limit)
344    {
345        synchronized (lockDetailEventsCount)
346        {
347            return detailEvents > limit;
348        }
349    }
350}