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