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
= 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 if (lastCursor
!= null) {
129 resultsSinceLastCursor
= -bufferSize
;
131 lastCursor
= nextCursor
;
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
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
172 public void remove() {
173 throw new UnsupportedOperationException();
176 public int getNumSkipped() {
178 return resultsSource
.getNumSkipped();