1 /*
2  * Copyright (c) 2018 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you
5  * may not use this file except in compliance with the License. You may
6  * obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13  * implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 
17 package com.android.vts.util;
18 
19 import com.google.cloud.datastore.Cursor;
20 import com.google.cloud.datastore.QueryResults;
21 import com.googlecode.objectify.cmd.Query;
22 
23 import java.util.ArrayList;
24 import java.util.Iterator;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.stream.Collectors;
29 
30 /** Helper class for pagination. */
31 public class Pagination<T> implements Iterable<T> {
32 
33     /** the default page size */
34     public static final int DEFAULT_PAGE_SIZE = 10;
35     /** the default page window size */
36     private static final int DEFAULT_PAGE_WINDOW = 10;
37 
38     /** the current page */
39     private int page;
40 
41     /** the page window size */
42     private int pageSize = DEFAULT_PAGE_SIZE;
43 
44     /** the total number of found entities */
45     private int totalCount;
46 
47     /** the previous cursor string token where to start */
48     private String previousPageCountToken = "";
49 
50     /** the previous cursor string token where to start */
51     private String currentPageCountToken = "";
52 
53     /** the next cursor string token where to start */
54     private String nextPageCountToken = "";
55 
56     /** the previous cursor string token list where to start */
57     private LinkedHashSet<String> pageCountTokenSet;
58 
59     /** the maximum number of pages */
60     private int maxPages;
61 
62     /** the list of object on the page */
63     private List<T> list = new ArrayList<>();
64 
Pagination(List<T> list, int page, int pageSize, int totalCount)65     public Pagination(List<T> list, int page, int pageSize, int totalCount) {
66         this.list = list;
67         this.page = page;
68         this.pageSize = pageSize;
69         this.totalCount = totalCount;
70     }
71 
Pagination( Query<T> query, int page, int pageSize, String startPageToken, LinkedHashSet<String> pageCountTokenSet)72     public Pagination(
73             Query<T> query,
74             int page,
75             int pageSize,
76             String startPageToken,
77             LinkedHashSet<String> pageCountTokenSet) {
78         this.page = page;
79         this.pageSize = pageSize;
80         this.pageCountTokenSet = pageCountTokenSet;
81         this.currentPageCountToken = startPageToken;
82 
83         List<String> pageCountTokenList =
84                 this.pageCountTokenSet.stream().collect(Collectors.toList());
85         if (pageCountTokenList.size() > 0) {
86             int foundIndex = pageCountTokenList.indexOf(startPageToken);
87             if (foundIndex <= 0) {
88                 this.previousPageCountToken = "";
89             } else {
90                 this.previousPageCountToken = pageCountTokenList.get(foundIndex - 1);
91             }
92         }
93 
94         int limitValue = pageSize * DEFAULT_PAGE_WINDOW + pageSize / 2;
95         query = query.limit(limitValue);
96         if (startPageToken.equals("")) {
97             this.totalCount = query.count();
98         } else {
99             query = query.startAt(Cursor.fromUrlSafe(startPageToken));
100             this.totalCount = query.count();
101         }
102 
103         this.maxPages =
104                 this.totalCount / this.pageSize + (this.totalCount % this.pageSize == 0 ? 0 : 1);
105 
106         int iteratorIndex = 0;
107         int startIndex = (page % pageSize == 0 ? 9 : page % pageSize - 1) * pageSize;
108 
109         QueryResults<T> resultIterator = query.iterator();
110         while (resultIterator.hasNext()) {
111             if (startIndex <= iteratorIndex && iteratorIndex < startIndex + this.pageSize)
112                 this.list.add(resultIterator.next());
113             else resultIterator.next();
114             if (iteratorIndex == DEFAULT_PAGE_WINDOW * this.pageSize) {
115                 if ( Objects.nonNull(resultIterator) && Objects.nonNull(resultIterator.getCursorAfter()) ) {
116                     this.nextPageCountToken = resultIterator.getCursorAfter().toUrlSafe();
117                 }
118             }
119             iteratorIndex++;
120         }
121     }
122 
iterator()123     public Iterator<T> iterator() {
124         return list.iterator();
125     }
126 
127     /**
128      * Gets the total number of objects.
129      *
130      * @return the total number of objects as an int
131      */
getTotalCount()132     public int getTotalCount() {
133         return totalCount;
134     }
135 
136     /**
137      * Gets the number of page window.
138      *
139      * @return the number of page window as an int
140      */
getPageSize()141     public int getPageSize() {
142         return pageSize;
143     }
144 
145     /**
146      * Gets the maximum number of pages.
147      *
148      * @return the maximum number of pages as an int
149      */
getMaxPages()150     public int getMaxPages() {
151         return this.maxPages;
152     }
153 
154     /**
155      * Gets the page.
156      *
157      * @return the page as an int
158      */
getPage()159     public int getPage() {
160         return page;
161     }
162 
163     /**
164      * Gets the minimum page in the window.
165      *
166      * @return the page number
167      */
getMinPageRange()168     public int getMinPageRange() {
169         if (this.getPage() <= this.getPageSize()) {
170             return 1;
171         } else {
172             if ((this.getPage() % this.getPageSize()) == 0) {
173                 return (this.getPage() / this.getPageSize() - 1) * this.getPageSize() + 1;
174             } else {
175                 return this.getPage() / this.getPageSize() * this.getPageSize() + 1;
176             }
177         }
178     }
179 
180     /**
181      * Gets the maximum page in the window.
182      *
183      * @return the page number
184      */
getMaxPageRange()185     public int getMaxPageRange() {
186         if (this.getMaxPages() > this.getPageSize()) {
187             return getMinPageRange() + this.getPageSize() - 1;
188         } else {
189             return this.getMinPageRange() + this.getMaxPages() - 1;
190         }
191     }
192 
193     /**
194      * Gets the subset of the list for the current page.
195      *
196      * @return a List
197      */
getList()198     public List<T> getList() {
199         return this.list;
200     }
201 
202     /**
203      * Gets the cursor token for the previous page starting point.
204      *
205      * @return a string of cursor of previous starting point
206      */
getPreviousPageCountToken()207     public String getPreviousPageCountToken() {
208         return this.previousPageCountToken;
209     }
210 
211     /**
212      * Gets the cursor token for the current page starting point.
213      *
214      * @return a string of cursor of current starting point
215      */
getCurrentPageCountToken()216     public String getCurrentPageCountToken() {
217         return this.currentPageCountToken;
218     }
219 
220     /**
221      * Gets the cursor token for the next page starting point.
222      *
223      * @return a string of cursor of next starting point
224      */
getNextPageCountToken()225     public String getNextPageCountToken() {
226         return this.nextPageCountToken;
227     }
228 }
229