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
;
8 import java
.util
.NoSuchElementException
;
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;
37 * Create a QueryIterator that wraps around the specified Cursor.
38 * Elements will be retrieved in batches {@code minRequestSize}.
40 QueryResultIteratorImpl(
42 QueryResultsSource resultsSource
,
43 FetchOptions fetchOptions
,
46 this.resultsSource
= resultsSource
;
47 this.entityBuffer
= new LinkedList
<Entity
>();
48 this.entityCursorBuffer
= new LinkedList
<Cursor
>();
51 if (fetchOptions
.getCompile() == Boolean
.TRUE
) {
52 nextCursor
= fetchOptions
.getStartCursor() == null
54 : new Cursor(fetchOptions
.getStartCursor());
56 if (fetchOptions
.getOffset() != null) {
57 resultsSinceLastCursor
= fetchOptions
.getOffset();
63 public boolean hasNext() {
64 return ensureLoaded();
68 public Entity
next() {
69 TransactionImpl
.ensureTxnActive(txn
);
71 ++resultsSinceLastCursor
;
72 entityCursorBuffer
.removeFirst();
73 return entityBuffer
.removeFirst();
75 throw new NoSuchElementException();
80 public List
<Index
> getIndexList() {
81 return resultsSource
.getIndexList();
85 public Cursor
getCursor() {
88 if (!entityCursorBuffer
.isEmpty() && entityCursorBuffer
.getFirst() != null) {
89 cursor
= entityCursorBuffer
.getFirst();
90 } else if (entityBuffer
.isEmpty()) {
92 } else if (lastCursor
!= null) {
93 lastCursor
.advance(resultsSinceLastCursor
, query
);
94 resultsSinceLastCursor
= 0;
99 return new Cursor(cursor
);
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
);
121 entityCursorBuffer
.subList(0, numberToReturn
).clear();
122 resultsSinceLastCursor
+= returnList
.size();
126 private void saveNextCursor(int bufferSize
, Cursor next
) {
128 lastCursor
= nextCursor
;
129 resultsSinceLastCursor
= -bufferSize
;
134 private void ensureInitialized() {
135 saveNextCursor(entityBuffer
.size(),
136 resultsSource
.loadMoreEntities(0, entityBuffer
, entityCursorBuffer
));
140 * Request additional {@code Entity} objects from the current cursor so
141 * that there is at least one pending object.
143 private boolean ensureLoaded() {
144 if (entityBuffer
.isEmpty() && resultsSource
.hasMoreEntities()) {
145 saveNextCursor(entityBuffer
.size(),
146 resultsSource
.loadMoreEntities(entityBuffer
, entityCursorBuffer
));
148 return !entityBuffer
.isEmpty();
152 * Request enough additional {@code Entity} objects from the
153 * current results source that there are {@code numberDesired} pending
156 private boolean ensureLoaded(int numberDesired
) {
157 int numberToLoad
= numberDesired
- entityBuffer
.size();
158 if (numberToLoad
> 0 && resultsSource
.hasMoreEntities()) {
159 saveNextCursor(entityBuffer
.size(),
160 resultsSource
.loadMoreEntities(numberToLoad
, entityBuffer
, entityCursorBuffer
));
162 return entityBuffer
.size() >= numberDesired
;
166 * The optional remove method is not supported.
167 * @throws UnsupportedOperationException
170 public void remove() {
171 throw new UnsupportedOperationException();
174 public int getNumSkipped() {
176 return resultsSource
.getNumSkipped();