View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.component.html.ext;
21  
22  import java.text.CollationKey;
23  import java.text.Collator;
24  import java.util.Collections;
25  import java.util.Comparator;
26  import java.util.HashMap;
27  import java.util.List;
28  import java.util.Map;
29  
30  import javax.faces.context.FacesContext;
31  import javax.faces.model.DataModel;
32  
33  import org.apache.commons.beanutils.PropertyUtils;
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  
37  public final class SortableModel extends BaseSortableModel
38  {    
39      private static final Log log = LogFactory.getLog(SortableModel.class);
40      
41      private SortCriterion _sortCriterion = null;
42      
43      /**
44       * Create a new SortableModel from the given instance.
45       * @param model This will be converted into a {@link DataModel}
46       * @see #setWrappedData
47       */
48      public SortableModel(Object model) 
49      {
50          super(model);
51      }
52      
53      /**
54       * No arg constructor for use as a managed-bean.
55       * Must call setWrappedData before using this instance.
56       */
57      public SortableModel(){}
58      
59      /**
60       * Sets the underlying data being managed by this instance.
61       * @param data This Object will be converted into a
62       * {@link DataModel}.
63       */
64      public void setWrappedData(Object data) 
65      {
66          super.setWrappedData(data);
67          setSortCriteria(null);
68      }
69      
70      /**
71       * Checks to see if the underlying collection is sortable by the given property.
72       * @param property The name of the property to sort the underlying collection by.
73       * @return true, if the property implements java.lang.Comparable
74       */
75      public boolean isSortable(String property) 
76      {
77          final int oldIndex = _model.getRowIndex();
78          try 
79          {
80              _model.setRowIndex(0);
81              if (!_model.isRowAvailable())
82                  return false; // if there is no data in the table then nothing is sortable
83              
84              try 
85              {   
86                  Object propertyValue = PropertyUtils.getProperty(_model.getRowData(),property);                
87                  
88                  // when the value is null, we don't know if we can sort it.
89                  // by default let's support sorting of null values, and let the user
90                  // turn off sorting if necessary:
91                  return (propertyValue instanceof Comparable) || (propertyValue == null);
92              } 
93              catch (RuntimeException e) 
94              {
95                  // don't propagate this exception out. This is because it might break
96                  // the VE.
97                  log.warn(e);
98                  return false;
99              }
100             catch (Exception e) {
101                 log.warn(e);
102                 return false;
103             }
104         } 
105         finally 
106         {
107             _model.setRowIndex(oldIndex);
108         }
109     }
110     
111     public List getSortCriteria() 
112     {
113         return (_sortCriterion == null) ? Collections.EMPTY_LIST : Collections.singletonList(_sortCriterion);
114     }
115     
116     public void setSortCriteria(List criteria) 
117     {
118         if ((criteria == null) || (criteria.isEmpty())) 
119         {
120             _sortCriterion = null;
121             setComparator(null);
122         } 
123         else 
124         {
125             SortCriterion sc = (SortCriterion) criteria.get(0);
126             if ((_sortCriterion == null) || (!_sortCriterion.equals(sc))) 
127             {
128                 _sortCriterion = sc;
129                 
130                 /*
131                  * Sorts the underlying collection by the given property, in the
132                  * given direction.
133                  * @param property The name of the property to sort by. The value of this
134                  * property must implement java.lang.Comparable.
135                  * @param isAscending true if the collection is to be sorted in
136                  * ascending order.
137                  * @todo support -1 for rowCount
138                  */
139                Comparator comp = new Comp(_sortCriterion.getProperty());                       
140                 
141                 if (!_sortCriterion.isAscending())
142                     comp = new Inverter(comp);
143                 setComparator(comp);
144             }
145         }
146     }
147     
148     public String toString() 
149     {
150         return "SortableModel[" + _model + "]";
151     }
152     
153     private final class Comp implements Comparator 
154     {
155         private final String _prop;
156         
157         private Collator _collator;
158         
159         private Map _collationKeys;
160         
161         public Comp(String property) 
162         {            
163             _prop = property;
164             _collator = Collator.getInstance(FacesContext.getCurrentInstance().getViewRoot().getLocale()); 
165             _collationKeys = new HashMap();
166         }               
167         
168         public int compare(Object o1, Object o2) 
169         {
170             Object value1 = null;
171             Object value2 = null;
172             try {
173                 value1 = PropertyUtils.getProperty(o1,_prop);  
174                 value2 = PropertyUtils.getProperty(o2,_prop);  
175             }
176             catch (Exception exc) {    
177                 log.error(exc);
178             }
179                                     
180             if (value1 == null)
181                 return (value2 == null) ? 0 : -1;
182             
183             if (value2 == null)
184                 return 1;
185             
186             // ?? Sometimes, isSortable returns true
187             // even if the underlying object is not a Comparable.
188             // This happens if the object at rowIndex zero is null.
189             // So test before we cast:
190             
191             if (value1 instanceof String) {
192                 //if the object is a String we best compare locale-sesitive
193                 CollationKey collationKey1 = getCollationKey((String)value1);
194                 CollationKey collationKey2 = getCollationKey((String)value2);
195                 
196                 return collationKey1.compareTo(collationKey2);
197             }
198             else if (value1 instanceof Comparable) 
199             {
200                 return ((Comparable) value1).compareTo(value2);
201             } 
202             else 
203             {
204                 // if the object is not a Comparable, then
205                 // the best we can do is string comparison:
206                 return value1.toString().compareTo(value2.toString());
207             }
208         }         
209         
210         private CollationKey getCollationKey(String propertyValue) {
211             CollationKey key = (CollationKey)_collationKeys.get(propertyValue);
212             if (key == null) {
213                 key = _collator.getCollationKey(propertyValue);
214                 _collationKeys.put(propertyValue, key);
215             }
216                 
217             return key;
218         }
219     }
220     /**
221      *
222      */
223     private static final class Inverter implements Comparator 
224     {
225         private final Comparator _comp;
226         
227         public Inverter(Comparator comp) 
228         {
229             _comp = comp;
230         }
231         
232         public int compare(Object o1, Object o2) 
233         {
234             return _comp.compare(o2, o1);
235         }              
236     }      
237 }