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