View Javadoc
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.tobago.internal.util;
21  
22  import org.apache.myfaces.tobago.component.Attributes;
23  import org.apache.myfaces.tobago.component.RendererTypes;
24  import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
25  import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
26  import org.apache.myfaces.tobago.model.SheetState;
27  import org.apache.myfaces.tobago.util.MessageUtils;
28  import org.apache.myfaces.tobago.util.ValueExpressionComparator;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  
32  import javax.el.ValueExpression;
33  import javax.faces.application.FacesMessage;
34  import javax.faces.component.UIColumn;
35  import javax.faces.component.UICommand;
36  import javax.faces.component.UIComponent;
37  import javax.faces.component.UIInput;
38  import javax.faces.component.UIOutput;
39  import javax.faces.component.UISelectBoolean;
40  import javax.faces.component.UISelectMany;
41  import javax.faces.component.UISelectOne;
42  import javax.faces.context.FacesContext;
43  import javax.faces.model.DataModel;
44  import java.lang.invoke.MethodHandles;
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.Collections;
48  import java.util.Comparator;
49  import java.util.List;
50  
51  public class SortingUtils {
52  
53    private static final Logger LOG = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
54  
55    private SortingUtils() {
56    }
57  
58    public static void sort(final AbstractUISheet sheet, final Comparator comparator) {
59      final FacesContext facesContext = FacesContext.getCurrentInstance();
60      Object data = sheet.getValue();
61      if (data instanceof DataModel) {
62        data = ((DataModel) data).getWrappedData();
63      }
64      final SheetState sheetState = sheet.getSheetState(facesContext);
65  
66      final String sortedColumnId = sheetState.getSortedColumnId();
67      LOG.debug("sorterId = '{}'", sortedColumnId);
68  
69      boolean success = false;
70      if (sortedColumnId != null) {
71        final UIColumn column = (UIColumn) sheet.findComponent(sortedColumnId);
72        if (column != null) {
73          success = sort(facesContext, sheet, data, column, sheetState, comparator);
74        } else {
75          LOG.error("No column to sort found, sorterId = '{}'!", sortedColumnId);
76          addNotSortableMessage(facesContext, null);
77        }
78      } else {
79        LOG.debug("No sorterId!");
80      }
81  
82      if (!success) {
83        sheetState.resetSortState();
84      }
85    }
86  
87    private static boolean sort(
88        final FacesContext facesContext, final AbstractUISheet sheet, final Object data, final UIColumn column,
89        final SheetState sheetState, Comparator comparator) {
90  
91      final Comparator actualComparator;
92  
93      if (data instanceof List || data instanceof Object[]) {
94        try {
95          final UIComponent child = getFirstSortableChild(column.getChildren());
96          if (child != null) {
97            final boolean descending = !sheetState.isAscending();
98            final String attribute = (child instanceof AbstractUICommand ? Attributes.label : Attributes.value).getName();
99            final ValueExpression expression = child.getValueExpression(attribute);
100           if (expression != null) {
101             final String var = sheet.getVar();
102             if (var == null) {
103                 LOG.error("No sorting performed. Property var of sheet is not set!");
104                 addNotSortableMessage(facesContext, column);
105                 return false;
106             }
107             actualComparator = new ValueExpressionComparator(facesContext, var, expression, descending, comparator);
108           } else {
109             LOG.error("No sorting performed, because no expression found for "
110                     + "attribute '{}' in component '{}' with id='{}'! You may check the type of the component!",
111                 attribute, child.getClass().getName(), child.getClientId());
112             addNotSortableMessage(facesContext, column);
113             return false;
114           }
115         } else {
116           LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
117           addNotSortableMessage(facesContext, column);
118           return false;
119         }
120       } catch (final Exception e) {
121         LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
122         addNotSortableMessage(facesContext, column);
123         return false;
124       }
125 
126       // memorize selected rows
127       List<Object> selectedDataRows = null;
128       if (sheetState.getSelectedRows().size() > 0) {
129         selectedDataRows = new ArrayList<>(sheetState.getSelectedRows().size());
130         Object dataRow;
131         for (final Integer index : sheetState.getSelectedRows()) {
132           if (data instanceof List) {
133             dataRow = ((List) data).get(index);
134           } else {
135             dataRow = ((Object[]) data)[index];
136           }
137           selectedDataRows.add(dataRow);
138         }
139       }
140 
141       // do sorting
142       if (data instanceof List) {
143         Collections.sort((List) data, actualComparator);
144       } else { // value is instanceof Object[]
145         Arrays.sort((Object[]) data, actualComparator);
146       }
147 
148       // restore selected rows
149       if (selectedDataRows != null) {
150         sheetState.getSelectedRows().clear();
151         for (final Object dataRow : selectedDataRows) {
152           int index = -1;
153           if (data instanceof List) {
154             for (int i = 0; i < ((List) data).size() && index < 0; i++) {
155               if (dataRow == ((List) data).get(i)) {
156                 index = i;
157               }
158             }
159           } else {
160             for (int i = 0; i < ((Object[]) data).length && index < 0; i++) {
161               if (dataRow == ((Object[]) data)[i]) {
162                 index = i;
163               }
164             }
165           }
166           if (index >= 0) {
167             sheetState.getSelectedRows().add(index);
168           }
169         }
170       }
171 
172     } else {  // DataModel?, ResultSet, Result or Object
173       LOG.warn("Sorting not supported for type '{}'.", data != null ? data.getClass().toString() : "null");
174       addNotSortableMessage(facesContext, column);
175       return false;
176     }
177     return true;
178   }
179 
180   private static void addNotSortableMessage(final FacesContext facesContext, final UIColumn column) {
181     if (column != null) {
182       final String label = MessageUtils.getLabel(facesContext, column);
183       facesContext.addMessage(column.getClientId(facesContext),
184           MessageUtils.getMessage(
185               facesContext, FacesMessage.SEVERITY_WARN, AbstractUISheet.NOT_SORTABLE_COL_MESSAGE_ID, label));
186     } else {
187       facesContext.addMessage(null,
188           MessageUtils.getMessage(
189               facesContext, FacesMessage.SEVERITY_WARN, AbstractUISheet.NOT_SORTABLE_MESSAGE_ID));
190     }
191   }
192 
193   private static UIComponent getFirstSortableChild(final List<UIComponent> children) {
194     UIComponent result = null;
195 
196     for (UIComponent child : children) {
197       result = child;
198       if (child instanceof UISelectMany
199           || child instanceof UISelectOne
200           || child instanceof UISelectBoolean
201           || (child instanceof AbstractUICommand && child.getChildren().isEmpty())
202           || (child instanceof UIInput && RendererTypes.HIDDEN.equals(child.getRendererType()))) {
203         continue;
204         // look for a better component if any
205       }
206       if (child instanceof UIOutput) {
207         break;
208       }
209       if (child instanceof UICommand
210           || child instanceof javax.faces.component.UIPanel) {
211         child = getFirstSortableChild(child.getChildren());
212         if (child instanceof UIOutput) {
213           break;
214         }
215       }
216     }
217     return result;
218   }
219 }