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}