Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / api / datastore / LazyList.java
blobb515ae9b3a8af8d66a7d314e187ff39e8dc09564
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;
14 /**
15 * A {@link List} implementation that pulls query results from the server
16 * lazily.
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 <max.ross@gmail.com>
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;
40 /**
41 * Resolves the entire result set.
43 private void resolveAllData() {
44 resolveToIndex(-1, true);
47 /**
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);
59 /**
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
67 * all data.
69 private void resolveToIndex(int index, boolean fetchAll) {
70 if (cleared) {
71 return;
73 forceResolveToIndex(index, fetchAll);
76 /**
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
79 * {@link #clear()}.
81 private void forceResolveToIndex(int index, boolean fetchAll) {
82 if (endOfData) {
83 return;
85 if (fetchAll || results.size() <= index) {
86 int numToFetch;
87 if (fetchAll) {
88 numToFetch = Integer.MAX_VALUE;
89 } else {
90 numToFetch = (index - results.size()) + 1;
93 List<Entity> nextBatch = resultIterator.nextList(numToFetch);
94 results.addAll(nextBatch);
95 if (nextBatch.size() < numToFetch) {
96 endOfData = true;
102 * Implementation required for concrete implementations of
103 * {@link AbstractList}.
105 @Override
106 public Entity get(int i) {
107 resolveToIndex(i);
108 return results.get(i);
112 * Implementation required for concrete implementations of
113 * {@link AbstractList}.
115 @Override
116 public int size() {
117 resolveAllData();
118 return results.size();
122 * Implementation required for concrete, modifiable implementations of
123 * {@link AbstractList}.
125 @Override
126 public Entity set(int i, Entity entity) {
127 resolveToIndex(i);
128 return results.set(i, entity);
132 * Implementation required for concrete, modifiable, variable-length
133 * implementations of {@link AbstractList}.
135 @Override
136 public void add(int i, Entity entity) {
137 resolveToIndex(i);
138 results.add(i, entity);
142 * Implementation required for concrete, modifiable, variable-length
143 * implementations of {@link AbstractList}.
145 @Override
146 public Entity remove(int i) {
147 resolveToIndex(i);
148 return results.remove(i);
152 * We provide our own implementation that does not invoke {@link #size()}.
154 @Override
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.
164 @Override
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;
172 @Override
173 public boolean hasNext() {
174 resolveToIndex(currentIndex);
175 return currentIndex < results.size();
178 @Override
179 public Entity next() {
180 if (hasNext()) {
181 elementReturned = true;
182 addOrRemoveCalledSinceElementReturned = false;
183 indexOfLastElementReturned = currentIndex++;
184 return results.get(indexOfLastElementReturned);
186 throw new NoSuchElementException();
189 @Override
190 public boolean hasPrevious() {
191 return currentIndex > 0;
194 @Override
195 public Entity previous() {
196 if (hasPrevious()) {
197 elementReturned = true;
198 addOrRemoveCalledSinceElementReturned = false;
199 indexOfLastElementReturned = --currentIndex;
200 return results.get(indexOfLastElementReturned);
202 throw new NoSuchElementException();
205 @Override
206 public int nextIndex() {
207 return currentIndex;
210 @Override
211 public int previousIndex() {
212 return currentIndex - 1;
215 @Override
216 public void remove() {
217 if (!elementReturned || addOrRemoveCalledSinceElementReturned) {
218 throw new IllegalStateException();
220 addOrRemoveCalledSinceElementReturned = true;
221 if (indexOfLastElementReturned < currentIndex) {
222 currentIndex--;
224 LazyList.this.remove(indexOfLastElementReturned);
227 @Override
228 public void set(Entity entity) {
229 if (!elementReturned || addOrRemoveCalledSinceElementReturned) {
230 throw new IllegalStateException();
232 LazyList.this.set(indexOfLastElementReturned, entity);
235 @Override
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.
250 @Override
251 public ListIterator<Entity> listIterator(int index) {
252 return super.listIterator(index);
256 * We provide our own implementation that does not invoke {@link #size()}.
258 @Override
259 public boolean isEmpty() {
260 resolveToIndex(0);
261 return results.isEmpty();
265 * We provide our own implementation that does not invoke {@link #size()}.
267 @Override
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()}.
276 @Override
277 public void clear() {
278 results.clear();
279 cleared = true;
283 * We provide our own implementation that does not invoke {@link #size()}.
285 @Override
286 public int indexOf(Object o) {
287 int index = 0;
288 for (Entity e : this) {
289 if (o == null) {
290 if (e == null) {
291 return index;
293 } else if (o.equals(e)) {
294 return index;
296 index++;
298 return -1;
301 @Override
302 public List<Index> getIndexList() {
303 List<Index> indexList = null;
304 if (resultIterator != null) {
305 indexList = resultIterator.getIndexList();
307 return indexList;
310 @Override
311 public Cursor getCursor() {
312 if (cursor == null && resultIterator != null) {
313 forceResolveToIndex(-1, true);
314 cursor = resultIterator.getCursor();
316 return cursor;
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 {
324 resolveAllData();
325 cursor = getCursor();
326 out.defaultWriteObject();