1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.kuali.rice.krad.uif.util;
17
18 import org.apache.commons.collections.CollectionUtils;
19 import org.kuali.rice.core.api.util.type.KualiDecimal;
20 import org.kuali.rice.krad.comparator.NumericValueComparator;
21 import org.kuali.rice.krad.comparator.TemporalValueComparator;
22 import org.kuali.rice.krad.uif.UifConstants;
23 import org.kuali.rice.krad.uif.container.CollectionGroup;
24 import org.kuali.rice.krad.uif.container.CollectionGroupLineBuilder;
25 import org.kuali.rice.krad.uif.container.collections.LineBuilderContext;
26 import org.kuali.rice.krad.uif.field.DataField;
27 import org.kuali.rice.krad.uif.field.Field;
28 import org.kuali.rice.krad.uif.layout.TableLayoutManager;
29 import org.kuali.rice.krad.uif.layout.collections.TableRow;
30 import org.kuali.rice.krad.uif.layout.collections.TableRowBuilder;
31 import org.kuali.rice.krad.uif.lifecycle.ViewLifecycle;
32 import org.kuali.rice.krad.uif.lifecycle.ViewLifecyclePhase;
33 import org.kuali.rice.krad.uif.view.ExpressionEvaluator;
34 import org.kuali.rice.krad.uif.view.View;
35 import org.kuali.rice.krad.uif.view.ViewModel;
36 import org.kuali.rice.krad.util.KRADUtils;
37
38 import java.util.Comparator;
39 import java.util.HashMap;
40 import java.util.List;
41 import java.util.Map;
42 import java.util.WeakHashMap;
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public class MultiColumnComparator implements Comparator<Integer> {
60
61 private final List<Object> modelCollection;
62 private final CollectionGroup collectionGroup;
63 private final List<ColumnSort> columnSorts;
64 private final View view;
65 private final ViewModel form;
66
67
68 private final TableLayoutManager tableLayoutManager;
69
70
71 private final List<Field> prototypeRow;
72
73
74
75 private final WeakHashMap<String, String> calculatedValueCache;
76
77
78 private final HashMap<String, Class> propertyClassCache;
79
80
81
82
83
84
85
86
87
88
89 public MultiColumnComparator(List<Object> modelCollection, CollectionGroup collectionGroup,
90 List<ColumnSort> columnSorts, ViewModel form, View view) {
91 this.modelCollection = modelCollection;
92 this.collectionGroup = collectionGroup;
93 this.columnSorts = columnSorts;
94 this.view = view;
95 this.form = form;
96
97
98
99
100
101 calculatedValueCache = new WeakHashMap<String, String>();
102 propertyClassCache = new HashMap<String, Class>();
103
104 tableLayoutManager = (TableLayoutManager) collectionGroup.getLayoutManager();
105 prototypeRow = buildPrototypeRow();
106 }
107
108
109
110
111
112
113
114
115
116
117 @Override
118 public int compare(Integer index1, Integer index2) {
119 int sortResult = 0;
120
121 for (ColumnSort columnSort : columnSorts) {
122 Field protoField = prototypeRow.get(columnSort.getColumnIndex());
123
124 Object modelElement1 = modelCollection.get(index1);
125 Object modelElement2 = modelCollection.get(index2);
126
127 if (isOneNull(modelElement1, modelElement2)) {
128 sortResult = compareOneIsNull(modelElement1, modelElement2);
129 } else if (protoField instanceof DataField) {
130 sortResult = compareDataFieldValues(columnSort, (DataField) protoField, index1, index2);
131 } else {
132 sortResult = compareFieldStringValues(columnSort, protoField, index1, index2);
133 }
134
135 if (sortResult != 0) {
136
137 if (columnSort.getDirection() == ColumnSort.Direction.DESC) {
138 sortResult *= -1;
139 }
140
141 break;
142 }
143 }
144
145 return sortResult;
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159 private int compareDataFieldValues(ColumnSort columnSort, DataField protoField, Integer index1, Integer index2) {
160 final int sortResult;
161
162 final Object modelElement1 = modelCollection.get(index1);
163 final Object modelElement2 = modelCollection.get(index2);
164
165
166 final String propertyPath = protoField.getBindingInfo().getBindingName();
167 final Class<?> columnDataClass = getColumnDataClass(propertyPath);
168
169
170 if (Comparable.class.isAssignableFrom(columnDataClass)) {
171 Comparable datum1 = (Comparable) ObjectPropertyUtils.getPropertyValue(modelElement1, propertyPath);
172 Comparable datum2 = (Comparable) ObjectPropertyUtils.getPropertyValue(modelElement2, propertyPath);
173
174 if (isOneNull(datum1, datum2)) {
175 sortResult = compareOneIsNull(datum1, datum2);
176 } else if (String.class.equals(columnDataClass)) {
177 sortResult = columnTypeCompare((String) datum1, (String) datum2, columnSort.getSortType());
178 } else {
179 sortResult = datum1.compareTo(datum2);
180 }
181 } else {
182 sortResult = compareFieldStringValues(columnSort, protoField, index1, index2);
183 }
184
185 return sortResult;
186 }
187
188
189
190
191
192
193
194
195
196
197
198
199
200 private Class<?> getColumnDataClass(String propertyPath) {
201 Class<?> dataClass = propertyClassCache.get(propertyPath);
202
203 if (dataClass == null) {
204
205
206 for (int i = 0; i < modelCollection.size() && dataClass == null; i++) {
207
208 dataClass = ObjectPropertyUtils.getPropertyType(modelCollection.get(i), propertyPath);
209 }
210
211 if (dataClass == null) {
212 dataClass = Object.class;
213 }
214
215 propertyClassCache.put(propertyPath, dataClass);
216 }
217
218 return dataClass;
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233 private int compareFieldStringValues(ColumnSort columnSort, Field protoField, Integer index1, Integer index2) {
234 final int sortResult;
235 final String fieldValue1;
236 final String fieldValue2;
237
238 if (!CollectionUtils.sizeIsEmpty(protoField.getPropertyExpressions())) {
239
240 fieldValue1 = calculateFieldValue(protoField, index1, columnSort.getColumnIndex());
241 fieldValue2 = calculateFieldValue(protoField, index2, columnSort.getColumnIndex());
242 } else {
243 fieldValue1 = KRADUtils.getSimpleFieldValue(modelCollection.get(index1), protoField);
244 fieldValue2 = KRADUtils.getSimpleFieldValue(modelCollection.get(index2), protoField);
245 }
246
247 sortResult = columnTypeCompare(fieldValue1, fieldValue2, columnSort.getSortType());
248 return sortResult;
249 }
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265 private String calculateFieldValue(Field protoField, Integer collectionIndex, int columnIndex) {
266 final String fieldValue1;
267
268
269 final String cacheKey = String.format("%d,%d", collectionIndex, columnIndex);
270 String cachedValue = calculatedValueCache.get(cacheKey);
271
272 if (cachedValue == null) {
273 View view = ViewLifecycle.getView();
274
275 Object collectionElement = modelCollection.get(collectionIndex);
276 ExpressionEvaluator expressionEvaluator = ViewLifecycle.getExpressionEvaluator();
277
278
279 Map<String, Object> viewContext = view.getContext();
280 Map<String, Object> expressionContext = new HashMap<String, Object>();
281
282 if (viewContext != null) {
283 expressionContext.putAll(viewContext);
284 }
285
286 ViewLifecyclePhase phase = ViewLifecycle.getPhase();
287 if (phase.getParent() instanceof CollectionGroup) {
288 CollectionGroup collectionGroup = (CollectionGroup) phase.getParent();
289 expressionContext.put(UifConstants.ContextVariableNames.COLLECTION_GROUP, collectionGroup);
290 expressionContext.put(UifConstants.ContextVariableNames.MANAGER, collectionGroup.getLayoutManager());
291 expressionContext.put(UifConstants.ContextVariableNames.PARENT, collectionGroup);
292 }
293
294 expressionContext.put(UifConstants.ContextVariableNames.LINE, collectionElement);
295 expressionContext.put(UifConstants.ContextVariableNames.INDEX, collectionIndex);
296 expressionContext.put(UifConstants.ContextVariableNames.COMPONENT, protoField);
297
298 expressionEvaluator.evaluateExpressionsOnConfigurable(view, protoField, expressionContext);
299
300 fieldValue1 = KRADUtils.getSimpleFieldValue(collectionElement, protoField);
301
302 calculatedValueCache.put(cacheKey, fieldValue1);
303 } else {
304 fieldValue1 = cachedValue;
305 }
306
307 return fieldValue1;
308 }
309
310
311
312
313
314
315
316
317
318
319
320 private int columnTypeCompare(String val1, String val2, String sortType) {
321 final int result;
322
323 if (isOneNull(val1, val2)) {
324 result = compareOneIsNull(val1, val2);
325 } else if (UifConstants.TableToolsValues.STRING.equals(sortType)) {
326 result = val1.compareTo(val2);
327 } else if (UifConstants.TableToolsValues.NUMERIC.equals(sortType)) {
328 result = NumericValueComparator.getInstance().compare(val1, val2);
329 } else if (UifConstants.TableToolsValues.PERCENT.equals(sortType)) {
330 result = NumericValueComparator.getInstance().compare(val1, val2);
331 } else if (UifConstants.TableToolsValues.DATE.equals(sortType)) {
332 result = TemporalValueComparator.getInstance().compare(val1, val2);
333 } else if (UifConstants.TableToolsValues.CURRENCY.equals(sortType)) {
334
335 KualiDecimal decimal1 = new KualiDecimal(val1.replaceAll("[^0-9.]", ""));
336 KualiDecimal decimal2 = new KualiDecimal(val2.replaceAll("[^0-9.]", ""));
337
338 result = decimal1.compareTo(decimal2);
339 } else {
340 throw new RuntimeException("unknown sort type: " + sortType);
341 }
342
343 return result;
344 }
345
346
347
348
349
350
351
352
353 private boolean isOneNull(Object o1, Object o2) {
354 return (o1 == null || o2 == null);
355 }
356
357
358
359
360
361
362
363
364
365
366
367
368
369 private int compareOneIsNull(Object o1, Object o2) {
370 if (o1 == null) {
371 if (o2 == null) {
372 return 0;
373 }
374
375 return -1;
376 }
377
378 if (o2 != null) {
379 throw new IllegalStateException("at least one parameter must be null");
380 }
381
382 return 1;
383 }
384
385
386
387
388
389
390
391
392
393 protected List<Field> buildPrototypeRow() {
394 LineBuilderContext lineBuilderContext = new LineBuilderContext(0, modelCollection.get(0), null, false, form,
395 collectionGroup, collectionGroup.getLineActions());
396
397 CollectionGroupLineBuilder collectionGroupLineBuilder =
398 collectionGroup.getCollectionGroupBuilder().getCollectionGroupLineBuilder(lineBuilderContext);
399 collectionGroupLineBuilder.preprocessLine();
400
401 TableRowBuilder tableRowBuilder = new TableRowBuilder(collectionGroup, lineBuilderContext);
402 TableRow tableRow = tableRowBuilder.buildRow();
403
404 return tableRow.getColumns();
405 }
406 }