View Javadoc
1 /* 2 * Scope: a generic MVC framework. 3 * Copyright (c) 2000-2002, The Scope team 4 * All rights reserved. 5 * 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 18 * Neither the name "Scope" nor the names of its contributors 19 * may be used to endorse or promote products derived from this software 20 * without specific prior written permission. 21 * 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 24 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 25 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 26 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 28 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 29 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 30 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 31 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 32 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 33 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 * 35 * 36 * $Id: SAbstractListModel.java,v 1.17 2002/09/13 17:11:26 ludovicc Exp $ 37 */ 38 package org.scopemvc.view.swing; 39 40 41 import java.util.Collection; 42 import java.util.Comparator; 43 import javax.swing.AbstractListModel; 44 import org.apache.commons.logging.Log; 45 import org.apache.commons.logging.LogFactory; 46 import org.scopemvc.core.IntIndexSelector; 47 import org.scopemvc.core.ModelChangeEvent; 48 import org.scopemvc.core.ModelChangeEventSource; 49 import org.scopemvc.core.ModelChangeListener; 50 import org.scopemvc.core.PropertyManager; 51 import org.scopemvc.core.Selector; 52 import org.scopemvc.model.collection.ListModelAdaptor; 53 import org.scopemvc.model.collection.ListModelSource; 54 import org.scopemvc.util.Debug; 55 import org.scopemvc.view.util.ModelBindable; 56 57 /*** 58 * <P> 59 * 60 * A javax.swing.AbstractListModel bound to a property of a model used by {@link 61 * SList}, {@link SComboBox}, {@link STable}. The property should have elements 62 * accessible using IntIndexedSelectors and needs to fulfill one of the 63 * following criteria: 64 * <OL> 65 * <LI> be a java.util.List</LI> 66 * <LI> be an Object[]</LI> 67 * <LI> optional: have an accessible 'size' property in the parent view model 68 * (see below) </LI> 69 * </OL> 70 * Unfortunately this means that JavaBeans indexed properties cannot be bound 71 * directly to an SAbstractListModel because there is no way to discover the 72 * size of such a list, <B>unless</B> a separate property can be accessed to 73 * provide the size of the list using {@link #setSizeSelector} or {@link 74 * #setSizeSelectorString}. </P> <P> 75 * 76 * If required, the model for items can be specified as a static 77 * ListModelAdapter when it is more convenient to specify the list model at 78 * initialisation of (for example) an SComboBox rather than include it in the 79 * view model for active binding. </P> <P> 80 * 81 * The list can present its elements as a sorted list if a Comparator is passed 82 * to {@link #setSorted(java.util.Comparator)} or all list elements implement 83 * Comparable and {@link #setSorted(boolean)} is called. </P> <P> 84 * 85 * ***** This implementation makes no provisions for thread-safety. </P> 86 * 87 * @author <A HREF="mailto:smeyfroi@users.sourceforge.net">Steve Meyfroidt</A> 88 * @created 05 September 2002 89 * @version $Revision: 1.17 $ $Date: 2002/09/13 17:11:26 $ 90 * @see SList 91 * @see STable 92 * @see SComboBox 93 * @see org.scopemvc.model.collection.ListModelAdaptor 94 */ 95 public abstract class SAbstractListModel extends AbstractListModel 96 implements ModelBindable, Refreshable, ModelChangeListener { 97 98 private static final Log LOG = LogFactory.getLog(SAbstractListModel.class); 99 100 // ------------------ implement AstractListModel ------------------------- 101 102 /*** 103 * Share this instance for all fetches from the list. Synchronize on it 104 * first! 105 */ 106 private static final IntIndexSelector index = Selector.fromInt(0); 107 108 // ----------- Allow size of list to come from independent Selector -------- 109 110 /*** 111 * TODO: describe of the Field 112 */ 113 protected Selector sizeSelector; 114 115 // ------------------ Allow fixed list model to be set ------------------- 116 117 /*** 118 * TODO: describe of the Field 119 */ 120 protected Object listModel; 121 122 private boolean sorted; 123 private Comparator comparator; 124 125 // ------------------- Delegate to SwingBoundModel ------------------- 126 127 /*** 128 * Helper to manage model to view binding. 129 */ 130 private SwingBoundModel boundModel = new SwingBoundModel(this); 131 132 // --------------------- shownModel ----------------------- 133 134 /*** 135 * The (list) model object that the SListModel presents, which may be a 136 * property of the bound model if a Selector is specified. May be null if 137 * the bound property is a JavaBeans indexed property. 138 */ 139 private Object shownModel; 140 141 /*** 142 * The property manager for the bound (list) model. 143 */ 144 private PropertyManager manager; 145 146 147 /*** 148 * Gets the bound model 149 * 150 * @return The boundModel value 151 */ 152 public final Object getBoundModel() { 153 return boundModel.getBoundModel(); 154 } 155 156 157 /*** 158 * Gets the selector 159 * 160 * @return The selector value 161 */ 162 public final Selector getSelector() { 163 return boundModel.getSelector(); 164 } 165 166 167 /*** 168 * Get the current value (what would be set as a property of the bound model 169 * object) being presented on the View. 170 * 171 * @return property's value from the UI. 172 */ 173 public final Object getViewValue() { 174 return getShownModel(); 175 } 176 177 178 /*** 179 * Get the (list) model object that the SListModel presents, which may be a 180 * property of the bound model if a Selector is specified. May be null if 181 * the bound property is a JavaBeans indexed property. 182 * 183 * @return The shownModel value 184 */ 185 public final Object getShownModel() { 186 return shownModel; 187 } 188 189 190 /*** 191 * Gets the size selector 192 * 193 * @return The sizeSelector value 194 */ 195 public Selector getSizeSelector() { 196 return sizeSelector; 197 } 198 199 200 // ---------------- Allow list to be sorted ----------------------- 201 202 /*** 203 * Gets the sorted 204 * 205 * @return The sorted value 206 */ 207 public boolean isSorted() { 208 return sorted; 209 } 210 211 212 // ------------------------- Implement javax.swing.AbstractListModel ---------------------------- 213 214 /*** 215 * Gets the size 216 * 217 * @return The size value 218 */ 219 public int getSize() { 220 /* 221 * if (getBoundModel() == null) { 222 * if (LOG.isDebugEnabled()) LOG.debug("getSize: null getBoundModel(): 0"); 223 * return 0; 224 * } else 225 */ 226 if (sizeSelector != null) { 227 try { 228 PropertyManager manager = boundModel.getPropertyManager(); 229 if (manager == null) { 230 if (LOG.isDebugEnabled()) { 231 LOG.debug("getSize: no manager for: " + shownModel + ": size 0"); 232 } 233 return 0; 234 } 235 Integer size = (Integer) manager.get(getBoundModel(), sizeSelector); 236 if (LOG.isDebugEnabled()) { 237 LOG.debug("getSize: from (" + sizeSelector + "): size " + size.intValue()); 238 } 239 return size.intValue(); 240 } catch (Exception e) { 241 LOG.warn("getSize: Can't get size using sizeSelector: " + sizeSelector, e); 242 return 0; 243 } 244 } else if (shownModel instanceof java.util.List) { 245 if (LOG.isDebugEnabled()) { 246 LOG.debug("getSize: from java.util.List: size " + ((java.util.List) shownModel).size()); 247 } 248 return ((java.util.List) shownModel).size(); 249 } else if (shownModel instanceof Object[]) { 250 if (LOG.isDebugEnabled()) { 251 LOG.debug("getSize: from Object[]: size " + ((Object[]) shownModel).length); 252 } 253 return ((Object[]) shownModel).length; 254 } else if (shownModel instanceof ListModelAdaptor) { 255 if (LOG.isDebugEnabled()) { 256 LOG.debug("getSize: from adaptor: size " + ((ListModelAdaptor) shownModel).getSize()); 257 } 258 return ((ListModelAdaptor) shownModel).getSize(); 259 } else { 260 if (LOG.isDebugEnabled()) { 261 LOG.debug("getSize: from null shownModel: size 0"); 262 } 263 return 0; 264 } 265 } 266 267 268 /*** 269 * Note that if the get() fails for any reason, a null is returned. 270 * 271 * @param inIndex TODO: Describe the Parameter 272 * @return The elementAt value 273 */ 274 public Object getElementAt(int inIndex) { 275 if (LOG.isDebugEnabled()) { 276 LOG.debug("getElementAt: " + inIndex); 277 } 278 279 if (shownModel == null && getBoundModel() == null) { 280 LOG.warn("getElementAt: null model trying to get element: " + inIndex); 281 return null; 282 } else if (shownModel instanceof java.util.List) { 283 return ((java.util.List) shownModel).get(inIndex); 284 } else if (shownModel instanceof Object[]) { 285 return ((Object[]) shownModel)[inIndex]; 286 } else if (shownModel instanceof ListModelAdaptor) { 287 return ((ListModelAdaptor) shownModel).getElementAt(inIndex); 288 } else { 289 try { 290 synchronized (index) { 291 index.setIndex(inIndex); 292 if (shownModel == null) { 293 // JavaBeans indexed property... this is ugly 294 Selector s = boundModel.getSelector().deepClone(); 295 s.chain(index); 296 if (LOG.isDebugEnabled()) { 297 LOG.debug("Indexed: " + s); 298 } 299 return boundModel.getPropertyManager().get(getBoundModel(), s); 300 } else { 301 if (manager != null) { 302 return manager.get(shownModel, index); 303 } 304 } 305 } 306 } catch (Exception e) { 307 LOG.warn("Can't get element " + inIndex + " from: " + shownModel, e); 308 } 309 } 310 return null; 311 } 312 313 314 /*** 315 * Sets the selector 316 * 317 * @param inSelector The new selector value 318 */ 319 public final void setSelector(Selector inSelector) { 320 boundModel.setSelector(inSelector); 321 } 322 323 324 /*** 325 * Sets the selector string 326 * 327 * @param inSelectorString The new selectorString value 328 */ 329 public final void setSelectorString(String inSelectorString) { 330 if (LOG.isDebugEnabled()) { 331 LOG.debug("setSelectorString: " + inSelectorString); 332 } 333 334 boundModel.setSelectorString(inSelectorString); 335 } 336 337 338 /*** 339 * Sets the size selector 340 * 341 * @param inSelector The new sizeSelector value 342 */ 343 public void setSizeSelector(Selector inSelector) { 344 sizeSelector = inSelector; 345 } 346 347 348 /*** 349 * Sets the size selector string 350 * 351 * @param inSelectorString The new sizeSelectorString value 352 */ 353 public void setSizeSelectorString(String inSelectorString) { 354 if (inSelectorString == null) { 355 setSizeSelector(null); 356 } else { 357 setSizeSelector(Selector.fromString(inSelectorString)); 358 } 359 } 360 361 362 /*** 363 * Can use this to specify a static list model for the contents of the list 364 * rather than binding to a dynamic property of some view model. 365 * 366 * @param inModel The new listModel value 367 * @see org.scopemvc.model.collection.ListModelAdaptor 368 */ 369 public void setListModel(Object inModel) { 370 listModel = inModel; 371 setShownModel(listModel); 372 } 373 374 375 /*** 376 * Sets the sorted 377 * 378 * @param inSorted The new sorted value 379 */ 380 public void setSorted(boolean inSorted) { 381 sorted = true; 382 boundModel.updateFromModel(ModelChangeEvent.VALUE_CHANGED); 383 } 384 385 386 /*** 387 * Sets the sorted 388 * 389 * @param inComparator The new sorted value 390 */ 391 public void setSorted(Comparator inComparator) { 392 if (inComparator == null) { 393 setSorted(false); 394 comparator = null; 395 } else { 396 setSorted(true); 397 comparator = inComparator; 398 } 399 boundModel.updateFromModel(ModelChangeEvent.VALUE_CHANGED); 400 } 401 402 403 /*** 404 * @param inComparator The new comparator value 405 * @deprecated see setSorted(Comparator) 406 */ 407 public void setComparator(Comparator inComparator) { 408 setSorted(inComparator); 409 } 410 411 412 /*** 413 * Sets the bound model 414 * 415 * @param inModel The new boundModel value 416 */ 417 public void setBoundModel(Object inModel) { 418 boundModel.setBoundModel(inModel); 419 } 420 421 422 // --------------------- Implement ModelBindable ---------------------- 423 424 /*** 425 * Ignores inReadOnly because makes no sense here. 426 * 427 * @param inValue TODO: Describe the Parameter 428 * @param inReadOnly TODO: Describe the Parameter 429 */ 430 public void updateFromProperty(Object inValue, boolean inReadOnly) { 431 if (LOG.isDebugEnabled()) { 432 LOG.debug("updateFromProperty: " + inValue + ", " + inReadOnly); 433 } 434 435 setShownModel(inValue); 436 } 437 438 439 /*** 440 * Makes no sense: can't change the property using an SAbstractListModel! 441 * 442 * @param inException TODO: Describe the Parameter 443 */ 444 public void validationFailed(Exception inException) { 445 // noop 446 } 447 448 449 /*** 450 * Makes no sense: can't change the property using an SAbstractListModel! 451 */ 452 public void validationSuccess() { 453 // noop 454 } 455 456 457 // ------------------ Refreshable ------------------------- 458 459 /*** 460 * TODO: document the method 461 */ 462 public void refresh() { 463 Object propertyValue = boundModel.getPropertyValue(); 464 boolean propertyReadOnly = boundModel.getPropertyReadOnly(); 465 updateFromProperty(propertyValue, propertyReadOnly); 466 } 467 468 469 // --------- Implement ModelChangeListener for the shownModel ------------- 470 471 /*** 472 * <P> 473 * 474 * Invoked to notify listeners of a change in the state of a {@link 475 * ModelChangeEventSource}. </P> <P> 476 * 477 * ListDataEvent are fired by this method from the Swing event thread. </P> 478 * 479 * @param inEvent the {@link ModelChangeEvent} representing the change in 480 * state of the ModelChangeEventSource. 481 */ 482 public void modelChanged(final ModelChangeEvent inEvent) { 483 SwingUtil.runFromSwingEventThread( 484 new Runnable() { 485 public void run() { 486 doModelChanged(inEvent); 487 } 488 }); 489 } 490 491 492 /*** 493 * Called internally from updateFromProperty(). 494 * 495 * @param inModel The new shownModel value 496 */ 497 protected void setShownModel(Object inModel) { 498 499 // If we've got a static list model then don't bind to dynamic 500 // property here. 501 if (listModel != null && inModel != listModel) { 502 return; 503 } 504 505 if (shownModel == inModel) { 506 // ensure refresh does something even when bound to a non-ModelChangeSource 507 // ... this might turn out to be a bad idea but I can't see another way right now. 508 fireContentsChanged(this, 0, Integer.MAX_VALUE); 509 //getSize() - 1); 510 return; 511 } 512 513 if (shownModel instanceof ListModelAdaptor) { 514 // To allow GC 515 ((ListModelAdaptor) shownModel).removeModelChangeListeners(); 516 } 517 518 if (shownModel instanceof ModelChangeEventSource) { 519 ((ModelChangeEventSource) shownModel).removeModelChangeListener(this); 520 } 521 522 shownModel = inModel; 523 524 if (isSorted() && shownModel instanceof Collection) { 525 shownModel = new ListModelAdaptor(new ListModelSource((Collection) inModel)); 526 ((ListModelAdaptor) shownModel).setComparator(comparator); 527 ((ListModelAdaptor) shownModel).setSorted(true); 528 } 529 530 if (shownModel != null) { 531 manager = PropertyManager.getInstance(shownModel); 532 } 533 534 if (shownModel instanceof ModelChangeEventSource) { 535 ((ModelChangeEventSource) shownModel).addModelChangeListener(this); 536 } 537 538 fireContentsChanged(this, 0, getSize() - 1); 539 } 540 541 542 /*** 543 * Respond to a change in the model by firing the appropriate ListDataEvent 544 * 545 * @param inEvent the {@link ModelChangeEvent} representing the change in 546 * state of the ModelChangeEventSource. 547 */ 548 protected void doModelChanged(ModelChangeEvent inEvent) { 549 if (Debug.ON) { 550 Debug.assertTrue(inEvent.getModel() == shownModel, "not my model: " + inEvent.getModel()); 551 } 552 553 Selector selector = inEvent.getSelector(); 554 int index0 = 0; 555 int index1 = getSize() - 1; 556 if (selector instanceof IntIndexSelector) { 557 index0 = ((IntIndexSelector) selector).getIndex(); 558 index1 = index0; 559 } 560 561 if (LOG.isDebugEnabled()) { 562 LOG.debug("modelChanged: " + inEvent.getModel() + ", selector is " + selector); 563 } 564 565 if (inEvent.getType() == ModelChangeEvent.ACCESS_CHANGED) { 566 // implement when lists are editable 567 return; 568 } else if (inEvent.getType() == ModelChangeEvent.VALUE_ADDED) { 569 fireIntervalAdded(this, index0, index1); 570 } else if (inEvent.getType() == ModelChangeEvent.VALUE_REMOVED) { 571 fireIntervalRemoved(this, index0, index1); 572 } else if (inEvent.getType() == ModelChangeEvent.VALUE_CHANGED) { 573 fireContentsChanged(this, index0, index1); 574 } 575 } 576 577 /*** 578 * Returns true if the bound model contains the value in its list of 579 * elements 580 * 581 * @param inValue The value to check 582 * @return true if the value belongs to the list of bound items 583 */ 584 protected boolean containsElement(Object inValue) { 585 // this implementation is slow and is reserved for validation purposes only 586 LOG.debug("containsElement - size = " + getSize()); 587 for (int i = 0; i < getSize(); i++) { 588 Object elem = getElementAt(i); 589 LOG.debug("containsElement - " + inValue + " ==? " + elem); 590 if (elem == inValue || elem != null && elem.equals(inValue)) { 591 return true; 592 } 593 } 594 return false; 595 } 596 597 /*** 598 * Gets the manager 599 * 600 * @return The manager value 601 */ 602 final PropertyManager getManager() { 603 return manager; 604 } 605 }

This page was automatically generated by Maven