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}