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