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.sql.ResultSet;
23  import java.util.ArrayList;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Comparator;
27  import java.util.List;
28  
29  import javax.faces.model.ArrayDataModel;
30  import javax.faces.model.DataModel;
31  import javax.faces.model.ListDataModel;
32  import javax.faces.model.ResultDataModel;
33  import javax.faces.model.ResultSetDataModel;
34  import javax.faces.model.ScalarDataModel;
35  import javax.servlet.jsp.jstl.sql.Result;
36  
37  /**
38   * BaseSortableModel provides a DataModel that is automatically sorted by the specified Comparator.
39   * Each time the Comparator is set, the model will be resorted.
40   * @since 1.1.7
41   * @version $Revision: 691871 $
42   */
43  public class BaseSortableModel extends DataModel
44  {    
45      private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
46      
47      protected DataModel _model = null;
48      private Object _wrappedData = null;
49      
50      private IntList _sortedIndicesList = null,  // from baseIndex to sortedIndex
51                      _baseIndicesList = null;    // from sortedIndex to baseIndex
52      
53      private Comparator _comparator = null;
54      
55  
56      public Comparator getComparator() {
57          return _comparator;
58      }
59  
60      public void setComparator(Comparator comparator) {
61          this._comparator = comparator;
62          sort();
63      }
64  
65      /**
66       * Create a new SortableModel from the given instance.
67       * @param model This will be converted into a {@link DataModel}
68       * @see #setWrappedData
69       */
70      public BaseSortableModel(Object model) 
71      {
72          setWrappedData(model);
73      }
74      
75      /**
76       * No arg constructor for use as a managed-bean.
77       * Must call setWrappedData before using this instance.
78       */
79      public BaseSortableModel(){}
80      
81      public Object getRowData() 
82      {
83          return _model.getRowData();
84      }
85      
86      public Object getWrappedData() 
87      {
88          return _wrappedData;
89      }
90      
91      public boolean isRowAvailable() 
92      {
93          return _model.isRowAvailable();
94      }
95      
96      /**
97       * Sets the underlying data being managed by this instance.
98       * @param data This Object will be converted into a
99       * {@link DataModel}.
100      */
101     public void setWrappedData(Object data) 
102     {
103         _baseIndicesList = null;
104         _model = toDataModel(data);
105         _sortedIndicesList = null;
106         _wrappedData = data;
107     }
108     
109     protected DataModel toDataModel(Object data) 
110     {
111         if (data == null)
112         {
113             return EMPTY_DATA_MODEL;
114         }
115         else if (data instanceof DataModel)
116         {
117             return (DataModel) data;
118         }
119         else if (data instanceof List)
120         {
121             return new ListDataModel((List) data);
122         }
123         // accept a Collection is not supported in the Spec
124         else if (data instanceof Collection)
125         {
126             return new ListDataModel(new ArrayList((Collection) data));
127         }
128         else if (OBJECT_ARRAY_CLASS.isAssignableFrom(data.getClass()))
129         {
130             return new ArrayDataModel((Object[]) data);
131         }
132         else if (data instanceof ResultSet)
133         {
134             return new ResultSetDataModel((ResultSet) data);
135         }
136         else if (data instanceof Result)
137         {
138             return new ResultDataModel((Result) data);
139         }
140         else
141         {
142             return new ScalarDataModel(data);
143         }
144     }
145     
146     public int getRowCount() 
147     {
148         return _model.getRowCount();
149     }
150     
151     public void setRowIndex(int rowIndex) 
152     {
153         int baseIndex = _toBaseIndex(rowIndex);
154         _model.setRowIndex(baseIndex);
155     }
156     
157     public int getRowIndex() 
158     {
159         int baseIndex = _model.getRowIndex();
160         return _toSortedIndex(baseIndex);
161     }       
162     
163     public String toString() 
164     {
165         return "BaseSortableModel[" + _model + "]";
166     }
167     
168     /**
169      * Sorts the underlying collection by the Comparator.
170      * @todo support -1 for rowCount
171      */
172     public void sort() 
173     {
174         Comparator comparator = getComparator();
175         if (null == comparator)
176         {
177             // restore unsorted order:
178             _baseIndicesList = _sortedIndicesList = null;
179             return;
180         } 
181         
182         //TODO: support -1 for rowCount:
183         int sz = getRowCount();
184         if ((_baseIndicesList == null) || (_baseIndicesList.size() != sz)) 
185         {
186             // we do not want to mutate the original data.
187             // however, instead of copying the data and sorting the copy,
188             // we will create a list of indices into the original data, and
189             // sort the indices. This way, when certain rows are made current
190             // in this Collection, we can make them current in the underlying
191             // DataModel as well.            
192             _baseIndicesList = new IntList(sz);
193         }
194         
195         final int rowIndex = _model.getRowIndex();
196         
197         _model.setRowIndex(0);
198         // Make sure the model has that row 0! (It could be empty.)
199         if (_model.isRowAvailable()) 
200         {            
201             Collections.sort(_baseIndicesList, new RowDataComparator(comparator, _model));
202             _sortedIndicesList = null;
203         }
204         
205         _model.setRowIndex(rowIndex);
206     }
207     
208     private int _toSortedIndex(int baseIndex) 
209     {
210         if ((_sortedIndicesList == null) && (_baseIndicesList != null)) 
211         {
212             _sortedIndicesList = (IntList) _baseIndicesList.clone();
213             for(int i=0; i<_baseIndicesList.size(); i++) 
214             {
215                 Integer base = (Integer) _baseIndicesList.get(i);
216                 _sortedIndicesList.set(base.intValue(), new Integer(i));
217             }
218         }
219         
220         return _convertIndex(baseIndex, _sortedIndicesList);
221     }
222     
223     private int _toBaseIndex(int sortedIndex) 
224     {
225         return _convertIndex(sortedIndex, _baseIndicesList);
226     }
227     
228     private int _convertIndex(int index, List indices) 
229     {
230         if (index < 0) // -1 is special
231             return index;
232         
233         if ((indices != null) && (indices.size() > index)) 
234         {
235             index = ((Integer) indices.get(index)).intValue();
236         }
237         return index;
238     }              
239     
240     private static final class IntList extends ArrayList implements Cloneable 
241     {
242         public IntList(int size) 
243         {
244             super(size);
245             _expandToSize(size);
246         }
247         
248         private void _expandToSize(int desiredSize) 
249         {
250             for(int i=0; i<desiredSize; i++) 
251                 add(new Integer(i));            
252         }
253     }
254     
255     protected static class RowDataComparator implements Comparator
256     {
257         private Comparator dataComparator = null;
258         private DataModel dataModel = null;
259         
260         public RowDataComparator(Comparator comparator, DataModel model)
261         {
262             this.dataComparator = comparator;
263             this.dataModel = model;
264         }
265         
266         public int compare(Object arg1, Object arg2) {
267             Integer r1 = (Integer)arg1;
268             Integer r2 = (Integer)arg2;
269             dataModel.setRowIndex(r1.intValue());
270             Object rowData1 = dataModel.getRowData();
271             dataModel.setRowIndex(r2.intValue());
272             Object rowData2 = dataModel.getRowData();
273             
274             return dataComparator.compare(rowData1, rowData2);
275         }
276     }
277     
278     private static final DataModel EMPTY_DATA_MODEL = new _SerializableDataModel()
279     {
280         public boolean isRowAvailable()
281         {
282             return false;
283         }
284 
285         public int getRowCount()
286         {
287             return 0;
288         }
289 
290         public Object getRowData()
291         {
292             throw new IllegalArgumentException();
293         }
294 
295         public int getRowIndex()
296         {
297             return -1;
298         }
299 
300         public void setRowIndex(int i)
301         {
302             if (i < -1)
303                 throw new IndexOutOfBoundsException("Index < 0 : " + i);
304         }
305 
306         public Object getWrappedData()
307         {
308             return null;
309         }
310 
311         public void setWrappedData(Object obj)
312         {
313             if (obj == null)
314                 return; //Clearing is allowed
315             throw new UnsupportedOperationException(this.getClass().getName()
316                             + " UnsupportedOperationException");
317         }
318     };
319 }