001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kns.web.struts.form;
017
018import org.apache.commons.beanutils.BeanComparator;
019import org.apache.commons.beanutils.PropertyUtils;
020import org.apache.commons.lang.StringUtils;
021import org.kuali.rice.kns.util.TableRenderUtil;
022
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.Date;
026import java.util.List;
027
028/**
029 * This class holds the metadata necessary to render a table when displaytag is not being used.
030 */
031public class KualiTableRenderFormMetadata {
032    private int viewedPageNumber;
033    private int totalNumberOfPages;
034    private int firstRowIndex;
035    private int lastRowIndex;
036    private int switchToPageNumber;
037
038    /**
039     * The number of rows that match the query criteria
040     */
041    private int resultsActualSize;
042
043    /**
044     * The number of rows that match the query criteria or
045     *  the max results limit size (if applicable), whichever is less
046     */
047    private int resultsLimitedSize;
048
049    /**
050     * when the looked results screen was rendered, the index of the column that the results were sorted on.  -1 for unknown, index numbers
051     * starting at 0
052     */
053    private int previouslySortedColumnIndex;
054
055    /**
056     * Comment for <code>columnToSortIndex</code>
057     */
058    private int columnToSortIndex;
059
060    /**
061     * If it is not feasible to use an index for lookup, as with mapped properties in an Map<String, String>, it may be necessary to store a string value
062     */
063    private String columnToSortName;
064
065    /**
066     * When the screen was last rendered, the column name on which it was previously sorted -- this is important for toggling between ascending and descending
067     * sort orders
068     */
069    private String previouslySortedColumnName;
070
071    private boolean sortDescending;
072
073    public KualiTableRenderFormMetadata() {
074        sortDescending = false;
075    }
076
077    /**
078     * Gets the columnToSortIndex attribute.
079     * @return Returns the columnToSortIndex.
080     */
081    public int getColumnToSortIndex() {
082        return columnToSortIndex;
083    }
084
085    /**
086     * Sets the columnToSortIndex attribute value.
087     * @param columnToSortIndex The columnToSortIndex to set.
088     */
089    public void setColumnToSortIndex(int columnToSortIndex) {
090        this.columnToSortIndex = columnToSortIndex;
091    }
092
093    /**
094     * Gets the previouslySortedColumnIndex attribute.
095     * @return Returns the previouslySortedColumnIndex.
096     */
097    public int getPreviouslySortedColumnIndex() {
098        return previouslySortedColumnIndex;
099    }
100
101    /**
102     * Sets the previouslySortedColumnIndex attribute value.
103     * @param previouslySortedColumnIndex The previouslySortedColumnIndex to set.
104     */
105    public void setPreviouslySortedColumnIndex(int previouslySortedColumnIndex) {
106        this.previouslySortedColumnIndex = previouslySortedColumnIndex;
107    }
108
109    /**
110     * Gets the resultsActualSize attribute.
111     * @return Returns the resultsActualSize.
112     */
113    public int getResultsActualSize() {
114        return resultsActualSize;
115    }
116
117    /**
118     * Sets the resultsActualSize attribute value.
119     * @param resultsActualSize The resultsActualSize to set.
120     */
121    public void setResultsActualSize(int resultsActualSize) {
122        this.resultsActualSize = resultsActualSize;
123    }
124
125    /**
126     * Gets the resultsLimitedSize attribute.
127     * @return Returns the resultsLimitedSize.
128     */
129    public int getResultsLimitedSize() {
130        return resultsLimitedSize;
131    }
132
133    /**
134     * Sets the resultsLimitedSize attribute value.
135     * @param resultsLimitedSize The resultsLimitedSize to set.
136     */
137    public void setResultsLimitedSize(int resultsLimitedSize) {
138        this.resultsLimitedSize = resultsLimitedSize;
139    }
140
141    /**
142     * Gets the switchToPageNumber attribute.
143     * @return Returns the switchToPageNumber.
144     */
145    public int getSwitchToPageNumber() {
146        return switchToPageNumber;
147    }
148
149    /**
150     * Sets the switchToPageNumber attribute value.
151     * @param switchToPageNumber The switchToPageNumber to set.
152     */
153    public void setSwitchToPageNumber(int switchToPageNumber) {
154        this.switchToPageNumber = switchToPageNumber;
155    }
156
157    /**
158     * Gets the viewedPageNumber attribute.
159     * @return Returns the viewedPageNumber.
160     */
161    public int getViewedPageNumber() {
162        return viewedPageNumber;
163    }
164
165    /**
166     * Sets the viewedPageNumber attribute value.
167     * @param viewedPageNumber The viewedPageNumber to set.
168     */
169    public void setViewedPageNumber(int viewedPageNumber) {
170        this.viewedPageNumber = viewedPageNumber;
171    }
172
173    /**
174     * Gets the totalNumberOfPages attribute.
175     * @return Returns the totalNumberOfPages.
176     */
177    public int getTotalNumberOfPages() {
178        return totalNumberOfPages;
179    }
180
181    /**
182     * Sets the totalNumberOfPages attribute value.
183     * @param totalNumberOfPages The totalNumberOfPages to set.
184     */
185    public void setTotalNumberOfPages(int totalNumberOfPages) {
186        this.totalNumberOfPages = totalNumberOfPages;
187    }
188
189    /**
190     * Gets the firstRowIndex attribute.
191     * @return Returns the firstRowIndex.
192     */
193    public int getFirstRowIndex() {
194        return firstRowIndex;
195    }
196
197    /**
198     * Sets the firstRowIndex attribute value.
199     * @param firstRowIndex The firstRowIndex to set.
200     */
201    public void setFirstRowIndex(int firstRowIndex) {
202        this.firstRowIndex = firstRowIndex;
203    }
204
205    /**
206     * Gets the lastRowIndex attribute.
207     * @return Returns the lastRowIndex.
208     */
209    public int getLastRowIndex() {
210        return lastRowIndex;
211    }
212
213    /**
214     * Sets the lastRowIndex attribute value.
215     * @param lastRowIndex The lastRowIndex to set.
216     */
217    public void setLastRowIndex(int lastRowIndex) {
218        this.lastRowIndex = lastRowIndex;
219    }
220
221    /**
222     * Gets the sortDescending attribute.
223     * @return Returns the sortDescending.
224     */
225    public boolean isSortDescending() {
226        return sortDescending;
227    }
228
229    /**
230     * Sets the sortDescending attribute value.
231     * @param sortDescending The sortDescending to set.
232     */
233    public void setSortDescending(boolean sortDescending) {
234        this.sortDescending = sortDescending;
235    }
236
237        /**
238         * @return the columnToSortName
239         */
240        public String getColumnToSortName() {
241                return this.columnToSortName;
242        }
243
244        /**
245         * @param columnToSortName the columnToSortName to set
246         */
247        public void setColumnToSortName(String columnToSortName) {
248                this.columnToSortName = columnToSortName;
249        }
250
251        /**
252         * @return the previouslySortedColumnName
253         */
254        public String getPreviouslySortedColumnName() {
255                return this.previouslySortedColumnName;
256        }
257
258        /**
259         * @param previouslySortedColumnName the previouslySortedColumnName to set
260         */
261        public void setPreviouslySortedColumnName(String previouslySortedColumnName) {
262                this.previouslySortedColumnName = previouslySortedColumnName;
263        }
264
265
266    /**
267     * Sets the paging form parameters to go to the first page of the list
268     *
269     * @param listSize size of table being rendered
270     * @param maxRowsPerPage
271     */
272    public void jumpToFirstPage(int listSize, int maxRowsPerPage) {
273        jumpToPage(0, listSize, maxRowsPerPage);
274    }
275
276    /**
277     * Sets the paging form parameters to go to the last page of the list
278     *
279     * @param listSize size of table being rendered
280     * @param maxRowsPerPage
281     */
282    public void jumpToLastPage(int listSize, int maxRowsPerPage) {
283        jumpToPage(TableRenderUtil.computeTotalNumberOfPages(listSize, maxRowsPerPage) - 1, listSize, maxRowsPerPage);
284    }
285
286    /**
287     * Sets the paging form parameters to go to the specified page of the list
288     *
289     * @param pageNumber first page is 0, must be non-negative.  If the list is not large enough to have the page specified, then
290     *   this method will be equivalent to calling jumpToLastPage.
291     * @param listSize size of table being rendered
292     * @param maxRowsPerPage
293     *
294     * @see KualiTableRenderFormMetadata#jumpToLastPage(int, int)
295     */
296    public void jumpToPage(int pageNumber, int listSize, int maxRowsPerPage) {
297        int totalPages = TableRenderUtil.computeTotalNumberOfPages(listSize, maxRowsPerPage);
298        setTotalNumberOfPages(totalPages);
299        if (pageNumber >= totalPages) {
300            pageNumber = totalPages - 1;
301        }
302        setViewedPageNumber(pageNumber);
303        setFirstRowIndex(TableRenderUtil.computeStartIndexForPage(pageNumber, listSize, maxRowsPerPage));
304        setLastRowIndex(TableRenderUtil.computeLastIndexForPage(pageNumber, listSize, maxRowsPerPage));
305    }
306
307    /**
308     * Sorts a list on the form according to the form metadata (sortColumName, previouslySortedColumnName)
309     *
310     * @param memberTableMetadata
311     * @param items
312     * @param maxRowsPerPage
313     * @throws org.kuali.rice.kew.api.exception.WorkflowException
314     */
315    public void sort(List<?> items, int maxRowsPerPage) {
316
317        // Don't bother to sort null, empty or singleton lists
318        if (items == null || items.size() <= 1)
319                return;
320
321        String columnToSortOn = getColumnToSortName();
322
323        // Don't bother to sort if no column to sort on is provided
324        if (StringUtils.isEmpty(columnToSortOn))
325                return;
326
327        String previouslySortedColumnName = getPreviouslySortedColumnName();
328
329        // We know members isn't null or empty from the check above
330        Object firstItem = items.get(0);
331        // Need to decide if the comparator is for a bean property or a mapped key on the qualififer attribute set
332        Comparator comparator = null;
333        Comparator subComparator = new Comparator<Object>() {
334
335                public int compare(Object o1, Object o2) {
336                        if (o1 == null)
337                                return -1;
338                        if (o2 == null)
339                                return 1;
340
341                        if (o1 instanceof java.util.Date && o2 instanceof java.util.Date) {
342                                Date d1 = (Date)o1;
343                                Date d2 = (Date)o2;
344                                return d1.compareTo(d2);
345                        }
346
347                        String s1 = o1.toString();
348                        String s2 = o2.toString();
349                        int n1=s1.length(), n2=s2.length();
350                        for (int i1=0, i2=0; i1<n1 && i2<n2; i1++, i2++) {
351                                char c1 = s1.charAt(i1);
352                                char c2 = s2.charAt(i2);
353                                if (c1 != c2) {
354                                        c1 = Character.toUpperCase(c1);
355                                        c2 = Character.toUpperCase(c2);
356                                        if (c1 != c2) {
357                                                c1 = Character.toLowerCase(c1);
358                                                c2 = Character.toLowerCase(c2);
359                                                if (c1 != c2) {
360                                                        return c1 - c2;
361                                                }
362                                        }
363                                }
364                        }
365                        return n1 - n2;
366                }
367        };
368        // If the columnName is a readable bean property on the first member, then it's safe to say we need a simple bean property comparator,
369        // otherwise it's a mapped property -- syntax for BeanComparator is "name" and "name(key)", respectively
370        if (PropertyUtils.isReadable(firstItem, columnToSortOn))
371                comparator = new BeanComparator(columnToSortOn, subComparator);
372        else
373                comparator = new BeanComparator(new StringBuilder().append("qualifierAsMap(").append(columnToSortOn).append(")").toString(), subComparator);
374
375
376        // If the user has decided to resort by the same column that the list is currently sorted by, then assume that s/he wants to reverse the order of the sort
377        if (!StringUtils.isEmpty(columnToSortOn) && !StringUtils.isEmpty(previouslySortedColumnName) && columnToSortOn.equals(previouslySortedColumnName)) {
378            // we're already sorted on the same column that the user clicked on, so we reverse the list
379            if (isSortDescending())
380                comparator = Collections.reverseOrder(comparator);
381
382            setSortDescending(!isSortDescending());
383        } else {
384                // Track which column we're currently sorting, so that the above logic will work on the next sort
385                setPreviouslySortedColumnName(columnToSortOn);
386                setSortDescending(true);
387        }
388
389        //if the user is just going between pages no need to sort
390        if (getSwitchToPageNumber() == getViewedPageNumber()) {
391            Collections.sort(items, comparator);
392        }
393
394                jumpToFirstPage(items.size(), maxRowsPerPage);
395    }
396
397}