001    package org.apache.myfaces.tobago.component;
002    
003    /*
004     * Licensed to the Apache Software Foundation (ASF) under one or more
005     * contributor license agreements.  See the NOTICE file distributed with
006     * this work for additional information regarding copyright ownership.
007     * The ASF licenses this file to You under the Apache License, Version 2.0
008     * (the "License"); you may not use this file except in compliance with
009     * the License.  You may obtain a copy of the License at
010     *
011     *      http://www.apache.org/licenses/LICENSE-2.0
012     *
013     * Unless required by applicable law or agreed to in writing, software
014     * distributed under the License is distributed on an "AS IS" BASIS,
015     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016     * See the License for the specific language governing permissions and
017     * limitations under the License.
018     */
019    
020    import org.apache.commons.lang.StringUtils;
021    import org.apache.myfaces.tobago.compat.FacesUtils;
022    import org.apache.myfaces.tobago.event.SortActionEvent;
023    import org.apache.myfaces.tobago.internal.component.AbstractUICommand;
024    import org.apache.myfaces.tobago.internal.component.AbstractUISheet;
025    import org.apache.myfaces.tobago.model.SheetState;
026    import org.apache.myfaces.tobago.util.BeanComparator;
027    import org.slf4j.Logger;
028    import org.slf4j.LoggerFactory;
029    
030    import javax.faces.component.UIColumn;
031    import javax.faces.component.UICommand;
032    import javax.faces.component.UIComponent;
033    import javax.faces.component.UIInput;
034    import javax.faces.component.UIOutput;
035    import javax.faces.component.UISelectBoolean;
036    import javax.faces.component.UISelectMany;
037    import javax.faces.component.UISelectOne;
038    import javax.faces.context.FacesContext;
039    import javax.faces.model.DataModel;
040    import java.util.ArrayList;
041    import java.util.Arrays;
042    import java.util.Collections;
043    import java.util.Comparator;
044    import java.util.List;
045    
046    public class Sorter {
047    
048      private static final Logger LOG = LoggerFactory.getLogger(Sorter.class);
049    
050      private Comparator comparator;
051    
052      public void perform(SortActionEvent sortEvent) {
053        if (LOG.isDebugEnabled()) {
054          LOG.debug("sorterId = {}", sortEvent.getComponent().getId());
055        }
056        UIColumn column = sortEvent.getColumn();
057        AbstractUISheet data = (AbstractUISheet) sortEvent.getComponent();
058    
059        Object value = data.getValue();
060        if (value instanceof DataModel) {
061          value = ((DataModel) value).getWrappedData();
062        }
063        FacesContext facesContext = FacesContext.getCurrentInstance();
064        SheetState sheetState = data.getSheetState(facesContext);
065    
066        Comparator actualComparator = null;
067    
068        if (value instanceof List || value instanceof Object[]) {
069          String sortProperty;
070    
071          try {
072    
073            UIComponent child = getFirstSortableChild(column.getChildren());
074            if (child != null) {
075    
076              String attributeName = child instanceof AbstractUICommand ? Attributes.LABEL:Attributes.VALUE;
077              if (FacesUtils.hasValueBindingOrValueExpression(child, attributeName)) {
078                String var = data.getVar();
079                String expressionString = FacesUtils.getExpressionString(child, attributeName);
080                if (isSimpleProperty(expressionString)) {
081                  if (expressionString.startsWith("#{")
082                      && expressionString.endsWith("}")) {
083                    expressionString =
084                        expressionString.substring(2,
085                            expressionString.length() - 1);
086                  }
087                  sortProperty = expressionString.substring(var.length() + 1);
088    
089                  actualComparator = new BeanComparator(
090                      sortProperty, comparator, !sheetState.isAscending());
091    
092                  if (LOG.isDebugEnabled()) {
093                    LOG.debug("Sort property is {}", sortProperty);
094                  }
095                } else {
096    
097                  boolean descending = !sheetState.isAscending();
098                  actualComparator =
099                      FacesUtils.getBindingOrExpressionComparator(facesContext, child, var, descending, comparator);
100                }
101              }
102    
103            } else {
104              LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
105              unsetSortableAttribute(column);
106              return;
107            }
108          } catch (Exception e) {
109            LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
110            if (column != null) {
111              unsetSortableAttribute(column);
112            }
113            return;
114          }
115    
116          // TODO: locale / comparator parameter?
117          // don't compare numbers with Collator.getInstance() comparator
118    //        Comparator comparator = Collator.getInstance();
119    //          comparator = new RowComparator(ascending, method);
120    
121          // memorize selected rows
122          List<Object> selectedDataRows = null;
123          if (sheetState.getSelectedRows().size() > 0) {
124            selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size());
125            Object dataRow;
126            for (Integer index : sheetState.getSelectedRows()) {
127              if (value instanceof List) {
128                dataRow = ((List) value).get(index);
129              } else {
130                dataRow = ((Object[]) value)[index];
131              }
132              selectedDataRows.add(dataRow);
133            }
134          }
135    
136          // do sorting
137          if (value instanceof List) {
138            Collections.sort((List) value, actualComparator);
139          } else { // value is instanceof Object[]
140            Arrays.sort((Object[]) value, actualComparator);
141          }
142    
143          // restore selected rows
144          if (selectedDataRows != null) {
145            sheetState.getSelectedRows().clear();
146            for (Object dataRow : selectedDataRows) {
147              int index = -1;
148              if (value instanceof List) {
149                for (int i = 0; i < ((List) value).size() && index < 0; i++) {
150                  if (dataRow == ((List) value).get(i)) {
151                    index = i;
152                  }
153                }
154              } else {
155                for (int i = 0; i < ((Object[]) value).length && index < 0; i++) {
156                  if (dataRow == ((Object[]) value)[i]) {
157                    index = i;
158                  }
159                }
160              }
161              if (index >= 0) {
162                sheetState.getSelectedRows().add(index);
163              }
164            }
165          }
166    
167        } else {  // DataModel?, ResultSet, Result or Object
168          LOG.warn("Sorting not supported for type "
169              + (value != null ? value.getClass().toString() : "null"));
170        }
171      }
172    
173      // XXX needs to be tested
174      // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w
175      boolean isSimpleProperty(String expressionString) {
176        if (expressionString.startsWith("#{") && expressionString.endsWith("}")) {
177          String inner = expressionString.substring(2, expressionString.length() - 1);
178          String[] parts = StringUtils.split(inner, ".");
179          for (String part : parts) {
180            if (!StringUtils.isAlpha(part)) {
181              return false;
182            }
183          }
184          return true;
185        }
186        return false;
187      }
188    
189      private void unsetSortableAttribute(UIColumn uiColumn) {
190        LOG.warn("removing attribute sortable from column " + uiColumn.getId());
191        uiColumn.getAttributes().put(Attributes.SORTABLE, Boolean.FALSE);
192      }
193    
194      private UIComponent getFirstSortableChild(List children) {
195        UIComponent child = null;
196    
197        for (Object aChildren : children) {
198          child = (UIComponent) aChildren;
199          if (child instanceof UISelectMany
200              || child instanceof UISelectOne
201              || child instanceof UISelectBoolean
202              || child instanceof AbstractUICommand
203              || (child instanceof UIInput && RendererTypes.HIDDEN.equals(child.getRendererType()))) {
204            continue;
205            // look for a better component if any
206          }
207          if (child instanceof UIOutput) {
208            break;
209          }
210          if (child instanceof UICommand
211              || child instanceof javax.faces.component.UIPanel) {
212            child = getFirstSortableChild(child.getChildren());
213            if (child instanceof UIOutput) {
214              break;
215            }
216          }
217        }
218        return child;
219      }
220    
221      public Comparator getComparator() {
222        return comparator;
223      }
224    
225      public void setComparator(Comparator comparator) {
226        this.comparator = comparator;
227      }
228    }
229