1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 package org.apache.myfaces.component.html.ext;
21
22 import java.text.CollationKey;
23 import java.text.Collator;
24 import java.util.Collections;
25 import java.util.Comparator;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.faces.context.FacesContext;
31 import javax.faces.model.DataModel;
32
33 import org.apache.commons.beanutils.PropertyUtils;
34 import org.apache.commons.logging.Log;
35 import org.apache.commons.logging.LogFactory;
36
37 public final class SortableModel extends BaseSortableModel
38 {
39 private static final Log log = LogFactory.getLog(SortableModel.class);
40
41 private SortCriterion _sortCriterion = null;
42
43 /**
44 * Create a new SortableModel from the given instance.
45 * @param model This will be converted into a {@link DataModel}
46 * @see #setWrappedData
47 */
48 public SortableModel(Object model)
49 {
50 super(model);
51 }
52
53 /**
54 * No arg constructor for use as a managed-bean.
55 * Must call setWrappedData before using this instance.
56 */
57 public SortableModel(){}
58
59 /**
60 * Sets the underlying data being managed by this instance.
61 * @param data This Object will be converted into a
62 * {@link DataModel}.
63 */
64 public void setWrappedData(Object data)
65 {
66 super.setWrappedData(data);
67 setSortCriteria(null);
68 }
69
70 /**
71 * Checks to see if the underlying collection is sortable by the given property.
72 * @param property The name of the property to sort the underlying collection by.
73 * @return true, if the property implements java.lang.Comparable
74 */
75 public boolean isSortable(String property)
76 {
77 final int oldIndex = _model.getRowIndex();
78 try
79 {
80 _model.setRowIndex(0);
81 if (!_model.isRowAvailable())
82 return false; // if there is no data in the table then nothing is sortable
83
84 try
85 {
86 Object propertyValue = PropertyUtils.getProperty(_model.getRowData(),property);
87
88 // when the value is null, we don't know if we can sort it.
89 // by default let's support sorting of null values, and let the user
90 // turn off sorting if necessary:
91 return (propertyValue instanceof Comparable) || (propertyValue == null);
92 }
93 catch (RuntimeException e)
94 {
95 // don't propagate this exception out. This is because it might break
96 // the VE.
97 log.warn(e);
98 return false;
99 }
100 catch (Exception e) {
101 log.warn(e);
102 return false;
103 }
104 }
105 finally
106 {
107 _model.setRowIndex(oldIndex);
108 }
109 }
110
111 public List getSortCriteria()
112 {
113 return (_sortCriterion == null) ? Collections.EMPTY_LIST : Collections.singletonList(_sortCriterion);
114 }
115
116 public void setSortCriteria(List criteria)
117 {
118 if ((criteria == null) || (criteria.isEmpty()))
119 {
120 _sortCriterion = null;
121 setComparator(null);
122 }
123 else
124 {
125 SortCriterion sc = (SortCriterion) criteria.get(0);
126 if ((_sortCriterion == null) || (!_sortCriterion.equals(sc)))
127 {
128 _sortCriterion = sc;
129
130 /*
131 * Sorts the underlying collection by the given property, in the
132 * given direction.
133 * @param property The name of the property to sort by. The value of this
134 * property must implement java.lang.Comparable.
135 * @param isAscending true if the collection is to be sorted in
136 * ascending order.
137 * @todo support -1 for rowCount
138 */
139 Comparator comp = new Comp(_sortCriterion.getProperty());
140
141 if (!_sortCriterion.isAscending())
142 comp = new Inverter(comp);
143 setComparator(comp);
144 }
145 }
146 }
147
148 public String toString()
149 {
150 return "SortableModel[" + _model + "]";
151 }
152
153 private final class Comp implements Comparator
154 {
155 private final String _prop;
156
157 private Collator _collator;
158
159 private Map _collationKeys;
160
161 public Comp(String property)
162 {
163 _prop = property;
164 _collator = Collator.getInstance(FacesContext.getCurrentInstance().getViewRoot().getLocale());
165 _collationKeys = new HashMap();
166 }
167
168 public int compare(Object o1, Object o2)
169 {
170 Object value1 = null;
171 Object value2 = null;
172 try {
173 value1 = PropertyUtils.getProperty(o1,_prop);
174 value2 = PropertyUtils.getProperty(o2,_prop);
175 }
176 catch (Exception exc) {
177 log.error(exc);
178 }
179
180 if (value1 == null)
181 return (value2 == null) ? 0 : -1;
182
183 if (value2 == null)
184 return 1;
185
186 // ?? Sometimes, isSortable returns true
187 // even if the underlying object is not a Comparable.
188 // This happens if the object at rowIndex zero is null.
189 // So test before we cast:
190
191 if (value1 instanceof String) {
192 //if the object is a String we best compare locale-sesitive
193 CollationKey collationKey1 = getCollationKey((String)value1);
194 CollationKey collationKey2 = getCollationKey((String)value2);
195
196 return collationKey1.compareTo(collationKey2);
197 }
198 else if (value1 instanceof Comparable)
199 {
200 return ((Comparable) value1).compareTo(value2);
201 }
202 else
203 {
204 // if the object is not a Comparable, then
205 // the best we can do is string comparison:
206 return value1.toString().compareTo(value2.toString());
207 }
208 }
209
210 private CollationKey getCollationKey(String propertyValue) {
211 CollationKey key = (CollationKey)_collationKeys.get(propertyValue);
212 if (key == null) {
213 key = _collator.getCollationKey(propertyValue);
214 _collationKeys.put(propertyValue, key);
215 }
216
217 return key;
218 }
219 }
220 /**
221 *
222 */
223 private static final class Inverter implements Comparator
224 {
225 private final Comparator _comp;
226
227 public Inverter(Comparator comp)
228 {
229 _comp = comp;
230 }
231
232 public int compare(Object o1, Object o2)
233 {
234 return _comp.compare(o2, o1);
235 }
236 }
237 }