Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / search / Cursor.java
blob266f58a1eb75e7ead01a2302057a611df8c896ec
1 // Copyright 2012 Google Inc. All Rights Reserved.
3 package com.google.appengine.api.search;
5 import com.google.appengine.api.search.SearchServicePb.SearchParams;
6 import com.google.appengine.api.search.checkers.CursorChecker;
7 import com.google.appengine.api.search.checkers.Preconditions;
9 import java.io.Serializable;
11 /**
12 * Represents a cursor on the set of results found for executing a {@link Query}
13 * during a search on the {@link Index}.
14 * <p>
15 * For example, the following code shows how to use a cursor to get the
16 * next page of results
17 * <p>
18 * <pre>
19 * Index index = ...
20 * Cursor cursor = Cursor.newBuilder().build();
21 * Query query = Query.newBuilder().setOptions(
22 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
24 * // Get the first page of results
25 * Results&lt;ScoredDocument&gt; results = index.search(query);
27 * // process results
28 * ...
30 * // Get the next set of results from the returned cursor
31 * query = Query.newBuilder().setOptions(
32 * QueryOptions.newBuilder().setCursor(
33 * results.getCursor()).build("some query"));
35 * results = index.search(query);
36 * </pre>
37 * <p>
38 * Alternatively, you can get a cursor to continue from each of the returned
39 * results.
40 * <pre>
41 * Cursor cursor =
42 * Cursor.newBuilder().setPerResult(true).build();
43 * Query query = Query.newBuilder().setOptions(
44 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
46 * // Get the first page of results
47 * Results&lt;ScoredDocument&gt; results = index.search(query);
49 * // process results
50 * for (ScoredDocument result : results) {
51 * // choose a cursor from one of the results
52 * cursor = result.getCursor();
53 * }
55 * // Get the next set of results from the result's cursor
56 * query = Query.newBuilder().setOptions(
57 * QueryOptions.newBuilder().setCursor(cursor).build("some query"));
59 * results = index.search(query);
60 * </pre>
63 public final class Cursor implements Serializable {
64 private static final long serialVersionUID = 5983232584697220044L;
66 /**
67 * A builder which constructs Cursor objects.
69 public static final class Builder { private String webSafeString; private boolean perResult;
71 private Builder() {
74 /**
75 * Constructs a {@link Cursor} builder with the given request.
77 * @param request the search request to populate the builder
79 private Builder(Cursor request) {
80 setFromWebSafeString(request.toWebSafeString());
83 /**
84 * Sets whether or not to return a Cursor on each individual result
85 * in {@link Results} or a single cursor with all
86 * Results.
88 * @param perResult if True, then return a Cursor with each
89 * {@link ScoredDocument} result, otherwise return a single Cursor
90 * with {@link Results}
91 * @return this Builder
93 public Builder setPerResult(boolean perResult) {
94 this.perResult = perResult;
95 return this;
98 /**
99 * Construct the final message.
101 * @param webSafeString use a cursor returned from a
102 * previous set of search results as a starting point to retrieve
103 * the next set of results. This can get you better performance, and
104 * also improves the consistency of pagination through index updates
105 * @return the Cursor built from the parameters entered on this
106 * Builder
107 * @throws IllegalArgumentException if the cursor string is invalid
109 public Cursor build(String webSafeString) {
110 setFromWebSafeString(webSafeString);
111 return new Cursor(this);
115 * @param webSafeString a string from some other
116 * {@link Cursor#toWebSafeString}
118 private void setFromWebSafeString(String webSafeString) {
119 CursorChecker.checkCursor(webSafeString);
120 int colon = webSafeString.indexOf(":");
121 Preconditions.checkArgument(colon > 0 && colon < webSafeString.length(),
122 "Invalid format for cursor string");
123 String booleanString = webSafeString.substring(0, colon);
124 Preconditions.checkArgument("true".equals(booleanString) || "false".equals(booleanString),
125 "Invalid format of webSafeString");
126 this.perResult = Boolean.parseBoolean(booleanString);
127 this.webSafeString = webSafeString.substring(colon + 1);
131 * Construct the final message.
133 * @return the Cursor built from the parameters entered on this
134 * Builder
136 public Cursor build() {
137 return new Cursor(this);
141 private final String webSafeString; private final boolean perResult;
144 * Creates a search request from the builder.
146 * @param builder the search request builder to populate with
148 private Cursor(Builder builder) {
149 webSafeString = builder.webSafeString;
150 perResult = builder.perResult;
151 checkValid();
155 * A web safe string representing a cursor returned from a previous set of
156 * search results to use as a starting point to retrieve the next
157 * set of results. Can be null.
159 * @return a web safe string representation of the cursor
161 public String toWebSafeString() {
162 if (webSafeString == null) {
163 return null;
165 return perResult + ":" + webSafeString;
169 * @return whether the cursor is for all Results or one cursor per result
171 public boolean isPerResult() {
172 return perResult;
176 * Creates and returns a {@link Cursor} builder. Set the search request
177 * parameters and use the {@link Builder#build()} method to create a concrete
178 * instance of Cursor.
180 * @return a {@link Builder} which can construct a search request
182 public static Builder newBuilder() {
183 return new Builder();
187 * Creates a builder from the given request.
189 * @param request the search request for the builder to use
190 * to build another request
191 * @return this builder
193 public static Builder newBuilder(Cursor request) {
194 return new Builder(request);
198 * Checks the search specification is valid, specifically, has
199 * a non-null number of documents to return specification, a valid
200 * cursor if present, valid sort specification list, a valid
201 * collection of field names for sorting.
203 * @return this checked Cursor
204 * @throws IllegalArgumentException if some part of the specification is
205 * invalid
207 private Cursor checkValid() {
208 CursorChecker.checkCursor(webSafeString);
209 return this;
213 * Copies the contents of this {@link Cursor} object into a
214 * {@link SearchParams} protocol buffer builder.
216 * @return a search params protocol buffer builder initialized with
217 * the values from this request
218 * @throws IllegalArgumentException if the cursor type is
219 * unknown
221 SearchParams.Builder copyToProtocolBuffer(SearchParams.Builder builder) {
222 if (webSafeString != null) {
223 builder.setCursor(webSafeString);
225 if (perResult) {
226 builder.setCursorType(SearchParams.CursorType.PER_RESULT);
227 } else {
228 builder.setCursorType(SearchParams.CursorType.SINGLE);
230 return builder;
233 @Override
234 public String toString() {
235 return String.format(
236 "Cursor(%s)",
237 Util.fieldToString("webSafeString", toWebSafeString(), true));