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.concurrent.CopyOnWriteArrayList; 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 mechanism. In this case these 033 * classes only need to call the {@code fireEvent()} 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} 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()} method that can be implemented as a 045 * combination of {@code clearProperty()} and {@code addProperty()}. 046 * With {@code detailEvents} 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 * {@link org.apache.commons.configuration.HierarchicalConfiguration HierarchicalConfiguration} 053 * for instance has a custom implementation of {@code setProperty()}, 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 * {@link ConfigurationErrorListener}. There is another set of 061 * methods dealing with event listeners of this type. The 062 * {@code fireError()} 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 1234617 2012-01-22 21:31:01Z oheger $ 068 * @since 1.3 069 */ 070 public class EventSource 071 { 072 /** A collection for the registered event listeners. */ 073 private Collection<ConfigurationListener> listeners; 074 075 /** A collection for the registered error listeners.*/ 076 private Collection<ConfigurationErrorListener> errorListeners; 077 078 /** A lock object for guarding access to the detail events counter. */ 079 private final Object lockDetailEventsCount = new Object(); 080 081 /** A counter for the detail events. */ 082 private int detailEvents; 083 084 /** 085 * Creates a new instance of {@code EventSource}. 086 */ 087 public EventSource() 088 { 089 initListeners(); 090 } 091 092 /** 093 * Adds a configuration listener to this object. 094 * 095 * @param l the listener to add 096 */ 097 public void addConfigurationListener(ConfigurationListener l) 098 { 099 checkListener(l); 100 listeners.add(l); 101 } 102 103 /** 104 * Removes the specified event listener so that it does not receive any 105 * further events caused by this object. 106 * 107 * @param l the listener to be removed 108 * @return a flag whether the event listener was found 109 */ 110 public boolean removeConfigurationListener(ConfigurationListener l) 111 { 112 return listeners.remove(l); 113 } 114 115 /** 116 * Returns a collection with all configuration event listeners that are 117 * currently registered at this object. 118 * 119 * @return a collection with the registered 120 * {@code ConfigurationListener}s (this collection is a snapshot 121 * of the currently registered listeners; manipulating it has no effect 122 * on this event source object) 123 */ 124 public Collection<ConfigurationListener> getConfigurationListeners() 125 { 126 return Collections.unmodifiableCollection(new ArrayList<ConfigurationListener>(listeners)); 127 } 128 129 /** 130 * Removes all registered configuration listeners. 131 */ 132 public void clearConfigurationListeners() 133 { 134 listeners.clear(); 135 } 136 137 /** 138 * Returns a flag whether detail events are enabled. 139 * 140 * @return a flag if detail events are generated 141 */ 142 public boolean isDetailEvents() 143 { 144 return checkDetailEvents(0); 145 } 146 147 /** 148 * Determines whether detail events should be generated. If enabled, some 149 * methods can generate multiple update events. Note that this method 150 * records the number of calls, i.e. if for instance 151 * {@code setDetailEvents(false)} was called three times, you will 152 * have to invoke the method as often to enable the details. 153 * 154 * @param enable a flag if detail events should be enabled or disabled 155 */ 156 public void setDetailEvents(boolean enable) 157 { 158 synchronized (lockDetailEventsCount) 159 { 160 if (enable) 161 { 162 detailEvents++; 163 } 164 else 165 { 166 detailEvents--; 167 } 168 } 169 } 170 171 /** 172 * Adds a new configuration error listener to this object. This listener 173 * will then be notified about internal problems. 174 * 175 * @param l the listener to register (must not be <b>null</b>) 176 * @since 1.4 177 */ 178 public void addErrorListener(ConfigurationErrorListener l) 179 { 180 checkListener(l); 181 errorListeners.add(l); 182 } 183 184 /** 185 * Removes the specified error listener so that it does not receive any 186 * further events caused by this object. 187 * 188 * @param l the listener to remove 189 * @return a flag whether the listener could be found and removed 190 * @since 1.4 191 */ 192 public boolean removeErrorListener(ConfigurationErrorListener l) 193 { 194 return errorListeners.remove(l); 195 } 196 197 /** 198 * Removes all registered error listeners. 199 * 200 * @since 1.4 201 */ 202 public void clearErrorListeners() 203 { 204 errorListeners.clear(); 205 } 206 207 /** 208 * Returns a collection with all configuration error listeners that are 209 * currently registered at this object. 210 * 211 * @return a collection with the registered 212 * {@code ConfigurationErrorListener}s (this collection is a 213 * snapshot of the currently registered listeners; it cannot be manipulated) 214 * @since 1.4 215 */ 216 public Collection<ConfigurationErrorListener> getErrorListeners() 217 { 218 return Collections.unmodifiableCollection(new ArrayList<ConfigurationErrorListener>(errorListeners)); 219 } 220 221 /** 222 * Creates an event object and delivers it to all registered event 223 * listeners. The method will check first if sending an event is allowed 224 * (making use of the {@code detailEvents} property), and if 225 * listeners are registered. 226 * 227 * @param type the event's type 228 * @param propName the name of the affected property (can be <b>null</b>) 229 * @param propValue the value of the affected property (can be <b>null</b>) 230 * @param before the before update flag 231 */ 232 protected void fireEvent(int type, String propName, Object propValue, boolean before) 233 { 234 if (checkDetailEvents(-1)) 235 { 236 Iterator<ConfigurationListener> it = listeners.iterator(); 237 if (it.hasNext()) 238 { 239 ConfigurationEvent event = 240 createEvent(type, propName, propValue, before); 241 while (it.hasNext()) 242 { 243 it.next().configurationChanged(event); 244 } 245 } 246 } 247 } 248 249 /** 250 * Creates a {@code ConfigurationEvent} object based on the passed in 251 * parameters. This is called by {@code fireEvent()} if it decides 252 * that an event needs to be generated. 253 * 254 * @param type the event's type 255 * @param propName the name of the affected property (can be <b>null</b>) 256 * @param propValue the value of the affected property (can be <b>null</b>) 257 * @param before the before update flag 258 * @return the newly created event object 259 */ 260 protected ConfigurationEvent createEvent(int type, String propName, Object propValue, boolean before) 261 { 262 return new ConfigurationEvent(this, type, propName, propValue, before); 263 } 264 265 /** 266 * Creates an error event object and delivers it to all registered error 267 * listeners. 268 * 269 * @param type the event's type 270 * @param propName the name of the affected property (can be <b>null</b>) 271 * @param propValue the value of the affected property (can be <b>null</b>) 272 * @param ex the {@code Throwable} object that caused this error event 273 * @since 1.4 274 */ 275 protected void fireError(int type, String propName, Object propValue, Throwable ex) 276 { 277 Iterator<ConfigurationErrorListener> it = errorListeners.iterator(); 278 if (it.hasNext()) 279 { 280 ConfigurationErrorEvent event = 281 createErrorEvent(type, propName, propValue, ex); 282 while (it.hasNext()) 283 { 284 it.next().configurationError(event); 285 } 286 } 287 } 288 289 /** 290 * Creates a {@code ConfigurationErrorEvent} object based on the 291 * passed in parameters. This is called by {@code fireError()} if it 292 * decides that an event needs to be generated. 293 * 294 * @param type the event's type 295 * @param propName the name of the affected property (can be <b>null</b>) 296 * @param propValue the value of the affected property (can be <b>null</b>) 297 * @param ex the {@code Throwable} object that caused this error 298 * event 299 * @return the event object 300 * @since 1.4 301 */ 302 protected ConfigurationErrorEvent createErrorEvent(int type, String propName, Object propValue, Throwable ex) 303 { 304 return new ConfigurationErrorEvent(this, type, propName, propValue, ex); 305 } 306 307 /** 308 * Overrides the {@code clone()} method to correctly handle so far 309 * registered event listeners. This implementation ensures that the clone 310 * will have empty event listener lists, i.e. the listeners registered at an 311 * {@code EventSource} object will not be copied. 312 * 313 * @return the cloned object 314 * @throws CloneNotSupportedException if cloning is not allowed 315 * @since 1.4 316 */ 317 @Override 318 protected Object clone() throws CloneNotSupportedException 319 { 320 EventSource copy = (EventSource) super.clone(); 321 copy.initListeners(); 322 return copy; 323 } 324 325 /** 326 * Checks whether the specified event listener is not <b>null</b>. If this 327 * is the case, an {@code IllegalArgumentException} exception is thrown. 328 * 329 * @param l the listener to be checked 330 * @throws IllegalArgumentException if the listener is <b>null</b> 331 */ 332 private static void checkListener(Object l) 333 { 334 if (l == null) 335 { 336 throw new IllegalArgumentException("Listener must not be null!"); 337 } 338 } 339 340 /** 341 * Initializes the collections for storing registered event listeners. 342 */ 343 private void initListeners() 344 { 345 listeners = new CopyOnWriteArrayList<ConfigurationListener>(); 346 errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>(); 347 } 348 349 /** 350 * Helper method for checking the current counter for detail events. This 351 * method checks whether the counter is greater than the passed in limit. 352 * 353 * @param limit the limit to be compared to 354 * @return <b>true</b> if the counter is greater than the limit, 355 * <b>false</b> otherwise 356 */ 357 private boolean checkDetailEvents(int limit) 358 { 359 synchronized (lockDetailEventsCount) 360 { 361 return detailEvents > limit; 362 } 363 } 364 }