View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may 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,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.util;
17  
18  import java.util.ArrayList;
19  import java.util.Collection;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.ListIterator;
23  import java.util.Map;
24  
25  /**
26   * A response list that extends List functionality and includes pagination and search metadata.
27   * This class wraps search results with pagination information, facet responses, and query statistics.
28   * It implements the List interface to provide standard list operations while adding search-specific
29   * functionality such as page navigation, record counts, and query execution times.
30   */
31  public class QueryResponseList implements List<Map<String, Object>> {
32  
33      /** The underlying list that contains the actual search result documents. */
34      protected final List<Map<String, Object>> parent;
35  
36      /** The starting position of the current page in the overall result set. */
37      protected final int start;
38  
39      /** The offset value used for pagination calculations. */
40      protected final int offset;
41  
42      /** The number of records per page. */
43      protected final int pageSize;
44  
45      /** The current page number (1-based). */
46      protected int currentPageNumber;
47  
48      /** The total number of records in the search result set. */
49      protected long allRecordCount;
50  
51      /** The relation type for the total record count (e.g., "eq", "gte"). */
52      protected String allRecordCountRelation;
53  
54      /** The total number of pages based on the page size and total record count. */
55      protected int allPageCount;
56  
57      /** Flag indicating whether there is a next page available. */
58      protected boolean existNextPage;
59  
60      /** Flag indicating whether there is a previous page available. */
61      protected boolean existPrevPage;
62  
63      /** The record number of the first record on the current page (1-based). */
64      protected long currentStartRecordNumber;
65  
66      /** The record number of the last record on the current page (1-based). */
67      protected long currentEndRecordNumber;
68  
69      /** A list of page numbers for pagination display (typically a range around the current page). */
70      protected List<String> pageNumberList;
71  
72      /** The search query string that was used to generate these results. */
73      protected String searchQuery;
74  
75      /** The total execution time for the search request in milliseconds. */
76      protected long execTime;
77  
78      /** The facet response containing aggregated search facets and their counts. */
79      protected FacetResponse facetResponse;
80  
81      /** Flag indicating whether the search results are partial (not complete). */
82      protected boolean partialResults = false;
83  
84      /** The time taken to execute the search query in milliseconds. */
85      protected long queryTime;
86  
87      /**
88       * Constructor for testing purposes.
89       * Creates a QueryResponseList with minimal pagination information.
90       *
91       * @param documentList the list of documents to wrap
92       * @param start the starting position of the current page
93       * @param pageSize the number of records per page
94       * @param offset the offset value for pagination
95       */
96      protected QueryResponseList(final List<Map<String, Object>> documentList, final int start, final int pageSize, final int offset) {
97          parent = documentList;
98          this.offset = offset;
99          this.start = start;
100         this.pageSize = pageSize;
101     }
102 
103     /**
104      * Main constructor that creates a QueryResponseList with complete search metadata.
105      *
106      * @param documentList the list of documents returned by the search
107      * @param allRecordCount the total number of records in the search result set
108      * @param allRecordCountRelation the relation type for the total record count
109      * @param queryTime the time taken to execute the search query in milliseconds
110      * @param partialResults flag indicating whether the results are partial
111      * @param facetResponse the facet response containing aggregated search facets
112      * @param start the starting position of the current page
113      * @param pageSize the number of records per page
114      * @param offset the offset value for pagination
115      */
116     public QueryResponseList(final List<Map<String, Object>> documentList, final long allRecordCount, final String allRecordCountRelation,
117             final long queryTime, final boolean partialResults, final FacetResponse facetResponse, final int start, final int pageSize,
118             final int offset) {
119         this(documentList, start, pageSize, offset);
120         this.allRecordCount = allRecordCount;
121         this.allRecordCountRelation = allRecordCountRelation;
122         this.queryTime = queryTime;
123         this.partialResults = partialResults;
124         this.facetResponse = facetResponse;
125         if (pageSize > 0) {
126             calculatePageInfo();
127         }
128     }
129 
130     /**
131      * Calculates pagination information based on the current parameters.
132      * This method computes page counts, navigation flags, record numbers, and page number lists.
133      */
134     protected void calculatePageInfo() {
135         int startWithOffset = start - offset;
136         if (startWithOffset < 0) {
137             startWithOffset = 0;
138         }
139         allPageCount = (int) ((allRecordCount - 1) / pageSize) + 1;
140         existPrevPage = startWithOffset > 0;
141         existNextPage = startWithOffset < (long) (allPageCount - 1) * (long) pageSize;
142         currentPageNumber = start / pageSize + 1;
143         if (existNextPage && size() < pageSize) {
144             // collapsing
145             existNextPage = false;
146             allPageCount = currentPageNumber;
147         }
148         currentStartRecordNumber = allRecordCount != 0 ? start + 1 : 0;
149         currentEndRecordNumber = currentStartRecordNumber + pageSize - 1;
150         currentEndRecordNumber = allRecordCount < currentEndRecordNumber ? allRecordCount : currentEndRecordNumber;
151 
152         final int pageRangeSize = 5;
153         int startPageRangeSize = currentPageNumber - pageRangeSize;
154         if (startPageRangeSize < 1) {
155             startPageRangeSize = 1;
156         }
157         int endPageRangeSize = currentPageNumber + pageRangeSize;
158         if (endPageRangeSize > allPageCount) {
159             endPageRangeSize = allPageCount;
160         }
161         pageNumberList = new ArrayList<>();
162         for (int i = startPageRangeSize; i <= endPageRangeSize; i++) {
163             pageNumberList.add(String.valueOf(i));
164         }
165     }
166 
167     @Override
168     public boolean add(final Map<String, Object> e) {
169         return parent.add(e);
170     }
171 
172     @Override
173     public void add(final int index, final Map<String, Object> element) {
174         parent.add(index, element);
175     }
176 
177     @Override
178     public boolean addAll(final Collection<? extends Map<String, Object>> c) {
179         return parent.addAll(c);
180     }
181 
182     @Override
183     public boolean addAll(final int index, final Collection<? extends Map<String, Object>> c) {
184         return parent.addAll(index, c);
185     }
186 
187     @Override
188     public void clear() {
189         parent.clear();
190     }
191 
192     @Override
193     public boolean contains(final Object o) {
194         return parent.contains(o);
195     }
196 
197     @Override
198     public boolean containsAll(final Collection<?> c) {
199         return parent.containsAll(c);
200     }
201 
202     @Override
203     public boolean equals(final Object o) {
204         return parent.equals(o);
205     }
206 
207     @Override
208     public Map<String, Object> get(final int index) {
209         return parent.get(index);
210     }
211 
212     @Override
213     public int hashCode() {
214         return parent.hashCode();
215     }
216 
217     @Override
218     public int indexOf(final Object o) {
219         return parent.indexOf(o);
220     }
221 
222     @Override
223     public boolean isEmpty() {
224         return parent.isEmpty();
225     }
226 
227     @Override
228     public Iterator<Map<String, Object>> iterator() {
229         return parent.iterator();
230     }
231 
232     @Override
233     public int lastIndexOf(final Object o) {
234         return parent.lastIndexOf(o);
235     }
236 
237     @Override
238     public ListIterator<Map<String, Object>> listIterator() {
239         return parent.listIterator();
240     }
241 
242     @Override
243     public ListIterator<Map<String, Object>> listIterator(final int index) {
244         return parent.listIterator(index);
245     }
246 
247     @Override
248     public Map<String, Object> remove(final int index) {
249         return parent.remove(index);
250     }
251 
252     @Override
253     public boolean remove(final Object o) {
254         return parent.remove(o);
255     }
256 
257     @Override
258     public boolean removeAll(final Collection<?> c) {
259         return parent.removeAll(c);
260     }
261 
262     @Override
263     public boolean retainAll(final Collection<?> c) {
264         return parent.retainAll(c);
265     }
266 
267     @Override
268     public Map<String, Object> set(final int index, final Map<String, Object> element) {
269         return parent.set(index, element);
270     }
271 
272     @Override
273     public int size() {
274         return parent.size();
275     }
276 
277     @Override
278     public List<Map<String, Object>> subList(final int fromIndex, final int toIndex) {
279         return parent.subList(fromIndex, toIndex);
280     }
281 
282     @Override
283     public Object[] toArray() {
284         return parent.toArray();
285     }
286 
287     @Override
288     public <T> T[] toArray(final T[] a) {
289         return parent.toArray(a);
290     }
291 
292     /**
293      * Gets the starting position of the current page in the overall result set.
294      *
295      * @return the start position (0-based)
296      */
297     public int getStart() {
298         return start;
299     }
300 
301     /**
302      * Gets the offset value used for pagination calculations.
303      *
304      * @return the offset value
305      */
306     public int getOffset() {
307         return offset;
308     }
309 
310     /**
311      * Gets the number of records per page.
312      *
313      * @return the page size
314      */
315     public int getPageSize() {
316         return pageSize;
317     }
318 
319     /**
320      * Gets the current page number (1-based).
321      *
322      * @return the current page number
323      */
324     public int getCurrentPageNumber() {
325         return currentPageNumber;
326     }
327 
328     /**
329      * Gets the total number of records in the search result set.
330      *
331      * @return the total record count
332      */
333     public long getAllRecordCount() {
334         return allRecordCount;
335     }
336 
337     /**
338      * Gets the relation type for the total record count.
339      *
340      * @return the relation type (e.g., "eq" for exact count, "gte" for greater than or equal)
341      */
342     public String getAllRecordCountRelation() {
343         return allRecordCountRelation;
344     }
345 
346     /**
347      * Gets the total number of pages based on the page size and total record count.
348      *
349      * @return the total page count
350      */
351     public int getAllPageCount() {
352         return allPageCount;
353     }
354 
355     /**
356      * Checks whether there is a next page available.
357      *
358      * @return true if a next page exists, false otherwise
359      */
360     public boolean isExistNextPage() {
361         return existNextPage;
362     }
363 
364     /**
365      * Checks whether there is a previous page available.
366      *
367      * @return true if a previous page exists, false otherwise
368      */
369     public boolean isExistPrevPage() {
370         return existPrevPage;
371     }
372 
373     /**
374      * Gets the record number of the first record on the current page (1-based).
375      *
376      * @return the starting record number of the current page
377      */
378     public long getCurrentStartRecordNumber() {
379         return currentStartRecordNumber;
380     }
381 
382     /**
383      * Gets the record number of the last record on the current page (1-based).
384      *
385      * @return the ending record number of the current page
386      */
387     public long getCurrentEndRecordNumber() {
388         return currentEndRecordNumber;
389     }
390 
391     /**
392      * Gets a list of page numbers for pagination display.
393      * Typically returns a range of page numbers around the current page.
394      *
395      * @return a list of page numbers as strings
396      */
397     public List<String> getPageNumberList() {
398         return pageNumberList;
399     }
400 
401     /**
402      * Gets the search query string that was used to generate these results.
403      *
404      * @return the search query string
405      */
406     public String getSearchQuery() {
407         return searchQuery;
408     }
409 
410     /**
411      * Sets the search query string that was used to generate these results.
412      *
413      * @param searchQuery the search query string
414      */
415     public void setSearchQuery(final String searchQuery) {
416         this.searchQuery = searchQuery;
417     }
418 
419     /**
420      * Gets the total execution time for the search request in milliseconds.
421      *
422      * @return the execution time in milliseconds
423      */
424     public long getExecTime() {
425         return execTime;
426     }
427 
428     /**
429      * Sets the total execution time for the search request in milliseconds.
430      *
431      * @param execTime the execution time in milliseconds
432      */
433     public void setExecTime(final long execTime) {
434         this.execTime = execTime;
435     }
436 
437     /**
438      * Gets the facet response containing aggregated search facets and their counts.
439      *
440      * @return the facet response, or null if no facets were requested
441      */
442     public FacetResponse getFacetResponse() {
443         return facetResponse;
444     }
445 
446     /**
447      * Checks whether the search results are partial (not complete).
448      *
449      * @return true if the results are partial, false if complete
450      */
451     public boolean isPartialResults() {
452         return partialResults;
453     }
454 
455     /**
456      * Gets the time taken to execute the search query in milliseconds.
457      *
458      * @return the query execution time in milliseconds
459      */
460     public long getQueryTime() {
461         return queryTime;
462     }
463 
464     @Override
465     public String toString() {
466         return "QueryResponseList [parent=" + parent + ", start=" + start + ", offset=" + offset + ", pageSize=" + pageSize
467                 + ", currentPageNumber=" + currentPageNumber + ", allRecordCount=" + allRecordCount + ", allRecordCountRelation="
468                 + allRecordCountRelation + ", allPageCount=" + allPageCount + ", existNextPage=" + existNextPage + ", existPrevPage="
469                 + existPrevPage + ", currentStartRecordNumber=" + currentStartRecordNumber + ", currentEndRecordNumber="
470                 + currentEndRecordNumber + ", pageNumberList=" + pageNumberList + ", searchQuery=" + searchQuery + ", execTime=" + execTime
471                 + ", facetResponse=" + facetResponse + ", partialResults=" + partialResults + ", queryTime=" + queryTime + "]";
472     }
473 
474 }