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 * @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}