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.logging.Log;
021    import org.apache.commons.logging.LogFactory;
022    import org.apache.myfaces.tobago.TobagoConstants;
023    import static org.apache.myfaces.tobago.TobagoConstants.ATTR_SORTABLE;
024    import org.apache.myfaces.tobago.event.SortActionEvent;
025    import org.apache.myfaces.tobago.model.SheetState;
026    import org.apache.myfaces.tobago.util.BeanComparator;
027    import org.apache.myfaces.tobago.util.ValueBindingComparator;
028    
029    import javax.faces.component.UIColumn;
030    import javax.faces.component.UIComponent;
031    import javax.faces.component.UIInput;
032    import javax.faces.component.UIOutput;
033    import javax.faces.component.UISelectBoolean;
034    import javax.faces.context.FacesContext;
035    import javax.faces.el.EvaluationException;
036    import javax.faces.el.MethodBinding;
037    import javax.faces.el.MethodNotFoundException;
038    import javax.faces.el.ValueBinding;
039    import javax.faces.model.DataModel;
040    
041    import java.util.ArrayList;
042    import java.util.Arrays;
043    import java.util.Collections;
044    import java.util.Comparator;
045    import java.util.Iterator;
046    import java.util.List;
047    
048    /*
049     * User: weber
050     * Date: Mar 7, 2005
051     * Time: 4:01:27 PM
052     */
053    public class Sorter extends MethodBinding {
054    
055      private static final Log LOG = LogFactory.getLog(Sorter.class);
056    
057      private Comparator comparator;
058    
059      public Object invoke(FacesContext facesContext, Object[] aobj)
060          throws EvaluationException {
061        if (aobj[0] instanceof SortActionEvent) {
062          SortActionEvent sortEvent = (SortActionEvent) aobj[0];
063          if (LOG.isDebugEnabled()) {
064            LOG.debug("sorterId = " + sortEvent.getComponent().getId());
065          }
066          UIColumn column = sortEvent.getColumn();
067          UIData data = sortEvent.getSheet();
068    
069          Object value = data.getValue();
070          if (value instanceof DataModel) {
071            value = ((DataModel) value).getWrappedData();
072          }
073          SheetState sheetState = data.getSheetState(facesContext);
074    
075          Comparator actualComparator = null;
076    
077          if (value instanceof List || value instanceof Object[]) {
078            String sortProperty;
079    
080            try {
081    
082              UIComponent child = getFirstSortableChild(column.getChildren());
083              if (child != null) {
084                ValueBinding valueBinding = child.getValueBinding("value");
085                String var = data.getVar();
086    
087                if (valueBinding != null) {
088                  if (isSimpleProperty(valueBinding.getExpressionString())) {
089                    String expressionString = valueBinding.getExpressionString();
090                    if (expressionString.startsWith("#{")
091                        && expressionString.endsWith("}")) {
092                      expressionString =
093                          expressionString.substring(2,
094                              expressionString.length() - 1);
095                    }
096                    sortProperty = expressionString.substring(var.length() + 1);
097    
098                    actualComparator = new BeanComparator(
099                        sortProperty, comparator, !sheetState.isAscending());
100    
101                    if (LOG.isDebugEnabled()) {
102                      LOG.debug("Sort property is " + sortProperty);
103                    }
104                  } else {
105                    actualComparator = new ValueBindingComparator(facesContext, var,
106                        valueBinding, !sheetState.isAscending(), comparator);
107                  }
108                }
109    
110              } else {
111                LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
112                unsetSortableAttribute(column);
113                return null;
114              }
115            } catch (Exception e) {
116              LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
117              if (column != null) {
118                unsetSortableAttribute(column);
119              }
120              return null;
121            }
122    
123            // TODO: locale / comparator parameter?
124            // don't compare numbers with Collator.getInstance() comparator
125    //        Comparator comparator = Collator.getInstance();
126    //          comparator = new RowComparator(ascending, method);
127    
128            // memorize selected rows
129            List<Object> selectedDataRows = null;
130            if (sheetState.getSelectedRows() != null && sheetState.getSelectedRows().size() > 0) {
131              selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size());
132              Object dataRow; 
133              for (Integer index : sheetState.getSelectedRows()) {
134                if (value instanceof List) {
135                  dataRow = ((List) value).get(index);
136                } else {
137                  dataRow = ((Object[]) value)[index];
138                }
139                selectedDataRows.add(dataRow);
140              }
141            }
142            
143            // do sorting
144            if (value instanceof List) {
145              Collections.sort((List) value, actualComparator);
146            } else { // value is instanceof Object[]
147              Arrays.sort((Object[]) value, actualComparator);
148            }
149    
150            // restore selected rows
151            if (selectedDataRows != null) {
152              sheetState.getSelectedRows().clear();
153              for (Object dataRow : selectedDataRows) {
154                int index = -1;
155                if (value instanceof List) {
156                  for (int i = 0; i < ((List) value).size() && index < 0; i++) {
157                    if (dataRow == ((List) value).get(i)) {
158                      index = i;
159                    }
160                  }
161                } else {
162                  for (int i = 0; i < ((Object[]) value).length && index < 0; i++) {
163                    if (dataRow == ((Object[]) value)[i]) {
164                      index = i;
165                    }
166                  }
167                }
168                if (index >= 0) {
169                  sheetState.getSelectedRows().add(index);
170                }
171              }
172            }
173            
174          } else {  // DataModel?, ResultSet, Result or Object
175            LOG.warn("Sorting not supported for type "
176                + (value != null ? value.getClass().toString() : "null"));
177          }
178        }
179        return null;
180      }
181    
182      private boolean isSimpleProperty(String expressionString) {
183        return expressionString.matches("^#\\{(\\w+(\\.\\w)*)\\}$");
184      }
185    
186      private void unsetSortableAttribute(UIColumn uiColumn) {
187        LOG.warn("removing attribute sortable from column " + uiColumn.getId());
188        uiColumn.getAttributes().put(ATTR_SORTABLE, Boolean.FALSE);
189      }
190    
191      private UIComponent getFirstSortableChild(List children) {
192        UIComponent child = null;
193    
194        for (Iterator iter = children.iterator(); iter.hasNext();) {
195          child = (UIComponent) iter.next();
196          if (child instanceof UICommand
197              || child instanceof javax.faces.component.UIPanel) {
198            child = getFirstSortableChild(child.getChildren());
199          }
200          if (child instanceof UISelectMany
201              || child instanceof UISelectOne
202              || child instanceof UISelectBoolean) {
203            continue;
204          } else if (child instanceof UIInput
205              && TobagoConstants.RENDERER_TYPE_HIDDEN.equals(child.getRendererType())) {
206            continue;
207          } else if (child instanceof UIOutput) {
208            break;
209          }
210        }
211        return child;
212      }
213    
214      public Class getType(FacesContext facescontext)
215          throws MethodNotFoundException {
216        return String.class;
217      }
218    
219      public Comparator getComparator() {
220        return comparator;
221      }
222    
223      public void setComparator(Comparator comparator) {
224        this.comparator = comparator;
225      }
226    }
227