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                ValueBinding valueBinding = child.getValueBinding("value");
086                String var = data.getVar();
087    
088                if (valueBinding != null) {
089                  if (isSimpleProperty(valueBinding.getExpressionString())) {
090                    String expressionString = valueBinding.getExpressionString();
091                    if (expressionString.startsWith("#{")
092                        && expressionString.endsWith("}")) {
093                      expressionString =
094                          expressionString.substring(2,
095                              expressionString.length() - 1);
096                    }
097                    sortProperty = expressionString.substring(var.length() + 1);
098    
099                    actualComparator = new BeanComparator(
100                        sortProperty, comparator, !sheetState.isAscending());
101    
102                    if (LOG.isDebugEnabled()) {
103                      LOG.debug("Sort property is " + sortProperty);
104                    }
105                  } else {
106                    actualComparator = new ValueBindingComparator(facesContext, var,
107                        valueBinding, !sheetState.isAscending(), comparator);
108                  }
109                }
110    
111              } else {
112                LOG.error("No sorting performed. Value is not instanceof List or Object[]!");
113                unsetSortableAttribute(column);
114                return null;
115              }
116            } catch (Exception e) {
117              LOG.error("Error while extracting sortMethod :" + e.getMessage(), e);
118              if (column != null) {
119                unsetSortableAttribute(column);
120              }
121              return null;
122            }
123    
124            // TODO: locale / comparator parameter?
125            // don't compare numbers with Collator.getInstance() comparator
126    //        Comparator comparator = Collator.getInstance();
127    //          comparator = new RowComparator(ascending, method);
128    
129            // memorize selected rows
130            List<Object> selectedDataRows = null;
131            if (sheetState.getSelectedRows() != null && sheetState.getSelectedRows().size() > 0) {
132              selectedDataRows = new ArrayList<Object>(sheetState.getSelectedRows().size());
133              Object dataRow; 
134              for (Integer index : sheetState.getSelectedRows()) {
135                if (value instanceof List) {
136                  dataRow = ((List) value).get(index);
137                } else {
138                  dataRow = ((Object[]) value)[index];
139                }
140                selectedDataRows.add(dataRow);
141              }
142            }
143            
144            // do sorting
145            if (value instanceof List) {
146              Collections.sort((List) value, actualComparator);
147            } else { // value is instanceof Object[]
148              Arrays.sort((Object[]) value, actualComparator);
149            }
150    
151            // restore selected rows
152            if (selectedDataRows != null) {
153              sheetState.getSelectedRows().clear();
154              for (Object dataRow : selectedDataRows) {
155                int index = -1;
156                if (value instanceof List) {
157                  for (int i = 0; i < ((List) value).size() && index < 0; i++) {
158                    if (dataRow == ((List) value).get(i)) {
159                      index = i;
160                    }
161                  }
162                } else {
163                  for (int i = 0; i < ((Object[]) value).length && index < 0; i++) {
164                    if (dataRow == ((Object[]) value)[i]) {
165                      index = i;
166                    }
167                  }
168                }
169                if (index >= 0) {
170                  sheetState.getSelectedRows().add(index);
171                }
172              }
173            }
174            
175          } else {  // DataModel?, ResultSet, Result or Object
176            LOG.warn("Sorting not supported for type "
177                + (value != null ? value.getClass().toString() : "null"));
178          }
179        }
180        return null;
181      }
182    
183      // XXX needs to be tested
184      // XXX was based on ^#\{(\w+(\.\w)*)\}$ which is wrong, because there is a + missing after the last \w
185      boolean isSimpleProperty(String expressionString) {
186        if (expressionString.startsWith("#{") && expressionString.endsWith("}")) {
187          String inner = expressionString.substring(2, expressionString.length() - 1);
188          String[] parts = StringUtils.split(inner, ".");
189          for (String part : parts) {
190            if (!StringUtils.isAlpha(part)) {
191              return false;
192            }
193          }
194          return true;
195        }
196        return false;
197      }
198    
199      private void unsetSortableAttribute(UIColumn uiColumn) {
200        LOG.warn("removing attribute sortable from column " + uiColumn.getId());
201        uiColumn.getAttributes().put(ATTR_SORTABLE, Boolean.FALSE);
202      }
203    
204      private UIComponent getFirstSortableChild(List children) {
205        UIComponent child = null;
206    
207        for (Iterator iter = children.iterator(); iter.hasNext();) {
208          child = (UIComponent) iter.next();
209          if (child instanceof UICommand
210              || child instanceof javax.faces.component.UIPanel) {
211            child = getFirstSortableChild(child.getChildren());
212          }
213          if (child instanceof UISelectMany
214              || child instanceof UISelectOne
215              || child instanceof UISelectBoolean) {
216            continue;
217          } else if (child instanceof UIInput
218              && TobagoConstants.RENDERER_TYPE_HIDDEN.equals(child.getRendererType())) {
219            continue;
220          } else if (child instanceof UIOutput) {
221            break;
222          }
223        }
224        return child;
225      }
226    
227      public Class getType(FacesContext facescontext)
228          throws MethodNotFoundException {
229        return String.class;
230      }
231    
232      public Comparator getComparator() {
233        return comparator;
234      }
235    
236      public void setComparator(Comparator comparator) {
237        this.comparator = comparator;
238      }
239    }
240