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 }