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}