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 */ 017 package org.apache.commons.configuration.event; 018 019 import java.util.ArrayList; 020 import java.util.Collection; 021 import java.util.Collections; 022 import java.util.Iterator; 023 import java.util.LinkedList; 024 025 /** 026 * <p> 027 * A base class for objects that can generate configuration events. 028 * </p> 029 * <p> 030 * This class implements functionality for managing a set of event listeners 031 * that can be notified when an event occurs. It can be extended by 032 * configuration classes that support the event machanism. In this case these 033 * classes only need to call the <code>fireEvent()</code> method when an event 034 * is to be delivered to the registered listeners. 035 * </p> 036 * <p> 037 * Adding and removing event listeners can happen concurrently to manipulations 038 * on a configuration that cause events. The operations are synchronized. 039 * </p> 040 * <p> 041 * With the <code>detailEvents</code> property the number of detail events can 042 * be controlled. Some methods in configuration classes are implemented in a way 043 * that they call other methods that can generate their own events. One example 044 * is the <code>setProperty()</code> method that can be implemented as a 045 * combination of <code>clearProperty()</code> and <code>addProperty()</code>. 046 * With <code>detailEvents</code> set to <b>true</b>, all involved methods 047 * will generate events (i.e. listeners will receive property set events, 048 * property clear events, and property add events). If this mode is turned off 049 * (which is the default), detail events are suppressed, so only property set 050 * events will be received. Note that the number of received detail events may 051 * differ for different configuration implementations. 052 * <code>{@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration}</code> 053 * for instance has a custom implementation of <code>setProperty()</code>, 054 * which does not generate any detail events. 055 * </p> 056 * <p> 057 * In addition to "normal" events, error events are supported. Such 058 * events signal an internal problem that occurred during access of properties. 059 * For them a special listener interface exists: 060 * <code>{@link ConfigurationErrorListener}</code>. There is another set of 061 * methods dealing with event listeners of this type. The 062 * <code>fireError()</code> method can be used by derived classes to send 063 * notifications about errors to registered observers. 064 * </p> 065 * 066 * @author <a href="http://commons.apache.org/configuration/team-list.html">Commons Configuration team</a> 067 * @version $Id: EventSource.java 561230 2007-07-31 04:17:09Z rahul $ 068 * @since 1.3 069 */ 070 public class EventSource 071 { 072 /** A collection for the registered event listeners. */ 073 private Collection listeners; 074 075 /** A collection for the registered error listeners.*/ 076 private Collection errorListeners; 077 078 /** A counter for the detail events. */ 079 private int detailEvents; 080 081 /** 082 * Creates a new instance of <code>EventSource</code>. 083 */ 084 public EventSource() 085 { 086 initListeners(); 087 } 088 089 /** 090 * Adds a configuration listener to this object. 091 * 092 * @param l the listener to add 093 */ 094 public void addConfigurationListener(ConfigurationListener l) 095 { 096 doAddListener(listeners, l); 097 } 098 099 /** 100 * Removes the specified event listener so that it does not receive any 101 * further events caused by this object. 102 * 103 * @param l the listener to be removed 104 * @return a flag whether the event listener was found 105 */ 106 public boolean removeConfigurationListener(ConfigurationListener l) 107 { 108 return doRemoveListener(listeners, l); 109 } 110 111 /** 112 * Returns a collection with all configuration event listeners that are 113 * currently registered at this object. 114 * 115 * @return a collection with the registered 116 * <code>ConfigurationListener</code>s (this collection is a snapshot 117 * of the currently registered listeners; manipulating it has no effect 118 * on this event source object) 119 */ 120 public Collection getConfigurationListeners() 121 { 122 return doGetListeners(listeners); 123 } 124 125 /** 126 * Removes all registered configuration listeners. 127 */ 128 public void clearConfigurationListeners() 129 { 130 doClearListeners(listeners); 131 } 132 133 /** 134 * Returns a flag whether detail events are enabled. 135 * 136 * @return a flag if detail events are generated 137 */ 138 public boolean isDetailEvents() 139 { 140 synchronized (listeners) 141 { 142 return detailEvents > 0; 143 } 144 } 145 146 /** 147 * Determines whether detail events should be generated. If enabled, some 148 * methods can generate multiple update events. Note that this method 149 * records the number of calls, i.e. if for instance 150 * <code>setDetailEvents(false)</code> was called three times, you will 151 * have to invoke the method as often to enable the details. 152 * 153 * @param enable a flag if detail events should be enabled or disabled 154 */ 155 public void setDetailEvents(boolean enable) 156 { 157 synchronized (listeners) 158 { 159 if (enable) 160 { 161 detailEvents++; 162 } 163 else 164 { 165 detailEvents--; 166 } 167 } 168 } 169 170 /** 171 * Adds a new configuration error listener to this object. This listener 172 * will then be notified about internal problems. 173 * 174 * @param l the listener to register (must not be <b>null</b>) 175 * @since 1.4 176 */ 177 public void addErrorListener(ConfigurationErrorListener l) 178 { 179 doAddListener(errorListeners, l); 180 } 181 182 /** 183 * Removes the specified error listener so that it does not receive any 184 * further events caused by this object. 185 * 186 * @param l the listener to remove 187 * @return a flag whether the listener could be found and removed 188 * @since 1.4 189 */ 190 public boolean removeErrorListener(ConfigurationErrorListener l) 191 { 192 return doRemoveListener(errorListeners, l); 193 } 194 195 /** 196 * Removes all registered error listeners. 197 * 198 * @since 1.4 199 */ 200 public void clearErrorListeners() 201 { 202 doClearListeners(errorListeners); 203 } 204 205 /** 206 * Returns a collection with all configuration error listeners that are 207 * currently registered at this object. 208 * 209 * @return a collection with the registered 210 * <code>ConfigurationErrorListener</code>s (this collection is a 211 * snapshot of the currently registered listeners; it cannot be manipulated) 212 * @since 1.4 213 */ 214 public Collection getErrorListeners() 215 { 216 return doGetListeners(errorListeners); 217 } 218 219 /** 220 * Creates an event object and delivers it to all registered event 221 * listeners. The method will check first if sending an event is allowed 222 * (making use of the <code>detailEvents</code> property), and if 223 * listeners are registered. 224 * 225 * @param type the event's type 226 * @param propName the name of the affected property (can be <b>null</b>) 227 * @param propValue the value of the affected property (can be <b>null</b>) 228 * @param before the before update flag 229 */ 230 protected void fireEvent(int type, String propName, Object propValue, boolean before) 231 { 232 Collection listenersToCall = null; 233 234 synchronized (listeners) 235 { 236 if (detailEvents >= 0 && listeners.size() > 0) 237 { 238 // Copy listeners to another collection so that manipulating 239 // the listener list during event delivery won't cause problems 240 listenersToCall = new ArrayList(listeners); 241 } 242 } 243 244 if (listenersToCall != null) 245 { 246 ConfigurationEvent event = createEvent(type, propName, propValue, before); 247 for (Iterator it = listenersToCall.iterator(); it.hasNext();) 248 { 249 ((ConfigurationListener) it.next()).configurationChanged(event); 250 } 251 } 252 } 253 254 /** 255 * Creates a <code>ConfigurationEvent</code> object based on the passed in 256 * parameters. This is called by <code>fireEvent()</code> if it decides 257 * that an event needs to be generated. 258 * 259 * @param type the event's type 260 * @param propName the name of the affected property (can be <b>null</b>) 261 * @param propValue the value of the affected property (can be <b>null</b>) 262 * @param before the before update flag 263 * @return the newly created event object 264 */ 265 protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before) 266 { 267 return new ConfigurationEvent(this, type, propName, propValue, before); 268 } 269 270 /** 271 * Creates an error event object and delivers it to all registered error 272 * listeners. 273 * 274 * @param type the event's type 275 * @param propName the name of the affected property (can be <b>null</b>) 276 * @param propValue the value of the affected property (can be <b>null</b>) 277 * @param ex the <code>Throwable</code> object that caused this error event 278 * @since 1.4 279 */ 280 protected void fireError(int type, String propName, Object propValue, Throwable ex) 281 { 282 Collection listenersToCall = null; 283 284 synchronized (errorListeners) 285 { 286 if (errorListeners.size() > 0) 287 { 288 // Copy listeners to another collection so that manipulating 289 // the listener list during event delivery won't cause problems 290 listenersToCall = new ArrayList(errorListeners); 291 } 292 } 293 294 if (listenersToCall != null) 295 { 296 ConfigurationErrorEvent event = createErrorEvent(type, propName, propValue, ex); 297 for (Iterator it = listenersToCall.iterator(); it.hasNext();) 298 { 299 ((ConfigurationErrorListener) it.next()).configurationError(event); 300 } 301 } 302 } 303 304 /** 305 * Creates a <code>ConfigurationErrorEvent</code> object based on the 306 * passed in parameters. This is called by <code>fireError()</code> if it 307 * decides that an event needs to be generated. 308 * 309 * @param type the event's type 310 * @param propName the name of the affected property (can be <b>null</b>) 311 * @param propValue the value of the affected property (can be <b>null</b>) 312 * @param ex the <code>Throwable</code> object that caused this error 313 * event 314 * @return the event object 315 * @since 1.4 316 */ 317 protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex) 318 { 319 return new ConfigurationErrorEvent(this, type, propName, propValue, ex); 320 } 321 322 /** 323 * Overrides the <code>clone()</code> method to correctly handle so far 324 * registered event listeners. This implementation ensures that the clone 325 * will have empty event listener lists, i.e. the listeners registered at an 326 * <code>EventSource</code> object will not be copied. 327 * 328 * @return the cloned object 329 * @throws CloneNotSupportedException if cloning is not allowed 330 * @since 1.4 331 */ 332 protected Object clone() throws CloneNotSupportedException 333 { 334 EventSource copy = (EventSource) super.clone(); 335 copy.initListeners(); 336 return copy; 337 } 338 339 /** 340 * Adds a new listener object to a listener collection. This is done in a 341 * synchronized block. The listener must not be <b>null</b>. 342 * 343 * @param listeners the collection with the listeners 344 * @param l the listener object 345 */ 346 private static void doAddListener(Collection listeners, Object l) 347 { 348 if (l == null) 349 { 350 throw new IllegalArgumentException("Listener must not be null!"); 351 } 352 synchronized (listeners) 353 { 354 listeners.add(l); 355 } 356 } 357 358 /** 359 * Removes an event listener from a listener collection. This is done in a 360 * synchronized block. 361 * 362 * @param listeners the collection with the listeners 363 * @param l the listener object 364 * @return a flag whether the listener could be found and removed 365 */ 366 private static boolean doRemoveListener(Collection listeners, Object l) 367 { 368 synchronized (listeners) 369 { 370 return listeners.remove(l); 371 } 372 } 373 374 /** 375 * Removes all entries from the given list of event listeners. 376 * 377 * @param listeners the collection with the listeners 378 */ 379 private static void doClearListeners(Collection listeners) 380 { 381 synchronized (listeners) 382 { 383 listeners.clear(); 384 } 385 } 386 387 /** 388 * Returns an unmodifiable snapshot of the given event listener collection. 389 * 390 * @param listeners the collection with the listeners 391 * @return a snapshot of the listeners collection 392 */ 393 private static Collection doGetListeners(Collection listeners) 394 { 395 synchronized (listeners) 396 { 397 return Collections.unmodifiableCollection(new ArrayList(listeners)); 398 } 399 } 400 401 /** 402 * Initializes the collections for storing registered event listeners. 403 */ 404 private void initListeners() 405 { 406 listeners = new LinkedList(); 407 errorListeners = new LinkedList(); 408 } 409 }