
org.vaadin.viritin.LazyList Maven / Gradle / Ivy
package org.vaadin.viritin;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
/**
* A general purpose helper class to us MTable/ListContainer for service layers
* (EJBs, Spring Data etc) that provide large amount of data. Makes paged
* requests to PagingProvider, caches recently used pages in memory and this way
* hides away Vaadin Container complexity from you. The class generic helper and
* is probably useful also other but Vaadin applications as well.
*
* @author Matti Tahvonen
* @param The type of the objects in the list
*/
public class LazyList extends AbstractList implements Serializable {
private static final long serialVersionUID = 2423832460602269469L;
private List findPageFromCache(int pageIndexForReqest) {
int p = pageIndexForReqest - pageIndex;
if (p < 0) {
return null;
}
if (pages.size() <= p) {
return null;
}
return pages.get(p);
}
private void loadPreviousPage() {
pageIndex--;
List page = findEntities(pageIndex * pageSize);
pages.add(0, page);
if (pages.size() > maxPages) {
pages.remove(pages.size() - 1);
}
}
private void loadNextPage() {
List page = findEntities((pageIndex + pages.size()) * pageSize);
pages.add(page);
if (pages.size() > maxPages) {
pages.remove(0);
pageIndex++;
}
}
// Split into subinterfaces for better Java 8 lambda support
/**
* Interface via the LazyList communicates with the "backend"
*
* @param The type of the objects in the list
*/
public interface PagingProvider extends Serializable {
/**
* Fetches one "page" of entities form the backend. The amount
* "maxResults" should match with the value configured for the LazyList
*
* @param firstRow the index of first row that should be fetched
* @return a sub list from given first index
*/
public List findEntities(int firstRow);
}
/**
* LazyList detects the size of the "simulated" list with via this
* interface. Backend call is cached as COUNT queries in databases are
* commonly heavy.
*/
public interface CountProvider extends Serializable {
/**
* @return the count of entities listed in the LazyList
*/
public int size();
}
/**
* Interface via the LazyList communicates with the "backend"
*
* @param The type of the objects in the list
*/
public interface EntityProvider extends PagingProvider, CountProvider {
}
private PagingProvider pageProvider;
private final CountProvider countProvider;
// Vaadin table by default has 15 rows, 2x that to cache up an down
// With this setting it is maximum of 2 requests that happens. With
// normal scrolling just 0-1 per user interaction
public static final int DEFAULT_PAGE_SIZE = 15 + 15 * 2;
public int getMaxPages() {
return maxPages;
}
/**
* Sets the maximum of pages that are held in memory. By default 3, but it
* is adjusted automatically based on requests that are made to the list,
* like subList method calls. Most often this shouldn't be called by end
* user.
*
* @param maxPages the number of pages to be held in memory
*/
public void setMaxPages(int maxPages) {
this.maxPages = maxPages;
}
private int maxPages = 3;
List> pages = new ArrayList<>();
private int pageIndex = -10;
private final int pageSize;
protected LazyList(CountProvider countProvider, int pageSize) {
this.countProvider = countProvider;
this.pageSize = pageSize;
}
/**
* Constructs a new LazyList with given provider and default page size of
* DEFAULT_PAGE_SIZE (30).
*
* @param dataProvider the data provider that is used to fetch pages of
* entities and to detect the total count of entities
*/
public LazyList(EntityProvider dataProvider) {
this(dataProvider, DEFAULT_PAGE_SIZE);
}
/**
* Constructs a new LazyList with given provider and default page size of
* DEFAULT_PAGE_SIZE (30).
*
* @param dataProvider the data provider that is used to fetch pages of
* entities and to detect the total count of entities
* @param pageSize the page size to be used
*/
public LazyList(EntityProvider dataProvider, int pageSize) {
this.pageProvider = dataProvider;
this.countProvider = dataProvider;
this.pageSize = pageSize;
}
/**
* Constructs a new LazyList with given providers and default page size of
* DEFAULT_PAGE_SIZE (30).
*
* @param pageProvider the interface via "pages" of entities are requested
* @param countProvider the interface via the total count of entities is
* detected.
*/
public LazyList(PagingProvider pageProvider, CountProvider countProvider) {
this(pageProvider, countProvider, DEFAULT_PAGE_SIZE);
}
/**
* Constructs a new LazyList with given providers and page size.
*
* @param pageProvider the interface via "pages" of entities are requested
* @param countProvider the interface via the total count of entities is
* detected.
* @param pageSize the page size that should be used
*/
public LazyList(PagingProvider pageProvider, CountProvider countProvider, int pageSize) {
this.pageProvider = pageProvider;
this.countProvider = countProvider;
this.pageSize = pageSize;
}
@Override
public T get(final int index) {
final int pageIndexForReqest = index / pageSize;
final int indexOnPage = index % pageSize;
// Find page from cache
List page = findPageFromCache(pageIndexForReqest);
if (page == null) {
if (pageIndex >= 0) {
if (pageIndexForReqest > pageIndex && pageIndexForReqest < pageIndex + pages.size() + maxPages) {
// load next n pages forward
while (pageIndexForReqest >= pageIndex + pages.size()) {
loadNextPage();
}
} else if (pageIndexForReqest < pageIndex && pageIndexForReqest > pageIndex - maxPages) {
//load prev page to cache
while (pageIndexForReqest < pageIndex) {
loadPreviousPage();
}
} else {
initCacheFormPage(pageIndexForReqest);
}
} else {
// first page to load
initCacheFormPage(pageIndexForReqest);
}
page = findPageFromCache(pageIndexForReqest);
}
final T get = page.get(indexOnPage);
return get;
}
protected void initCacheFormPage(final int pageIndexForReqest) {
// clear cache
pageIndex = pageIndexForReqest;
pages.clear();
pages.add(findEntities(pageIndex * pageSize));
}
protected List findEntities(int i) {
return pageProvider.findEntities(i);
}
private Integer cachedSize;
@Override
public int size() {
if (cachedSize == null) {
cachedSize = countProvider.size();
}
return cachedSize;
}
private transient WeakHashMap indexCache;
private Map getIndexCache() {
if (indexCache == null) {
indexCache = new WeakHashMap<>();
}
return indexCache;
}
@Override
public int indexOf(Object o) {
// optimize: check the buffers first
Integer indexViaCache = getIndexCache().get(o);
if (indexViaCache != null) {
return indexViaCache;
}
for (int i = 0; i < pages.size(); i++) {
List page = pages.get(i);
int indexOf = page.indexOf(o);
if (indexOf != -1) {
indexViaCache = (pageIndex + i) * pageSize + indexOf;
}
}
if (indexViaCache != null) {
/*
* In some cases (selected value) components like Vaadin combobox calls this, then stuff from elsewhere with indexes and
* finally again this method with the same object (possibly on other page). Thus, to avoid heavy iterating,
* cache the location.
*/
getIndexCache().put((T) o, indexViaCache);
return indexViaCache;
}
// fall back to iterating, this will most likely be sloooooow....
// If your app gets here, consider overwriting this method, and to
// some optimization at service/db level
return super.indexOf(o);
}
@Override
public boolean contains(Object o) {
// Although there would be the indexed version, vaadin sometimes calls this
// First check caches, then fall back to sluggish iterator :-(
if (getIndexCache().containsKey(o)) {
return true;
}
for (List t : pages) {
if (t.contains(o)) {
return true;
}
}
return super.contains(o);
}
@Override
public List subList(int fromIndex, int toIndex) {
final int sizeOfSublist = toIndex - fromIndex;
if (sizeOfSublist > maxPages * (pageSize -1)) {
// Increase the amount of cached pages if necessary
maxPages = sizeOfSublist/pageSize + 1;
}
return new ArrayList<>(super.subList(fromIndex, toIndex));
}
@Override
public Iterator iterator() {
return new Iterator() {
private int index = -1;
private final int size = size();
@Override
public boolean hasNext() {
return index + 1 < size;
}
@Override
public T next() {
index++;
return get(index);
}
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported.");
}
};
}
/**
* Resets buffers used by the LazyList.
*/
public void reset() {
pages.clear();
pageIndex = -10;
cachedSize = null;
if (indexCache != null) {
indexCache.clear();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy