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