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 "normal" 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 * @version $Id: BaseEventSource.java 1842194 2018-09-27 22:24:23Z ggregory $ 065 * @since 1.3 066 */ 067public class BaseEventSource implements EventSource 068{ 069 /** The list for managing registered event listeners. */ 070 private EventListenerList eventListeners; 071 072 /** A lock object for guarding access to the detail events counter. */ 073 private final Object lockDetailEventsCount = new Object(); 074 075 /** A counter for the detail events. */ 076 private int detailEvents; 077 078 /** 079 * Creates a new instance of {@code BaseEventSource}. 080 */ 081 public BaseEventSource() 082 { 083 initListeners(); 084 } 085 086 /** 087 * Returns a collection with all event listeners of the specified event type 088 * that are currently registered at this object. 089 * 090 * @param eventType the event type object 091 * @param <T> the event type 092 * @return a collection with the event listeners of the specified event type 093 * (this collection is a snapshot of the currently registered 094 * listeners; it cannot be manipulated) 095 */ 096 public <T extends Event> Collection<EventListener<? super T>> getEventListeners( 097 final EventType<T> eventType) 098 { 099 final List<EventListener<? super T>> result = 100 new LinkedList<>(); 101 for (final EventListener<? super T> l : eventListeners 102 .getEventListeners(eventType)) 103 { 104 result.add(l); 105 } 106 return Collections.unmodifiableCollection(result); 107 } 108 109 /** 110 * Returns a list with all {@code EventListenerRegistrationData} objects 111 * currently contained for this event source. This method allows access to 112 * all registered event listeners, independent on their type. 113 * 114 * @return a list with information about all registered event listeners 115 */ 116 public List<EventListenerRegistrationData<?>> getEventListenerRegistrations() 117 { 118 return eventListeners.getRegistrations(); 119 } 120 121 /** 122 * Returns a flag whether detail events are enabled. 123 * 124 * @return a flag if detail events are generated 125 */ 126 public boolean isDetailEvents() 127 { 128 return checkDetailEvents(0); 129 } 130 131 /** 132 * Determines whether detail events should be generated. If enabled, some 133 * methods can generate multiple update events. Note that this method 134 * records the number of calls, i.e. if for instance 135 * {@code setDetailEvents(false)} was called three times, you will 136 * have to invoke the method as often to enable the details. 137 * 138 * @param enable a flag if detail events should be enabled or disabled 139 */ 140 public void setDetailEvents(final boolean enable) 141 { 142 synchronized (lockDetailEventsCount) 143 { 144 if (enable) 145 { 146 detailEvents++; 147 } 148 else 149 { 150 detailEvents--; 151 } 152 } 153 } 154 155 @Override 156 public <T extends Event> void addEventListener(final EventType<T> eventType, 157 final EventListener<? super T> listener) 158 { 159 eventListeners.addEventListener(eventType, listener); 160 } 161 162 @Override 163 public <T extends Event> boolean removeEventListener( 164 final EventType<T> eventType, final EventListener<? super T> listener) 165 { 166 return eventListeners.removeEventListener(eventType, listener); 167 } 168 169 /** 170 * Removes all registered event listeners. 171 */ 172 public void clearEventListeners() 173 { 174 eventListeners.clear(); 175 } 176 177 /** 178 * Removes all registered error listeners. 179 * 180 * @since 1.4 181 */ 182 public void clearErrorListeners() 183 { 184 for (final EventListenerRegistrationData<? extends ConfigurationErrorEvent> reg : eventListeners 185 .getRegistrationsForSuperType(ConfigurationErrorEvent.ANY)) 186 { 187 eventListeners.removeEventListener(reg); 188 } 189 } 190 191 /** 192 * Copies all event listener registrations maintained by this object to the 193 * specified {@code BaseEventSource} object. 194 * 195 * @param source the target source for the copy operation (must not be 196 * <b>null</b>) 197 * @throws IllegalArgumentException if the target source is <b>null</b> 198 * @since 2.0 199 */ 200 public void copyEventListeners(final BaseEventSource source) 201 { 202 if (source == null) 203 { 204 throw new IllegalArgumentException( 205 "Target event source must not be null!"); 206 } 207 source.eventListeners.addAll(eventListeners); 208 } 209 210 /** 211 * Creates an event object and delivers it to all registered event 212 * listeners. The method checks first if sending an event is allowed (making 213 * use of the {@code detailEvents} property), and if listeners are 214 * registered. 215 * 216 * @param type the event's type 217 * @param propName the name of the affected property (can be <b>null</b>) 218 * @param propValue the value of the affected property (can be <b>null</b>) 219 * @param before the before update flag 220 * @param <T> the type of the event to be fired 221 */ 222 protected <T extends ConfigurationEvent> void fireEvent(final EventType<T> type, 223 final String propName, final Object propValue, final boolean before) 224 { 225 if (checkDetailEvents(-1)) 226 { 227 final EventListenerList.EventListenerIterator<T> it = 228 eventListeners.getEventListenerIterator(type); 229 if (it.hasNext()) 230 { 231 final ConfigurationEvent event = 232 createEvent(type, propName, propValue, before); 233 while (it.hasNext()) 234 { 235 it.invokeNext(event); 236 } 237 } 238 } 239 } 240 241 /** 242 * Creates a {@code ConfigurationEvent} object based on the passed in 243 * parameters. This method is called by {@code fireEvent()} if it decides 244 * that an event needs to be generated. 245 * 246 * @param type the event's type 247 * @param propName the name of the affected property (can be <b>null</b>) 248 * @param propValue the value of the affected property (can be <b>null</b>) 249 * @param before the before update flag 250 * @param <T> the type of the event to be created 251 * @return the newly created event object 252 */ 253 protected <T extends ConfigurationEvent> ConfigurationEvent createEvent( 254 final EventType<T> type, final String propName, final Object propValue, final boolean before) 255 { 256 return new ConfigurationEvent(this, type, propName, propValue, before); 257 } 258 259 /** 260 * Creates an error event object and delivers it to all registered error 261 * listeners of a matching type. 262 * 263 * @param eventType the event's type 264 * @param operationType the type of the failed operation 265 * @param propertyName the name of the affected property (can be 266 * <b>null</b>) 267 * @param propertyValue the value of the affected property (can be 268 * <b>null</b>) 269 * @param cause the {@code Throwable} object that caused this error event 270 * @param <T> the event type 271 */ 272 public <T extends ConfigurationErrorEvent> void fireError( 273 final EventType<T> eventType, final EventType<?> operationType, 274 final String propertyName, final Object propertyValue, final Throwable cause) 275 { 276 final EventListenerList.EventListenerIterator<T> iterator = 277 eventListeners.getEventListenerIterator(eventType); 278 if (iterator.hasNext()) 279 { 280 final ConfigurationErrorEvent event = 281 createErrorEvent(eventType, operationType, propertyName, 282 propertyValue, cause); 283 while (iterator.hasNext()) 284 { 285 iterator.invokeNext(event); 286 } 287 } 288 } 289 290 /** 291 * Creates a {@code ConfigurationErrorEvent} object based on the passed in 292 * parameters. This is called by {@code fireError()} if it decides that an 293 * event needs to be generated. 294 * 295 * @param type the event's type 296 * @param opType the operation type related to this error 297 * @param propName the name of the affected property (can be <b>null</b>) 298 * @param propValue the value of the affected property (can be <b>null</b>) 299 * @param ex the {@code Throwable} object that caused this error event 300 * @return the event object 301 */ 302 protected ConfigurationErrorEvent createErrorEvent( 303 final EventType<? extends ConfigurationErrorEvent> type, 304 final EventType<?> opType, final String propName, final Object propValue, final Throwable ex) 305 { 306 return new ConfigurationErrorEvent(this, type, opType, propName, 307 propValue, ex); 308 } 309 310 /** 311 * Overrides the {@code clone()} method to correctly handle so far 312 * registered event listeners. This implementation ensures that the clone 313 * will have empty event listener lists, i.e. the listeners registered at an 314 * {@code BaseEventSource} object will not be copied. 315 * 316 * @return the cloned object 317 * @throws CloneNotSupportedException if cloning is not allowed 318 * @since 1.4 319 */ 320 @Override 321 protected Object clone() throws CloneNotSupportedException 322 { 323 final BaseEventSource copy = (BaseEventSource) super.clone(); 324 copy.initListeners(); 325 return copy; 326 } 327 328 /** 329 * Initializes the collections for storing registered event listeners. 330 */ 331 private void initListeners() 332 { 333 eventListeners = new EventListenerList(); 334 } 335 336 /** 337 * Helper method for checking the current counter for detail events. This 338 * method checks whether the counter is greater than the passed in limit. 339 * 340 * @param limit the limit to be compared to 341 * @return <b>true</b> if the counter is greater than the limit, 342 * <b>false</b> otherwise 343 */ 344 private boolean checkDetailEvents(final int limit) 345 { 346 synchronized (lockDetailEventsCount) 347 { 348 return detailEvents > limit; 349 } 350 } 351}