001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache 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 * https://www.apache.org/licenses/LICENSE-2.0 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 */ 016 017package org.springframework.beans.support; 018 019import java.io.Serializable; 020import java.util.ArrayList; 021import java.util.Date; 022import java.util.List; 023 024import org.springframework.util.Assert; 025 026/** 027 * PagedListHolder is a simple state holder for handling lists of objects, 028 * separating them into pages. Page numbering starts with 0. 029 * 030 * <p>This is mainly targeted at usage in web UIs. Typically, an instance will be 031 * instantiated with a list of beans, put into the session, and exported as model. 032 * The properties can all be set/get programmatically, but the most common way will 033 * be data binding, i.e. populating the bean from request parameters. The getters 034 * will mainly be used by the view. 035 * 036 * <p>Supports sorting the underlying list via a {@link SortDefinition} implementation, 037 * available as property "sort". By default, a {@link MutableSortDefinition} instance 038 * will be used, toggling the ascending value on setting the same property again. 039 * 040 * <p>The data binding names have to be called "pageSize" and "sort.ascending", 041 * as expected by BeanWrapper. Note that the names and the nesting syntax match 042 * the respective JSTL EL expressions, like "myModelAttr.pageSize" and 043 * "myModelAttr.sort.ascending". 044 * 045 * @author Juergen Hoeller 046 * @since 19.05.2003 047 * @see #getPageList() 048 * @see org.springframework.beans.support.MutableSortDefinition 049 */ 050@SuppressWarnings("serial") 051public class PagedListHolder<E> implements Serializable { 052 053 /** 054 * The default page size. 055 */ 056 public static final int DEFAULT_PAGE_SIZE = 10; 057 058 /** 059 * The default maximum number of page links. 060 */ 061 public static final int DEFAULT_MAX_LINKED_PAGES = 10; 062 063 064 private List<E> source; 065 066 private Date refreshDate; 067 068 private SortDefinition sort; 069 070 private SortDefinition sortUsed; 071 072 private int pageSize = DEFAULT_PAGE_SIZE; 073 074 private int page = 0; 075 076 private boolean newPageSet; 077 078 private int maxLinkedPages = DEFAULT_MAX_LINKED_PAGES; 079 080 081 /** 082 * Create a new holder instance. 083 * You'll need to set a source list to be able to use the holder. 084 * @see #setSource 085 */ 086 public PagedListHolder() { 087 this(new ArrayList<E>(0)); 088 } 089 090 /** 091 * Create a new holder instance with the given source list, starting with 092 * a default sort definition (with "toggleAscendingOnProperty" activated). 093 * @param source the source List 094 * @see MutableSortDefinition#setToggleAscendingOnProperty 095 */ 096 public PagedListHolder(List<E> source) { 097 this(source, new MutableSortDefinition(true)); 098 } 099 100 /** 101 * Create a new holder instance with the given source list. 102 * @param source the source List 103 * @param sort the SortDefinition to start with 104 */ 105 public PagedListHolder(List<E> source, SortDefinition sort) { 106 setSource(source); 107 setSort(sort); 108 } 109 110 111 /** 112 * Set the source list for this holder. 113 */ 114 public void setSource(List<E> source) { 115 Assert.notNull(source, "Source List must not be null"); 116 this.source = source; 117 this.refreshDate = new Date(); 118 this.sortUsed = null; 119 } 120 121 /** 122 * Return the source list for this holder. 123 */ 124 public List<E> getSource() { 125 return this.source; 126 } 127 128 /** 129 * Return the last time the list has been fetched from the source provider. 130 */ 131 public Date getRefreshDate() { 132 return this.refreshDate; 133 } 134 135 /** 136 * Set the sort definition for this holder. 137 * Typically an instance of MutableSortDefinition. 138 * @see org.springframework.beans.support.MutableSortDefinition 139 */ 140 public void setSort(SortDefinition sort) { 141 this.sort = sort; 142 } 143 144 /** 145 * Return the sort definition for this holder. 146 */ 147 public SortDefinition getSort() { 148 return this.sort; 149 } 150 151 /** 152 * Set the current page size. 153 * Resets the current page number if changed. 154 * <p>Default value is 10. 155 */ 156 public void setPageSize(int pageSize) { 157 if (pageSize != this.pageSize) { 158 this.pageSize = pageSize; 159 if (!this.newPageSet) { 160 this.page = 0; 161 } 162 } 163 } 164 165 /** 166 * Return the current page size. 167 */ 168 public int getPageSize() { 169 return this.pageSize; 170 } 171 172 /** 173 * Set the current page number. 174 * Page numbering starts with 0. 175 */ 176 public void setPage(int page) { 177 this.page = page; 178 this.newPageSet = true; 179 } 180 181 /** 182 * Return the current page number. 183 * Page numbering starts with 0. 184 */ 185 public int getPage() { 186 this.newPageSet = false; 187 if (this.page >= getPageCount()) { 188 this.page = getPageCount() - 1; 189 } 190 return this.page; 191 } 192 193 /** 194 * Set the maximum number of page links to a few pages around the current one. 195 */ 196 public void setMaxLinkedPages(int maxLinkedPages) { 197 this.maxLinkedPages = maxLinkedPages; 198 } 199 200 /** 201 * Return the maximum number of page links to a few pages around the current one. 202 */ 203 public int getMaxLinkedPages() { 204 return this.maxLinkedPages; 205 } 206 207 208 /** 209 * Return the number of pages for the current source list. 210 */ 211 public int getPageCount() { 212 float nrOfPages = (float) getNrOfElements() / getPageSize(); 213 return (int) ((nrOfPages > (int) nrOfPages || nrOfPages == 0.0) ? nrOfPages + 1 : nrOfPages); 214 } 215 216 /** 217 * Return if the current page is the first one. 218 */ 219 public boolean isFirstPage() { 220 return getPage() == 0; 221 } 222 223 /** 224 * Return if the current page is the last one. 225 */ 226 public boolean isLastPage() { 227 return getPage() == getPageCount() -1; 228 } 229 230 /** 231 * Switch to previous page. 232 * Will stay on first page if already on first page. 233 */ 234 public void previousPage() { 235 if (!isFirstPage()) { 236 this.page--; 237 } 238 } 239 240 /** 241 * Switch to next page. 242 * Will stay on last page if already on last page. 243 */ 244 public void nextPage() { 245 if (!isLastPage()) { 246 this.page++; 247 } 248 } 249 250 /** 251 * Return the total number of elements in the source list. 252 */ 253 public int getNrOfElements() { 254 return getSource().size(); 255 } 256 257 /** 258 * Return the element index of the first element on the current page. 259 * Element numbering starts with 0. 260 */ 261 public int getFirstElementOnPage() { 262 return (getPageSize() * getPage()); 263 } 264 265 /** 266 * Return the element index of the last element on the current page. 267 * Element numbering starts with 0. 268 */ 269 public int getLastElementOnPage() { 270 int endIndex = getPageSize() * (getPage() + 1); 271 int size = getNrOfElements(); 272 return (endIndex > size ? size : endIndex) - 1; 273 } 274 275 /** 276 * Return a sub-list representing the current page. 277 */ 278 public List<E> getPageList() { 279 return getSource().subList(getFirstElementOnPage(), getLastElementOnPage() + 1); 280 } 281 282 /** 283 * Return the first page to which create a link around the current page. 284 */ 285 public int getFirstLinkedPage() { 286 return Math.max(0, getPage() - (getMaxLinkedPages() / 2)); 287 } 288 289 /** 290 * Return the last page to which create a link around the current page. 291 */ 292 public int getLastLinkedPage() { 293 return Math.min(getFirstLinkedPage() + getMaxLinkedPages() - 1, getPageCount() - 1); 294 } 295 296 297 /** 298 * Resort the list if necessary, i.e. if the current {@code sort} instance 299 * isn't equal to the backed-up {@code sortUsed} instance. 300 * <p>Calls {@code doSort} to trigger actual sorting. 301 * @see #doSort 302 */ 303 public void resort() { 304 SortDefinition sort = getSort(); 305 if (sort != null && !sort.equals(this.sortUsed)) { 306 this.sortUsed = copySortDefinition(sort); 307 doSort(getSource(), sort); 308 setPage(0); 309 } 310 } 311 312 /** 313 * Create a deep copy of the given sort definition, 314 * for use as state holder to compare a modified sort definition against. 315 * <p>Default implementation creates a MutableSortDefinition instance. 316 * Can be overridden in subclasses, in particular in case of custom 317 * extensions to the SortDefinition interface. Is allowed to return 318 * null, which means that no sort state will be held, triggering 319 * actual sorting for each {@code resort} call. 320 * @param sort the current SortDefinition object 321 * @return a deep copy of the SortDefinition object 322 * @see MutableSortDefinition#MutableSortDefinition(SortDefinition) 323 */ 324 protected SortDefinition copySortDefinition(SortDefinition sort) { 325 return new MutableSortDefinition(sort); 326 } 327 328 /** 329 * Actually perform sorting of the given source list, according to 330 * the given sort definition. 331 * <p>The default implementation uses Spring's PropertyComparator. 332 * Can be overridden in subclasses. 333 * @see PropertyComparator#sort(java.util.List, SortDefinition) 334 */ 335 protected void doSort(List<E> source, SortDefinition sort) { 336 PropertyComparator.sort(source, sort); 337 } 338 339}