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

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