1 // Copyright 2011 Google Inc. All Rights Reserved.
2 package com
.google
.appengine
.api
.datastore
;
4 import java
.io
.IOException
;
5 import java
.io
.ObjectOutputStream
;
6 import java
.io
.Serializable
;
7 import java
.util
.AbstractList
;
8 import java
.util
.ArrayList
;
9 import java
.util
.Iterator
;
10 import java
.util
.List
;
11 import java
.util
.ListIterator
;
12 import java
.util
.NoSuchElementException
;
15 * A {@link List} implementation that pulls query results from the server
18 * Although {@link AbstractList} only requires us to implement
19 * {@link #get(int)}, {@link #size()}, {@link #set(int, Entity)}, and
20 * {@link #remove(int)}, we provide our own implementations for many other
21 * methods. The reason is that many of the implementations in
22 * {@link AbstractList} invoke {@link #size()}, which requires us to pull the
23 * entire result set back from the server. We provide more efficient
24 * implementations wherever possible (which is most places).
26 * @author max.ross@gmail.com (Max Ross)
28 class LazyList
extends AbstractList
<Entity
> implements QueryResultList
<Entity
>, Serializable
{
29 static final long serialVersionUID
= -288529194506134706L;
30 private final transient QueryResultIteratorImpl resultIterator
;
31 final List
<Entity
> results
= new ArrayList
<Entity
>();
32 private boolean endOfData
= false;
33 private boolean cleared
= false;
34 private Cursor cursor
= null;
36 LazyList(QueryResultIteratorImpl resultIterator
) {
37 this.resultIterator
= resultIterator
;
41 * Resolves the entire result set.
43 private void resolveAllData() {
44 resolveToIndex(-1, true);
48 * Resolves enough of the result set to return the {@link Entity} at the
49 * specified {@code index}. There is no guarantee that the result set
50 * actually has an {@link Entity} at this index, but it's up to the caller
51 * to recognize this and respond appropriately.
53 * @param index The index to which we need to resolve.
55 private void resolveToIndex(int index
) {
56 resolveToIndex(index
, false);
60 * Resolves enough of the result set to return the {@link Entity} at the
61 * specified {@code index}. There is no guarantee that the result set
62 * actually has an {@link Entity} at this index, but it's up to the caller
63 * to recognize this and respond appropriately.
65 * @param index The index to which we need to resolve.
66 * @param fetchAll If {@code true}, ignores the provided index and fetches
69 private void resolveToIndex(int index
, boolean fetchAll
) {
73 forceResolveToIndex(index
, fetchAll
);
77 * @see #resolveToIndex(int, boolean) The only difference here is that we
78 * ignore the short-circuit that may have been set by a call to
81 private void forceResolveToIndex(int index
, boolean fetchAll
) {
85 if (fetchAll
|| results
.size() <= index
) {
88 numToFetch
= Integer
.MAX_VALUE
;
90 numToFetch
= (index
- results
.size()) + 1;
93 List
<Entity
> nextBatch
= resultIterator
.nextList(numToFetch
);
94 results
.addAll(nextBatch
);
95 if (nextBatch
.size() < numToFetch
) {
102 * Implementation required for concrete implementations of
103 * {@link AbstractList}.
106 public Entity
get(int i
) {
108 return results
.get(i
);
112 * Implementation required for concrete implementations of
113 * {@link AbstractList}.
118 return results
.size();
122 * Implementation required for concrete, modifiable implementations of
123 * {@link AbstractList}.
126 public Entity
set(int i
, Entity entity
) {
128 return results
.set(i
, entity
);
132 * Implementation required for concrete, modifiable, variable-length
133 * implementations of {@link AbstractList}.
136 public void add(int i
, Entity entity
) {
138 results
.add(i
, entity
);
142 * Implementation required for concrete, modifiable, variable-length
143 * implementations of {@link AbstractList}.
146 public Entity
remove(int i
) {
148 return results
.remove(i
);
152 * We provide our own implementation that does not invoke {@link #size()}.
155 public Iterator
<Entity
> iterator() {
156 return listIterator();
160 * We provide our own implementation that does not invoke {@link #size()}.
162 * @see ListIterator for the spec.
165 public ListIterator
<Entity
> listIterator() {
166 return new ListIterator
<Entity
>() {
167 int currentIndex
= 0;
168 int indexOfLastElementReturned
= -1;
169 boolean elementReturned
= false;
170 boolean addOrRemoveCalledSinceElementReturned
= false;
173 public boolean hasNext() {
174 resolveToIndex(currentIndex
);
175 return currentIndex
< results
.size();
179 public Entity
next() {
181 elementReturned
= true;
182 addOrRemoveCalledSinceElementReturned
= false;
183 indexOfLastElementReturned
= currentIndex
++;
184 return results
.get(indexOfLastElementReturned
);
186 throw new NoSuchElementException();
190 public boolean hasPrevious() {
191 return currentIndex
> 0;
195 public Entity
previous() {
197 elementReturned
= true;
198 addOrRemoveCalledSinceElementReturned
= false;
199 indexOfLastElementReturned
= --currentIndex
;
200 return results
.get(indexOfLastElementReturned
);
202 throw new NoSuchElementException();
206 public int nextIndex() {
211 public int previousIndex() {
212 return currentIndex
- 1;
216 public void remove() {
217 if (!elementReturned
|| addOrRemoveCalledSinceElementReturned
) {
218 throw new IllegalStateException();
220 addOrRemoveCalledSinceElementReturned
= true;
221 if (indexOfLastElementReturned
< currentIndex
) {
224 LazyList
.this.remove(indexOfLastElementReturned
);
228 public void set(Entity entity
) {
229 if (!elementReturned
|| addOrRemoveCalledSinceElementReturned
) {
230 throw new IllegalStateException();
232 LazyList
.this.set(indexOfLastElementReturned
, entity
);
236 public void add(Entity entity
) {
237 addOrRemoveCalledSinceElementReturned
= true;
238 LazyList
.this.add(currentIndex
++, entity
);
244 * The spec for this method says we need to throw
245 * {@link IndexOutOfBoundsException} if {@code index} is < 0 or > size().
246 * Since we need to know size up front, there's no way to service this method
247 * without resolving the entire result set. The only reason for the override
248 * is to provide a good location for this comment.
251 public ListIterator
<Entity
> listIterator(int index
) {
252 return super.listIterator(index
);
256 * We provide our own implementation that does not invoke {@link #size()}.
259 public boolean isEmpty() {
261 return results
.isEmpty();
265 * We provide our own implementation that does not invoke {@link #size()}.
268 public List
<Entity
> subList(int from
, int to
) {
269 resolveToIndex(to
- 1);
270 return results
.subList(from
, to
);
274 * We provide our own implementation that does not invoke {@link #size()}.
277 public void clear() {
283 * We provide our own implementation that does not invoke {@link #size()}.
286 public int indexOf(Object o
) {
288 for (Entity e
: this) {
293 } else if (o
.equals(e
)) {
302 public List
<Index
> getIndexList() {
303 List
<Index
> indexList
= null;
304 if (resultIterator
!= null) {
305 indexList
= resultIterator
.getIndexList();
311 public Cursor
getCursor() {
312 if (cursor
== null && resultIterator
!= null) {
313 forceResolveToIndex(-1, true);
314 cursor
= resultIterator
.getCursor();
320 * Custom serialization logic to ensure that we read the entire result set
321 * before we serialize.
323 private void writeObject(ObjectOutputStream out
) throws IOException
{
325 cursor
= getCursor();
326 out
.defaultWriteObject();