All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.appengine.api.datastore.LazyList Maven / Gradle / Ivy

Go to download

API for Google App Engine standard environment with some of the dependencies shaded (repackaged)

There is a newer version: 2.0.27
Show newest version
/*
 * Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.appengine.api.datastore;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.NoSuchElementException;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * A {@link List} implementation that pulls query results from the server lazily.
 *
 * 

Although {@link AbstractList} only requires us to implement {@link #get(int)}, {@link * #size()}, {@link #set(int, Entity)}, and {@link #remove(int)}, we provide our own implementations * for many other methods. The reason is that many of the implementations in {@link AbstractList} * invoke {@link #size()}, which requires us to pull the entire result set back from the server. We * provide more efficient implementations wherever possible (which is most places). * */ class LazyList extends AbstractList implements QueryResultList, Serializable { static final long serialVersionUID = -288529194506134706L; // The iterator from which we'll pull results lazily. Will be null if this // object is instantiated via deserialization. @Nullable private final transient QueryResultIteratorImpl resultIterator; final List results = new ArrayList(); // True if the entire result set has been fetched from the server. private boolean endOfData = false; // True if the user has called clear(). We require a separate flag for this // because if a user requests a Cursor we need to fetch the entire result set // from the server even if clear() has been called (which in all other cases // prevents us from fetching additional results). private boolean cleared = false; // Null until the user asks for it. Used to support serialization. /* @Nullabe */ private Cursor cursor = null; LazyList(QueryResultIteratorImpl resultIterator) { this.resultIterator = resultIterator; } /** Resolves the entire result set. */ private void resolveAllData() { resolveToIndex(-1, true); } /** * Resolves enough of the result set to return the {@link Entity} at the specified {@code index}. * There is no guarantee that the result set actually has an {@link Entity} at this index, but * it's up to the caller to recognize this and respond appropriately. * * @param index The index to which we need to resolve. */ private void resolveToIndex(int index) { resolveToIndex(index, false); } /** * Resolves enough of the result set to return the {@link Entity} at the specified {@code index}. * There is no guarantee that the result set actually has an {@link Entity} at this index, but * it's up to the caller to recognize this and respond appropriately. * * @param index The index to which we need to resolve. * @param fetchAll If {@code true}, ignores the provided index and fetches all data. */ private void resolveToIndex(int index, boolean fetchAll) { if (cleared) { // user called clear() so don't bother trying to fetch any more data return; } forceResolveToIndex(index, fetchAll); } /** * @see #resolveToIndex(int, boolean) The only difference here is that we ignore the short-circuit * that may have been set by a call to {@link #clear()}. */ private void forceResolveToIndex(int index, boolean fetchAll) { if (endOfData) { // No more data so don't bother trying to fetch. return; } if (fetchAll || results.size() <= index) { // There might be more data available and we haven't resolved enough of // the result set to return the Entity at the index specified by the // user, so fetch more data. int numToFetch; if (fetchAll) { numToFetch = Integer.MAX_VALUE; } else { numToFetch = (index - results.size()) + 1; } if (resultIterator == null) { endOfData = true; } else { List nextBatch = resultIterator.nextList(numToFetch); results.addAll(nextBatch); if (nextBatch.size() < numToFetch) { // If we got fewer results than we asked for then we know there is no // more data. endOfData = true; } } } } /** Implementation required for concrete implementations of {@link AbstractList}. */ @Override public Entity get(int i) { resolveToIndex(i); // Will throw IndexOutOfBounds if the result set isn't large enough, which // is what we want. return results.get(i); } /** Implementation required for concrete implementations of {@link AbstractList}. */ @Override public int size() { resolveAllData(); return results.size(); } /** Implementation required for concrete, modifiable implementations of {@link AbstractList}. */ @Override public Entity set(int i, Entity entity) { resolveToIndex(i); return results.set(i, entity); } /** * Implementation required for concrete, modifiable, variable-length implementations of {@link * AbstractList}. */ @Override public void add(int i, Entity entity) { resolveToIndex(i); // Will throw IndexOutOfBounds if the result set isn't large enough, which // is what we want. results.add(i, entity); } /** * Implementation required for concrete, modifiable, variable-length implementations of {@link * AbstractList}. */ @Override public Entity remove(int i) { resolveToIndex(i); // Will throw IndexOutOfBounds if the result set isn't large enough, which // is what we want. return results.remove(i); } /** We provide our own implementation that does not invoke {@link #size()}. */ @Override public Iterator iterator() { return listIterator(); } /** * We provide our own implementation that does not invoke {@link #size()}. * * @see ListIterator for the spec. */ @Override public ListIterator listIterator() { return new ListIterator() { // Index into the List over which we're iterating. // Must be >= 0 and < size. When next() is invoked, the element at this // index will be returned. When previous() is invoked, the element at // this index - 1 will be returned. int currentIndex = 0; int indexOfLastElementReturned = -1; // The ListIterator spec has all sorts of rules about when you can call // different methods. We use these members for book keeping so we can // enforce these rules. boolean elementReturned = false; boolean addOrRemoveCalledSinceElementReturned = false; @Override public boolean hasNext() { resolveToIndex(currentIndex); return currentIndex < results.size(); } @Override public Entity next() { if (hasNext()) { elementReturned = true; addOrRemoveCalledSinceElementReturned = false; indexOfLastElementReturned = currentIndex++; return results.get(indexOfLastElementReturned); } throw new NoSuchElementException(); } @Override public boolean hasPrevious() { return currentIndex > 0; } @Override public Entity previous() { if (hasPrevious()) { elementReturned = true; addOrRemoveCalledSinceElementReturned = false; indexOfLastElementReturned = --currentIndex; return results.get(indexOfLastElementReturned); } throw new NoSuchElementException(); } @Override public int nextIndex() { return currentIndex; } @Override public int previousIndex() { return currentIndex - 1; } @Override public void remove() { if (!elementReturned || addOrRemoveCalledSinceElementReturned) { // ListIterator returned by ArrayList throws an exception with no // message for all these conditions. throw new IllegalStateException(); } addOrRemoveCalledSinceElementReturned = true; if (indexOfLastElementReturned < currentIndex) { // the item we're removing is earlier than the current pointer so // move the current pointer down by one currentIndex--; } LazyList.this.remove(indexOfLastElementReturned); } @Override public void set(Entity entity) { if (!elementReturned || addOrRemoveCalledSinceElementReturned) { // ListIterator returned by ArrayList throws an exception with no // message. throw new IllegalStateException(); } LazyList.this.set(indexOfLastElementReturned, entity); } @Override public void add(Entity entity) { addOrRemoveCalledSinceElementReturned = true; LazyList.this.add(currentIndex++, entity); } }; } /** * The spec for this method says we need to throw {@link IndexOutOfBoundsException} if {@code * index} is < 0 or > size(). Since we need to know size up front, there's no way to service this * method without resolving the entire result set. The only reason for the override is to provide * a good location for this comment. */ @Override public ListIterator listIterator(int index) { return super.listIterator(index); } /** We provide our own implementation that does not invoke {@link #size()}. */ @Override public boolean isEmpty() { resolveToIndex(0); return results.isEmpty(); } /** We provide our own implementation that does not invoke {@link #size()}. */ @Override public List subList(int from, int to) { resolveToIndex(to - 1); return results.subList(from, to); } /** We provide our own implementation that does not invoke {@link #size()}. */ @Override public void clear() { // clear out whatever we've paged in thus far results.clear(); // make sure nothing else gets paged in cleared = true; } /** We provide our own implementation that does not invoke {@link #size()}. */ @Override public int indexOf(Object o) { int index = 0; // for loop invokes iterator(), which will only pull back data as needed for (Entity e : this) { if (o == null) { if (e == null) { return index; } } else if (o.equals(e)) { return index; } index++; } return -1; } @Override public List getIndexList() { List indexList = null; if (resultIterator != null) { indexList = resultIterator.getIndexList(); } return indexList; } @Override public Cursor getCursor() { if (cursor == null && resultIterator != null) { // The cursor needs to point to the end of the result set, so make sure we // resolve the entire result set before we actually ask for the cursor. forceResolveToIndex(-1, true); // Fetch all data. cursor = resultIterator.getCursor(); } return cursor; } /** * Custom serialization logic to ensure that we read the entire result set before we serialize. */ private void writeObject(ObjectOutputStream out) throws IOException { // Resolve all data before we serialize. resolveAllData(); // Get ahold of the cursor before we serialize. cursor = getCursor(); out.defaultWriteObject(); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy