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
19 import java.util.Collection;
20 import java.util.Collections;
21 import java.util.LinkedList;
22 import java.util.List;
23
24 /**
25 * <p>
26 * A base class for objects that can generate configuration events.
27 * </p>
28 * <p>
29 * This class implements functionality for managing a set of event listeners that can be notified when an event occurs.
30 * It can be extended by configuration classes that support the event mechanism. In this case these classes only need to
31 * call the {@code fireEvent()} method when an event is to be delivered to the registered listeners.
32 * </p>
33 * <p>
34 * Adding and removing event listeners can happen concurrently to manipulations on a configuration that cause events.
35 * The operations are synchronized.
36 * </p>
37 * <p>
38 * With the {@code detailEvents} property the number of detail events can be controlled. Some methods in configuration
39 * classes are implemented in a way that they call other methods that can generate their own events. One example is the
40 * {@code setProperty()} method that can be implemented as a combination of {@code clearProperty()} and
41 * {@code addProperty()}. With {@code detailEvents} set to <b>true</b>, all involved methods will generate events (i.e.
42 * listeners will receive property set events, property clear events, and property add events). If this mode is turned
43 * off (which is the default), detail events are suppressed, so only property set events will be received. Note that the
44 * number of received detail events may differ for different configuration implementations.
45 * {@link org.apache.commons.configuration2.BaseHierarchicalConfiguration BaseHierarchicalConfiguration} for instance
46 * has a custom implementation of {@code setProperty()}, which does not generate any detail events.
47 * </p>
48 * <p>
49 * In addition to "normal" events, error events are supported. Such events signal an internal problem that
50 * occurred during access of properties. They are handled via the regular {@link EventListener} interface, but there are
51 * special event types defined by {@link ConfigurationErrorEvent}. The {@code fireError()} method can be used by derived
52 * classes to send notifications about errors to registered observers.
53 * </p>
54 *
55 * @since 1.3
56 */
57 public class BaseEventSource implements EventSource {
58 /** The list for managing registered event listeners. */
59 private EventListenerList eventListeners;
60
61 /** A lock object for guarding access to the detail events counter. */
62 private final Object lockDetailEventsCount = new Object();
63
64 /** A counter for the detail events. */
65 private int detailEvents;
66
67 /**
68 * Creates a new instance of {@code BaseEventSource}.
69 */
70 public BaseEventSource() {
71 initListeners();
72 }
73
74 /**
75 * Gets a collection with all event listeners of the specified event type that are currently registered at this
76 * object.
77 *
78 * @param eventType the event type object
79 * @param <T> the event type
80 * @return a collection with the event listeners of the specified event type (this collection is a snapshot of the
81 * currently registered listeners; it cannot be manipulated)
82 */
83 public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
84 final List<EventListener<? super T>> result = new LinkedList<>();
85 eventListeners.getEventListeners(eventType).forEach(result::add);
86 return Collections.unmodifiableCollection(result);
87 }
88
89 /**
90 * Gets a list with all {@code EventListenerRegistrationData} objects currently contained for this event source. This
91 * method allows access to all registered event listeners, independent on their type.
92 *
93 * @return a list with information about all registered event listeners
94 */
95 public List<EventListenerRegistrationData<?>> getEventListenerRegistrations() {
96 return eventListeners.getRegistrations();
97 }
98
99 /**
100 * Returns a flag whether detail events are enabled.
101 *
102 * @return a flag if detail events are generated
103 */
104 public boolean isDetailEvents() {
105 return checkDetailEvents(0);
106 }
107
108 /**
109 * Determines whether detail events should be generated. If enabled, some methods can generate multiple update events.
110 * Note that this method records the number of calls, i.e. if for instance {@code setDetailEvents(false)} was called
111 * three times, you will have to invoke the method as often to enable the details.
112 *
113 * @param enable a flag if detail events should be enabled or disabled
114 */
115 public void setDetailEvents(final boolean enable) {
116 synchronized (lockDetailEventsCount) {
117 if (enable) {
118 detailEvents++;
119 } else {
120 detailEvents--;
121 }
122 }
123 }
124
125 @Override
126 public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
127 eventListeners.addEventListener(eventType, listener);
128 }
129
130 @Override
131 public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
132 return eventListeners.removeEventListener(eventType, listener);
133 }
134
135 /**
136 * Removes all registered event listeners.
137 */
138 public void clearEventListeners() {
139 eventListeners.clear();
140 }
141
142 /**
143 * Removes all registered error listeners.
144 *
145 * @since 1.4
146 */
147 public void clearErrorListeners() {
148 eventListeners.getRegistrationsForSuperType(ConfigurationErrorEvent.ANY).forEach(eventListeners::removeEventListener);
149 }
150
151 /**
152 * Copies all event listener registrations maintained by this object to the specified {@code BaseEventSource} object.
153 *
154 * @param source the target source for the copy operation (must not be <b>null</b>)
155 * @throws IllegalArgumentException if the target source is <b>null</b>
156 * @since 2.0
157 */
158 public void copyEventListeners(final BaseEventSource source) {
159 if (source == null) {
160 throw new IllegalArgumentException("Target event source must not be null!");
161 }
162 source.eventListeners.addAll(eventListeners);
163 }
164
165 /**
166 * Creates an event object and delivers it to all registered event listeners. The method checks first if sending an
167 * event is allowed (making use of the {@code detailEvents} property), and if listeners are registered.
168 *
169 * @param type the event's type
170 * @param propName the name of the affected property (can be <b>null</b>)
171 * @param propValue the value of the affected property (can be <b>null</b>)
172 * @param before the before update flag
173 * @param <T> the type of the event to be fired
174 */
175 protected <T extends ConfigurationEvent> void fireEvent(final EventType<T> type, final String propName, final Object propValue, final boolean before) {
176 if (checkDetailEvents(-1)) {
177 final EventListenerList.EventListenerIterator<T> it = eventListeners.getEventListenerIterator(type);
178 if (it.hasNext()) {
179 final ConfigurationEvent event = createEvent(type, propName, propValue, before);
180 while (it.hasNext()) {
181 it.invokeNext(event);
182 }
183 }
184 }
185 }
186
187 /**
188 * Creates a {@code ConfigurationEvent} object based on the passed in parameters. This method is called by
189 * {@code fireEvent()} if it decides that an event needs to be generated.
190 *
191 * @param type the event's type
192 * @param propName the name of the affected property (can be <b>null</b>)
193 * @param propValue the value of the affected property (can be <b>null</b>)
194 * @param before the before update flag
195 * @param <T> the type of the event to be created
196 * @return the newly created event object
197 */
198 protected <T extends ConfigurationEvent> ConfigurationEvent createEvent(final EventType<T> type, final String propName, final Object propValue,
199 final boolean before) {
200 return new ConfigurationEvent(this, type, propName, propValue, before);
201 }
202
203 /**
204 * Creates an error event object and delivers it to all registered error listeners of a matching type.
205 *
206 * @param eventType the event's type
207 * @param operationType the type of the failed operation
208 * @param propertyName the name of the affected property (can be <b>null</b>)
209 * @param propertyValue the value of the affected property (can be <b>null</b>)
210 * @param cause the {@code Throwable} object that caused this error event
211 * @param <T> the event type
212 */
213 public <T extends ConfigurationErrorEvent> void fireError(final EventType<T> eventType, final EventType<?> operationType, final String propertyName,
214 final Object propertyValue, final Throwable cause) {
215 final EventListenerList.EventListenerIterator<T> iterator = eventListeners.getEventListenerIterator(eventType);
216 if (iterator.hasNext()) {
217 final ConfigurationErrorEvent event = createErrorEvent(eventType, operationType, propertyName, propertyValue, cause);
218 while (iterator.hasNext()) {
219 iterator.invokeNext(event);
220 }
221 }
222 }
223
224 /**
225 * Creates a {@code ConfigurationErrorEvent} object based on the passed in parameters. This is called by
226 * {@code fireError()} if it decides that an event needs to be generated.
227 *
228 * @param type the event's type
229 * @param opType the operation type related to this error
230 * @param propName the name of the affected property (can be <b>null</b>)
231 * @param propValue the value of the affected property (can be <b>null</b>)
232 * @param ex the {@code Throwable} object that caused this error event
233 * @return the event object
234 */
235 protected ConfigurationErrorEvent createErrorEvent(final EventType<? extends ConfigurationErrorEvent> type, final EventType<?> opType,
236 final String propName, final Object propValue, final Throwable ex) {
237 return new ConfigurationErrorEvent(this, type, opType, propName, propValue, ex);
238 }
239
240 /**
241 * Overrides the {@code clone()} method to correctly handle so far registered event listeners. This implementation
242 * ensures that the clone will have empty event listener lists, i.e. the listeners registered at an
243 * {@code BaseEventSource} object will not be copied.
244 *
245 * @return the cloned object
246 * @throws CloneNotSupportedException if cloning is not allowed
247 * @since 1.4
248 */
249 @Override
250 protected Object clone() throws CloneNotSupportedException {
251 final BaseEventSource copy = (BaseEventSource) super.clone();
252 copy.initListeners();
253 return copy;
254 }
255
256 /**
257 * Initializes the collections for storing registered event listeners.
258 */
259 private void initListeners() {
260 eventListeners = new EventListenerList();
261 }
262
263 /**
264 * Helper method for checking the current counter for detail events. This method checks whether the counter is greater
265 * than the passed in limit.
266 *
267 * @param limit the limit to be compared to
268 * @return <b>true</b> if the counter is greater than the limit, <b>false</b> otherwise
269 */
270 private boolean checkDetailEvents(final int limit) {
271 synchronized (lockDetailEventsCount) {
272 return detailEvents > limit;
273 }
274 }
275 }