001 /*
002 * Copyright 2003-2004 The Apache Software Foundation
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package org.apache.commons.events.observable.standard;
017
018 import java.util.Collection;
019
020 import org.apache.commons.events.observable.ModificationEventType;
021 import org.apache.commons.events.observable.ModificationHandler;
022 import org.apache.commons.events.observable.ModificationHandlerFactory;
023 import org.apache.commons.events.observable.ObservableCollection;
024
025 /**
026 * The standard implementation of a <code>ModificationHandler</code> that
027 * sends standard JavaBean style events to listeners.
028 * <p>
029 * The information gathered by this implementation is all that is available
030 * as parameters or return values.
031 * In addition, the <code>size</code> method is used on the collection.
032 * All objects used are the real objects from the method calls, not clones.
033 * <p>
034 * Each listener can be filtered. There are separate filters for pre and post
035 * modification events.
036 *
037 * @since Commons Events 1.0
038 * @version $Revision: 155443 $ $Date: 2005-02-26 13:19:51 +0000 (Sat, 26 Feb 2005) $
039 *
040 * @author Stephen Colebourne
041 */
042 public class StandardModificationHandler extends ModificationHandler {
043
044 /** The singleton factory */
045 public static final ModificationHandlerFactory FACTORY = new Factory();
046
047 /** A reusable empty holders array. */
048 protected static final PreHolder[] EMPTY_PRE_HOLDERS = new PreHolder[0];
049 /** A reusable empty holders array. */
050 protected static final PostHolder[] EMPTY_POST_HOLDERS = new PostHolder[0];
051
052 /** The event mask as to which event types to send on pre events. */
053 protected int preMask = ModificationEventType.GROUP_NONE;
054 /** The event mask as to which event types to send on post events. */
055 protected int postMask = ModificationEventType.GROUP_NONE;
056
057 /** The event listeners. */
058 protected PreHolder[] preHolder = EMPTY_PRE_HOLDERS;
059 /** The event listeners. */
060 protected PostHolder[] postHolder = EMPTY_POST_HOLDERS;
061 /**
062 * Temporary store for the size.
063 * This makes the class thread-unsafe, but you should sync collections anyway.
064 */
065 protected int preSize;
066
067 // Constructors
068 //-----------------------------------------------------------------------
069 /**
070 * Constructor the creates the handler but leaves it invalid.
071 * <p>
072 * The handler can only be used after it has been properly initialized.
073 * This is normally done automatically by
074 * {@link ObservableCollection#decorate(Collection, Object)}.
075 */
076 public StandardModificationHandler() {
077 super();
078 }
079
080 /**
081 * Constructor the creates the handler but leaves it invalid.
082 * <p>
083 * The handler can only be used after it has been properly initialized.
084 * This is normally done automatically by
085 * {@link ObservableCollection#decorate(Collection, Object)}.
086 *
087 * @param pre the pre listener
088 * @param preMask the mask for the pre listener
089 * @param post the post listener
090 * @param postMask the mask for the post listener
091 */
092 public StandardModificationHandler(
093 StandardPreModificationListener pre, int preMask,
094 StandardPostModificationListener post, int postMask) {
095 super();
096 if (pre != null) {
097 preHolder = new PreHolder[1];
098 preHolder[0] = new PreHolder(pre, preMask);
099 this.preMask = preMask;
100 }
101 if (post != null) {
102 postHolder = new PostHolder[1];
103 postHolder[0] = new PostHolder(post, postMask);
104 this.postMask = postMask;
105 }
106 }
107
108 // Pre Listeners
109 //----------------------------------------------------------------------
110 /**
111 * Gets an array of all the pre listeners active in the handler.
112 * <p>
113 * All listeners will be instances of StandardPreModificationListener.
114 *
115 * @return the listeners
116 */
117 public synchronized Object[] getPreModificationListeners() {
118 Object[] lnrs = new Object[preHolder.length];
119 for (int i = 0; i < preHolder.length; i++) {
120 lnrs[i] = preHolder[i].listener;
121 }
122 return lnrs;
123 }
124
125 /**
126 * Adds a listener to the handler for pre modification events.
127 * <p>
128 * No error occurs if the listener is <code>null</code>.
129 *
130 * @param listener the listener to add, may be null (ignored)
131 * @throws ClassCastException if the listener is not a StandardPreModificationListener
132 */
133 public void addPreModificationListener(Object listener) {
134 addPreModificationListener((StandardPreModificationListener) listener, -1);
135 }
136
137 /**
138 * Adds a pre listener to the list held in the handler.
139 * <p>
140 * No error occurs if the listener is <code>null</code>.
141 *
142 * @param listener the listener to add, may be null (ignored)
143 * @param mask the mask for events (0 for none, -1 for all)
144 */
145 public synchronized void addPreModificationListener(StandardPreModificationListener listener, int mask) {
146 if (listener != null) {
147 int oldSize = preHolder.length;
148 PreHolder[] array = new PreHolder[oldSize + 1];
149 System.arraycopy(preHolder, 0, array, 0, oldSize);
150 array[oldSize] = new PreHolder(listener, mask);
151 preHolder = array;
152 calculatePreMask();
153 }
154 }
155
156 /**
157 * Removes a pre listener to the list held in the handler.
158 * <p>
159 * No error occurs if the listener is not in the list or the type
160 * of the listener is incorrect.
161 * The listener is matched using ==.
162 *
163 * @param listener the listener to remove, may be null (ignored)
164 */
165 public synchronized void removePreModificationListener(Object listener) {
166 if (listener != null) {
167 switch (preHolder.length) {
168 case 0:
169 return;
170
171 case 1:
172 if (preHolder[0].listener == listener) {
173 preHolder = EMPTY_PRE_HOLDERS;
174 calculatePreMask();
175 }
176 return;
177
178 default:
179 PreHolder[] array = new PreHolder[preHolder.length - 1];
180 boolean match = false;
181 for (int i = 0; i < preHolder.length; i++) {
182 if (match) {
183 array[i - 1] = preHolder[i];
184 } else if (preHolder[i].listener == listener) {
185 match = true;
186 } else {
187 array[i] = preHolder[i];
188 }
189 }
190 preHolder = array;
191 calculatePreMask();
192 return;
193 }
194 }
195 }
196
197 /**
198 * Sets the masks of a listener.
199 * <p>
200 * No error occurs if the listener is not in the list.
201 * The listener is matched using ==.
202 *
203 * @param listener the listener to change, may be null
204 * @param mask the new mask (0 for none, -1 for all)
205 */
206 public synchronized void setPreModificationListenerMask(StandardPreModificationListener listener, int mask) {
207 if (listener != null) {
208 for (int i = 0; i < preHolder.length; i++) {
209 if (preHolder[i].listener == listener) {
210 preHolder[i].mask = mask;
211 calculatePreMask();
212 break;
213 }
214 }
215 }
216 }
217
218 /**
219 * Calculate the combined masks.
220 */
221 protected void calculatePreMask() {
222 preMask = ModificationEventType.GROUP_NONE;
223 for (int i = 0; i < preHolder.length; i++) {
224 preMask |= preHolder[i].mask;
225 }
226 }
227
228 protected static class PreHolder {
229 final StandardPreModificationListener listener;
230 int mask;
231
232 PreHolder(StandardPreModificationListener listener, int mask) {
233 this.listener = listener;
234 this.mask = mask;
235 }
236
237 public String toString() {
238 return "[" + listener + "," + ModificationEventType.toString(mask) + "]";
239 }
240
241 }
242
243 // Post Listeners
244 //----------------------------------------------------------------------
245 /**
246 * Gets an array of all the post listeners active in the handler.
247 * <p>
248 * All listeners will be instances of StandardModificationListener.
249 *
250 * @return the listeners
251 */
252 public synchronized Object[] getPostModificationListeners() {
253 Object[] lnrs = new Object[postHolder.length];
254 for (int i = 0; i < postHolder.length; i++) {
255 lnrs[i] = postHolder[i].listener;
256 }
257 return lnrs;
258 }
259
260 /**
261 * Adds a listener to the handler for post modification events.
262 * <p>
263 * No error occurs if the listener is <code>null</code>.
264 *
265 * @param listener the listener to add, may be null (ignored)
266 * @throws ClassCastException if the listener is not a StandardPreModificationListener
267 */
268 public void addPostModificationListener(Object listener) {
269 addPostModificationListener((StandardPostModificationListener) listener, -1);
270 }
271
272 /**
273 * Adds a post listener to the list held in the handler.
274 * <p>
275 * No error occurs if the listener is <code>null</code>.
276 *
277 * @param listener the listener to add, may be null (ignored)
278 * @param mask the mask for events (0 for none, -1 for all)
279 */
280 public synchronized void addPostModificationListener(StandardPostModificationListener listener, int mask) {
281 if (listener != null) {
282 int oldSize = postHolder.length;
283 PostHolder[] array = new PostHolder[oldSize + 1];
284 System.arraycopy(postHolder, 0, array, 0, oldSize);
285 array[oldSize] = new PostHolder(listener, mask);
286 postHolder = array;
287 calculatePostMask();
288 }
289 }
290
291 /**
292 * Removes a post listener to the list held in the handler.
293 * <p>
294 * No error occurs if the listener is not in the list or the type
295 * of the listener is incorrect.
296 * The listener is matched using ==.
297 *
298 * @param listener the listener to remove, may be null (ignored)
299 */
300 public synchronized void removePostModificationListener(Object listener) {
301 if (listener != null) {
302 switch (postHolder.length) {
303 case 0:
304 return;
305
306 case 1:
307 if (postHolder[0].listener == listener) {
308 postHolder = EMPTY_POST_HOLDERS;
309 calculatePostMask();
310 }
311 return;
312
313 default:
314 PostHolder[] array = new PostHolder[postHolder.length - 1];
315 boolean match = false;
316 for (int i = 0; i < postHolder.length; i++) {
317 if (match) {
318 array[i - 1] = postHolder[i];
319 } else if (postHolder[i].listener == listener) {
320 match = true;
321 } else {
322 array[i] = postHolder[i];
323 }
324 }
325 postHolder = array;
326 calculatePostMask();
327 return;
328 }
329 }
330 }
331
332 /**
333 * Sets the masks of a listener.
334 * <p>
335 * No error occurs if the listener is not in the list.
336 * The listener is matched using ==.
337 *
338 * @param listener the listener to change, may be null
339 * @param mask the new mask (0 for none, -1 for all)
340 */
341 public synchronized void setPostModificationListenerMask(StandardPostModificationListener listener, int mask) {
342 if (listener != null) {
343 for (int i = 0; i < postHolder.length; i++) {
344 if (postHolder[i].listener == listener) {
345 postHolder[i].mask = mask;
346 calculatePostMask();
347 break;
348 }
349 }
350 }
351 }
352
353 /**
354 * Calculate the combined masks.
355 */
356 protected void calculatePostMask() {
357 postMask = ModificationEventType.GROUP_NONE;
358 for (int i = 0; i < postHolder.length; i++) {
359 postMask |= postHolder[i].mask;
360 }
361 }
362
363 protected static class PostHolder {
364 final StandardPostModificationListener listener;
365 int mask;
366
367 PostHolder(StandardPostModificationListener listener, int mask) {
368 this.listener = listener;
369 this.mask = mask;
370 }
371
372 public String toString() {
373 return "[" + listener + "," + ModificationEventType.toString(mask) + "]";
374 }
375
376 }
377
378 // Pre event sending
379 //-----------------------------------------------------------------------
380 /**
381 * Handles the pre event.
382 *
383 * @param type the event type to send
384 * @param index the index where the change starts, the method param or derived
385 * @param object the object that will be added/removed/set, the method param or derived
386 * @param repeat the number of repeats of the add/remove, the method param or derived
387 * @param previous the previous value that will be removed/replaced, must exist in coll
388 * @param view the view collection that the change was actioned on, null if no view
389 * @param viewOffset the offset of the subList view, -1 if unknown
390 * @return true to call the decorated collection
391 */
392 protected boolean preEvent(
393 int type, int index, Object object,
394 int repeat, Object previous, ObservableCollection view, int viewOffset) {
395
396 preSize = getObservedCollection().size();
397 return firePreEvent(type, index, object, repeat, previous, view, viewOffset);
398 }
399
400 /**
401 * Sends the pre event to the listeners.
402 *
403 * @param type the event type to send
404 * @param index the index where the change starts, the method param or derived
405 * @param object the object that will be added/removed/set, the method param or derived
406 * @param repeat the number of repeats of the add/remove, the method param or derived
407 * @param previous the previous value that will be removed/replaced, must exist in coll
408 * @param view the view collection that the change was actioned on, null if no view
409 * @param viewOffset the offset of the subList view, -1 if unknown
410 * @return true to call the decorated collection
411 */
412 protected boolean firePreEvent(
413 int type, int index, Object object, int repeat,
414 Object previous, ObservableCollection view, int viewOffset) {
415
416 if ((preMask & type) > 0) {
417 StandardPreModificationEvent event = null;
418 synchronized (this) {
419 for (int i = 0; i < preHolder.length; i++) {
420 PreHolder holder = preHolder[i];
421 if ((holder.mask & type) > 0) {
422 if (event == null) {
423 event = new StandardPreModificationEvent(
424 getObservedCollection(), this, type, preSize, index, object,
425 repeat, previous, view, viewOffset);
426 }
427 holder.listener.modificationOccurring(event);
428 }
429 }
430 }
431 }
432 return true;
433 }
434
435 // Post event sending
436 //-----------------------------------------------------------------------
437 /**
438 * Handles the post event.
439 *
440 * @param modified true if the method succeeded in changing the collection
441 * @param type the event type to send
442 * @param index the index where the change starts, the method param or derived
443 * @param object the object that was added/removed/set, the method param or derived
444 * @param repeat the number of repeats of the add/remove, the method param or derived
445 * @param previous the previous value that was removed/replace, must have existed in coll
446 * @param view the view collection that the change was actioned on, null if no view
447 * @param viewOffset the offset of the subList view, -1 if unknown
448 */
449 protected void postEvent(
450 boolean modified, int type, int index, Object object,
451 int repeat, Object previous, ObservableCollection view, int viewOffset) {
452
453 if (modified) {
454 firePostEvent(type, index, object, repeat, previous, view, viewOffset);
455 }
456 }
457
458 /**
459 * Sends the post event to the listeners.
460 *
461 * @param type the event type to send
462 * @param index the index where the change starts, the method param or derived
463 * @param object the object that was added/removed/set, the method param or derived
464 * @param repeat the number of repeats of the add/remove, the method param or derived
465 * @param previous the previous value that was removed/replace, must have existed in coll
466 * @param view the view collection that the change was actioned on, null if no view
467 * @param viewOffset the offset of the subList view, -1 if unknown
468 */
469 protected void firePostEvent(
470 int type, int index, Object object, int repeat,
471 Object previous, ObservableCollection view, int viewOffset) {
472
473 if ((postMask & type) > 0) {
474 StandardPostModificationEvent event = null;
475 synchronized (this) {
476 for (int i = 0; i < postHolder.length; i++) {
477 PostHolder holder = postHolder[i];
478 if ((holder.mask & type) > 0) {
479 if (event == null) {
480 event = new StandardPostModificationEvent(
481 getObservedCollection(), this, type, preSize, index,
482 object, repeat, previous, view, viewOffset);
483 }
484 holder.listener.modificationOccurred(event);
485 }
486 }
487 }
488 }
489 }
490
491 // Event handling
492 //-----------------------------------------------------------------------
493 /**
494 * Send an event after clear() is called.
495 * <p>
496 * Override to only send event if something actually cleared.
497 */
498 protected void postClear() {
499 postEvent(preSize > 0, ModificationEventType.CLEAR, -1, null, 1, null, null, -1);
500 }
501
502 // Factory
503 //-----------------------------------------------------------------------
504 /**
505 * Factory implementation for the StandardModificationHandler.
506 *
507 * @author Stephen Colebourne
508 */
509 static class Factory implements ModificationHandlerFactory {
510
511 /**
512 * Creates a StandardModificationHandler using the listener.
513 *
514 * @param coll the collection being decorated
515 * @param listener a listener object to create a handler for
516 * @return an instantiated handler with the listener attached,
517 * or null if the listener type is unsuited to this factory
518 */
519 public ModificationHandler createHandler(Collection coll, Object listener) {
520 if (listener instanceof StandardPreModificationListener) {
521 if (listener instanceof StandardPostModificationListener) {
522 return new StandardModificationHandler(
523 (StandardPreModificationListener) listener, -1,
524 (StandardPostModificationListener) listener, -1);
525 } else {
526 return new StandardModificationHandler(
527 (StandardPreModificationListener) listener, -1, null, 0);
528 }
529 }
530 if (listener instanceof StandardPostModificationListener) {
531 return new StandardModificationHandler(
532 null, 0, (StandardPostModificationListener) listener, -1);
533 }
534 return null;
535 }
536 }
537
538 }