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