1.9.30 sync.
[gae.git] / java / src / main / com / google / appengine / api / datastore / QueryResultIteratorImpl.java
blobd06ac2862efb97e9f97ee1a722d1b14a47e62d17
1 // Copyright 2007 Google Inc. All rights reserved.
3 package com.google.appengine.api.datastore;
5 import java.util.ArrayList;
6 import java.util.LinkedList;
7 import java.util.List;
8 import java.util.NoSuchElementException;
10 /**
11 * {@link QueryResultIteratorImpl} returns zero or more {@link Entity} objects
12 * that are the result of a {@link Query} that has been executed.
14 * {@link Entity} objects can be retrieved one-at-a-time using the
15 * standard {@link QueryResultIterator} interface. In addition, we extend this
16 * interface to allow access to batches of {@link Entity} objects by
17 * calling {@link #nextList}.
19 * If the {@link Query} is keys only, i.e. {@link Query#isKeysOnly()} returns
20 * true, then the {@link Entity} objects returned by this iterator will only
21 * have their key populated. They won't have properties or other data.
23 * Note: this class is not thread-safe.
26 class QueryResultIteratorImpl implements QueryResultIterator<Entity> {
27 private final PreparedQuery query;
28 private final QueryResultsSource resultsSource;
29 private final LinkedList<Entity> entityBuffer;
30 private final LinkedList<Cursor> entityCursorBuffer;
31 private final Transaction txn;
32 private Cursor lastCursor = null;
33 private Cursor nextCursor;
34 private int resultsSinceLastCursor = 0;
36 /**
37 * Create a QueryIterator that wraps around the specified Cursor.
38 * Elements will be retrieved in batches {@code minRequestSize}.
40 QueryResultIteratorImpl(
41 PreparedQuery query,
42 QueryResultsSource resultsSource,
43 FetchOptions fetchOptions,
44 Transaction txn) {
45 this.query = query;
46 this.resultsSource = resultsSource;
47 this.entityBuffer = new LinkedList<Entity>();
48 this.entityCursorBuffer = new LinkedList<Cursor>();
49 this.txn = txn;
51 if (fetchOptions.getCompile() == Boolean.TRUE) {
52 nextCursor = fetchOptions.getStartCursor() == null
53 ? new Cursor()
54 : new Cursor(fetchOptions.getStartCursor());
56 if (fetchOptions.getOffset() != null) {
57 resultsSinceLastCursor = fetchOptions.getOffset();
62 @Override
63 public boolean hasNext() {
64 return ensureLoaded();
67 @Override
68 public Entity next() {
69 TransactionImpl.ensureTxnActive(txn);
70 if (ensureLoaded()) {
71 ++resultsSinceLastCursor;
72 entityCursorBuffer.removeFirst();
73 return entityBuffer.removeFirst();
74 } else {
75 throw new NoSuchElementException();
79 @Override
80 public List<Index> getIndexList() {
81 return resultsSource.getIndexList();
84 @Override
85 public Cursor getCursor() {
86 ensureInitialized();
87 Cursor cursor = null;
88 if (!entityCursorBuffer.isEmpty() && entityCursorBuffer.getFirst() != null) {
89 cursor = entityCursorBuffer.getFirst();
90 } else if (entityBuffer.isEmpty()) {
91 cursor = nextCursor;
92 } else if (lastCursor != null) {
93 lastCursor = lastCursor.advance(resultsSinceLastCursor, query);
94 resultsSinceLastCursor = 0;
95 cursor = lastCursor;
98 if (cursor != null) {
99 return new Cursor(cursor);
100 } else {
101 return null;
106 * Returns a {@code List} of up to {@code maximumElements}
107 * elements. If there are fewer than this many entities left to
108 * be retrieved, the {@code List} returned will simply contain
109 * less than {@code maximumElements} elements. In particular,
110 * calling this when there are no elements remaining is not an
111 * error, it simply returns an empty list.
113 public List<Entity> nextList(int maximumElements) {
114 TransactionImpl.ensureTxnActive(txn);
115 ensureLoaded(maximumElements);
117 int numberToReturn = Math.min(maximumElements, entityBuffer.size());
118 List<Entity> backingList = entityBuffer.subList(0, numberToReturn);
119 List<Entity> returnList = new ArrayList<Entity>(backingList);
120 backingList.clear();
121 entityCursorBuffer.subList(0, numberToReturn).clear();
122 resultsSinceLastCursor += returnList.size();
123 return returnList;
126 private void saveNextCursor(int bufferSize, Cursor next) {
127 if (next != null) {
128 if (lastCursor != null) {
129 resultsSinceLastCursor = -bufferSize;
131 lastCursor = nextCursor;
132 nextCursor = next;
136 private void ensureInitialized() {
137 saveNextCursor(entityBuffer.size(),
138 resultsSource.loadMoreEntities(0, entityBuffer, entityCursorBuffer));
142 * Request additional {@code Entity} objects from the current cursor so
143 * that there is at least one pending object.
145 private boolean ensureLoaded() {
146 if (entityBuffer.isEmpty() && resultsSource.hasMoreEntities()) {
147 saveNextCursor(entityBuffer.size(),
148 resultsSource.loadMoreEntities(entityBuffer, entityCursorBuffer));
150 return !entityBuffer.isEmpty();
154 * Request enough additional {@code Entity} objects from the
155 * current results source that there are {@code numberDesired} pending
156 * objects.
158 private boolean ensureLoaded(int numberDesired) {
159 int numberToLoad = numberDesired - entityBuffer.size();
160 if (numberToLoad > 0 && resultsSource.hasMoreEntities()) {
161 saveNextCursor(entityBuffer.size(),
162 resultsSource.loadMoreEntities(numberToLoad, entityBuffer, entityCursorBuffer));
164 return entityBuffer.size() >= numberDesired;
168 * The optional remove method is not supported.
169 * @throws UnsupportedOperationException
171 @Override
172 public void remove() {
173 throw new UnsupportedOperationException();
176 public int getNumSkipped() {
177 ensureInitialized();
178 return resultsSource.getNumSkipped();