1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.trinidad.model;
20
21 import java.text.Collator;
22
23 import java.util.ArrayList;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Locale;
29 import java.util.Map;
30
31 import javax.el.ELContext;
32 import javax.el.ELResolver;
33 import javax.el.FunctionMapper;
34 import javax.el.VariableMapper;
35
36 import javax.faces.FactoryFinder;
37 import javax.faces.application.ApplicationFactory;
38 import javax.faces.context.FacesContext;
39 import javax.faces.model.DataModel;
40 import javax.faces.model.DataModelListener;
41
42 import org.apache.myfaces.trinidad.context.RequestContext;
43 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
44
45
46
47
48
49
50 public class SortableModel extends CollectionModel
51 {
52
53
54
55
56 public enum Strength
57 {
58
59 IDENTICAL(Collator.IDENTICAL),
60
61 PRIMARY(Collator.PRIMARY),
62
63 SECONDARY(Collator.SECONDARY),
64
65 TERTIARY(Collator.TERTIARY);
66
67 private Strength(int strength)
68 {
69 _strength = strength;
70 }
71
72 public int getIntValue()
73 {
74 return _strength;
75 }
76
77 private final int _strength;
78 }
79
80
81
82
83
84 public enum Decomposition
85 {
86
87 NO_DECOMPOSITION(Collator.NO_DECOMPOSITION),
88
89 CANONICAL_DECOMPOSITION(Collator.CANONICAL_DECOMPOSITION),
90
91 FULL_DECOMPOSITION(Collator.FULL_DECOMPOSITION);
92
93 private Decomposition(int decomposition)
94 {
95 _decomposition = decomposition;
96 }
97
98 public int getIntValue()
99 {
100 return _decomposition;
101 }
102
103 private final int _decomposition;
104 }
105
106
107
108
109
110
111 public SortableModel(Object model)
112 {
113 setWrappedData(model);
114 }
115
116
117
118
119
120 public SortableModel()
121 {
122 }
123
124 @Override
125 public Object getRowData()
126 {
127 return _model.getRowData();
128 }
129
130 @Override
131 public Object getWrappedData()
132 {
133 return _wrappedData;
134 }
135
136 @Override
137 public boolean isRowAvailable()
138 {
139 return _model.isRowAvailable();
140 }
141
142
143
144
145
146
147
148 @Override
149 public void setWrappedData(Object data)
150 {
151 _baseIndicesList = null;
152 _model = ModelUtils.toDataModel(data);
153 _sortCriterion = null;
154 _sortedIndicesList = null;
155 _wrappedData = data;
156 }
157
158 @Override
159 public int getRowCount()
160 {
161 return _model.getRowCount();
162 }
163
164 @Override
165 public void setRowIndex(int rowIndex)
166 {
167 int baseIndex = _toBaseIndex(rowIndex);
168 _model.setRowIndex(baseIndex);
169 }
170
171 @Override
172 public int getRowIndex()
173 {
174 int baseIndex = _model.getRowIndex();
175 return _toSortedIndex(baseIndex);
176 }
177
178
179
180
181
182 @Override
183 public Object getRowKey()
184 {
185 return isRowAvailable()
186 ? _model.getRowIndex()
187 : null;
188 }
189
190
191
192
193
194 @Override
195 public void setRowKey(Object key)
196 {
197 _model.setRowIndex(_toRowIndex(key));
198 }
199
200 public void addDataModelListener(DataModelListener listener)
201 {
202 _model.addDataModelListener(listener);
203 }
204
205 public DataModelListener[] getDataModelListeners()
206 {
207 return _model.getDataModelListeners();
208 }
209
210 public void removeDataModelListener(DataModelListener listener)
211 {
212 _model.removeDataModelListener(listener);
213 }
214
215
216
217
218
219
220 @Override
221 public boolean isSortable(String property)
222 {
223 final int oldIndex = _model.getRowIndex();
224 try
225 {
226 _model.setRowIndex(0);
227 if (!_model.isRowAvailable())
228 return false;
229
230 Object data = _model.getRowData();
231 try
232 {
233
234 FacesContext context = FacesContext.getCurrentInstance();
235 ELResolver resolver = _getELResolver(context);
236 ELContext elContext = _getELContext(context, resolver);
237 Object propertyValue = evaluateProperty(resolver, elContext, data, property);
238
239
240
241 return (propertyValue instanceof Comparable) ||
242 (propertyValue == null);
243 }
244 catch (RuntimeException e)
245 {
246
247
248 _LOG.warning(e);
249 return false;
250 }
251 }
252 finally
253 {
254 _model.setRowIndex(oldIndex);
255 }
256 }
257
258 private Object evaluateProperty(ELResolver resolver, ELContext context, Object base, String property)
259 {
260
261 if (!property.contains( "." ))
262 return resolver.getValue(context, base, property );
263
264 int index = property.indexOf( '.' );
265 Object newBase = resolver.getValue(context, base, property.substring( 0, index ) );
266
267 return evaluateProperty(resolver, context, newBase, property.substring( index + 1 ) );
268 }
269
270 @Override
271 public List<SortCriterion> getSortCriteria()
272 {
273 if (_sortCriterion == null)
274 {
275 return Collections.emptyList();
276 }
277 else
278 {
279 return Collections.singletonList(_sortCriterion);
280 }
281 }
282
283 @Override
284 public void setSortCriteria(List<SortCriterion> criteria)
285 {
286 if ((criteria == null) || (criteria.isEmpty()))
287 {
288 _sortCriterion = null;
289
290 _baseIndicesList = _sortedIndicesList = null;
291 }
292 else
293 {
294 SortCriterion sc = criteria.get(0);
295 if ((_sortCriterion == null) || (!_sortCriterion.equals(sc)))
296 {
297
298 _sortCriterion = sc;
299 _sort(_sortCriterion);
300 }
301 }
302 }
303
304
305
306
307
308
309
310 public Comparator getComparator(
311 String propertyName)
312 {
313 return _propertyComparators == null ?
314 null :
315 _propertyComparators.get(propertyName);
316 }
317
318
319
320
321
322
323
324 public void setComparator(
325 String propertyName,
326 Comparator comparator)
327 {
328 assert propertyName != null : "Property name may not be null";
329
330 if (comparator == null && _propertyComparators != null)
331 {
332 _propertyComparators.remove(propertyName);
333 if (_propertyComparators.isEmpty())
334 {
335 _propertyComparators = null;
336 }
337 }
338 else if (comparator != null)
339 {
340 if (_propertyComparators == null)
341 {
342 _propertyComparators = new HashMap<String, Comparator>();
343 }
344 _propertyComparators.put(propertyName, comparator);
345 }
346
347 if (_sortCriterion != null && propertyName.equals(_sortCriterion.getProperty()))
348 {
349 _sort(_sortCriterion);
350 }
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364 public void setCollator(
365 String propertyName,
366 Strength collatorStrength,
367 Decomposition collatorDecomposition)
368 {
369 Locale locale = null;
370
371 RequestContext reqCtx = RequestContext.getCurrentInstance();
372 if (reqCtx != null)
373 {
374 FacesContext facesContext = FacesContext.getCurrentInstance();
375 if (facesContext != null)
376 {
377 locale = _getLocale(reqCtx, facesContext);
378 }
379 }
380
381 Collator collator = locale == null ? Collator.getInstance() : Collator.getInstance(locale);
382 if (collatorDecomposition != null)
383 {
384 collator.setDecomposition(collatorDecomposition.getIntValue());
385 }
386
387 if (collatorStrength != null)
388 {
389 collator.setStrength(collatorStrength.getIntValue());
390 }
391
392 setComparator(propertyName, collator);
393 }
394
395 @Override
396 public String toString()
397 {
398 return "SortableModel[" + _model + "]";
399 }
400
401
402
403
404
405
406
407 private void _sort(SortCriterion sortCriterion)
408 {
409
410 int sz = getRowCount();
411 if ((_baseIndicesList == null) || (_baseIndicesList.size() != sz))
412 {
413
414
415
416
417
418
419
420 _baseIndicesList = new IntList(sz);
421 }
422
423 final int rowIndex = _model.getRowIndex();
424 _model.setRowIndex(0);
425
426 if (_model.isRowAvailable())
427 {
428 FacesContext context = FacesContext.getCurrentInstance();
429 RequestContext rc = RequestContext.getCurrentInstance();
430 ELResolver resolver = _getELResolver(context);
431 ELContext elContext = _getELContext(context, resolver);
432 Locale locale = _getLocale(rc, context);
433 Comparator<Integer> comp =
434 new Comp(resolver, elContext, locale, sortCriterion.getProperty(),
435 sortCriterion.getSortStrength());
436 if (!sortCriterion.isAscending())
437 comp = new Inverter<Integer>(comp);
438
439 Collections.sort(_baseIndicesList, comp);
440 _sortedIndicesList = null;
441 }
442
443 _model.setRowIndex(rowIndex);
444 }
445
446 private int _toSortedIndex(int baseIndex)
447 {
448 if ((_sortedIndicesList == null) && (_baseIndicesList != null))
449 {
450 _sortedIndicesList = (IntList) _baseIndicesList.clone();
451 for(int i=0; i<_baseIndicesList.size(); i++)
452 {
453 Integer base = _baseIndicesList.get(i);
454 _sortedIndicesList.set(base.intValue(), i);
455 }
456 }
457
458 return _convertIndex(baseIndex, _sortedIndicesList);
459 }
460
461 private int _toBaseIndex(int sortedIndex)
462 {
463 return _convertIndex(sortedIndex, _baseIndicesList);
464 }
465
466 private int _convertIndex(int index, List<Integer> indices)
467 {
468 if (index < 0)
469 return index;
470
471 if ((indices != null) && (indices.size() > index))
472 {
473 index = indices.get(index).intValue();
474 }
475 return index;
476 }
477
478 private int _toRowIndex(Object rowKey)
479 {
480 if (rowKey == null)
481 return -1;
482
483 try
484 {
485 return ((Integer)rowKey).intValue();
486 }
487 catch (ClassCastException e)
488 {
489 _LOG.warning("INVALID_ROWKEY", new Object[]{rowKey , rowKey.getClass()});
490 _LOG.warning(e);
491 return -1;
492 }
493 }
494
495
496
497 private static final class IntList extends ArrayList<Integer>
498 {
499 public IntList(int size)
500 {
501 super(size);
502 _expandToSize(size);
503 }
504
505 private void _expandToSize(int desiredSize)
506 {
507 for(int i=0; i<desiredSize; i++)
508 {
509 add(i);
510 }
511 }
512
513 private static final long serialVersionUID = 1L;
514 }
515
516 private final class Comp implements Comparator<Integer>
517 {
518 public Comp(
519 ELResolver resolver,
520 ELContext context,
521 Locale locale,
522 String property,
523 SortStrength sortStrength)
524 {
525 _resolver = resolver;
526 _context = context;
527
528
529
530 if (locale != null || sortStrength != null)
531 {
532 if (locale != null)
533 _collator = Collator.getInstance(locale);
534 else
535 _collator = Collator.getInstance();
536
537 if (sortStrength != null)
538 _collator.setStrength(sortStrength.getStrength());
539 }
540 else
541 {
542 _collator = null;
543 }
544
545 _prop = property;
546 }
547
548 @SuppressWarnings("unchecked")
549 public int compare(
550 Integer o1,
551 Integer o2)
552 {
553 int index1 = o1.intValue();
554 int index2 = o2.intValue();
555
556 _model.setRowIndex(index1);
557 Object instance1 = _model.getRowData();
558 Object value1 = evaluateProperty(_resolver, _context, instance1, _prop );
559
560 _model.setRowIndex(index2);
561 Object instance2 = _model.getRowData();
562 Object value2 = evaluateProperty(_resolver, _context, instance2, _prop );
563
564 if (value1 == null)
565 return (value2 == null) ? 0 : -1;
566
567 if (value2 == null)
568 return 1;
569
570 Comparator comparator = getComparator(_prop);
571 if (comparator == null)
572 {
573
574
575
576
577 if (value1 instanceof Comparable)
578 {
579 if ((value1 instanceof String) && (value2 instanceof String))
580 {
581 return _compare((String) value1, (String) value2);
582 }
583 else
584 {
585 return ((Comparable<Object>) value1).compareTo(value2);
586 }
587 }
588 else
589 {
590
591
592 return _compare(value1.toString(), value2.toString());
593 }
594 }
595 else
596 {
597 return comparator.compare(value1, value2);
598 }
599 }
600
601 private int _compare(
602 String s1,
603 String s2)
604 {
605 if (_collator != null)
606 {
607 return _collator.compare(s1, s2);
608 }
609 else
610 {
611 return s1.compareTo(s2);
612 }
613 }
614
615 private final ELResolver _resolver;
616 private final ELContext _context;
617 private final Collator _collator;
618 private final String _prop;
619 }
620
621 private static final class Inverter<T> implements Comparator<T>
622 {
623 public Inverter(Comparator<T> comp)
624 {
625 _comp = comp;
626 }
627
628 public int compare(T o1, T o2)
629 {
630 return _comp.compare(o2, o1);
631 }
632
633 private final Comparator<T> _comp;
634 }
635
636
637
638
639
640 private static final class ELContextImpl extends ELContext
641 {
642 public ELContextImpl(ELResolver resolver)
643 {
644 _resolver = resolver;
645 }
646
647 @Override
648 public ELResolver getELResolver()
649 {
650 return _resolver;
651 }
652
653 @Override
654 public FunctionMapper getFunctionMapper()
655 {
656
657
658 return null;
659 }
660
661 @Override
662 public VariableMapper getVariableMapper()
663 {
664
665
666 return null;
667 }
668
669 private final ELResolver _resolver;
670 }
671
672 static Object __resolveProperty(Object object, String propertyName)
673 {
674 FacesContext context = FacesContext.getCurrentInstance();
675 ELResolver resolver = _getELResolver(context);
676 ELContext elContext = _getELContext(context, resolver);
677 return resolver.getValue(elContext, object, propertyName);
678 }
679
680 static private ELContext _getELContext(
681 FacesContext context, ELResolver resolver)
682 {
683
684
685 if (context != null)
686 return context.getELContext();
687
688 return new ELContextImpl(resolver);
689 }
690
691 static private ELResolver _getELResolver(FacesContext context)
692 {
693
694
695 if (context != null)
696 return context.getApplication().getELResolver();
697
698
699
700 ApplicationFactory factory = (ApplicationFactory)
701 FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY);
702 return factory.getApplication().getELResolver();
703
704 }
705
706 static private Locale _getLocale(RequestContext requestContext, FacesContext facesContext)
707 {
708 if (requestContext != null)
709 return requestContext.getFormattingLocale();
710
711 if (facesContext != null)
712 return facesContext.getViewRoot().getLocale();
713
714 return null;
715 }
716
717 private SortCriterion _sortCriterion = null;
718
719 private DataModel _model = null;
720 private Object _wrappedData = null;
721
722 private Map<String, Comparator> _propertyComparators;
723
724 private IntList _sortedIndicesList = null,
725 _baseIndicesList = null;
726
727 static private final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(SortableModel.class);
728 }