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