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