package com.tri.ui.model; import java.util.List; import com.tri.ui.model.utility.Validate; /** * <p> * List based DataController implementation. Supports (in addition to * {@link ListDataController}: * </p> * <ul> * <li>pagination</li> * <li>unlimited data size (up to 2E31 - 1)</li> * </ul> * <p> * Notifies observers for selection changes. Notice: Sort order changes are not * seen as data changes, see {@link #setSorting(List)} etc. * </p> * * @author khennig@pobox.com * * @param <K> * key * @param <V> * value */ public abstract class PagedListDataController<K, V> extends ListDataController<K, V> implements Pagination { private static final long serialVersionUID = 1L; /** absolute index of first row in current page, zero based */ private int first = 0; /** page size, default 15 */ private int pageSize = 15; /** absolute data count */ private Integer size = null; /** * Counts absolute data size. * * @return absolute data size */ public abstract int count(); /** * Loads data. Loaded data will be cached internally, call * {@link #clearCache()} to clear cache. * * @return List, can be empty but never null. */ public abstract List<V> load(int first, int pageSize, List<SortProperty> sorting); /** * Loads data. Loaded data will be cached internally, call * {@link #clearCache()} to clear cache. * * @return List, can be empty but never null. */ @Override public List<V> load(List<SortProperty> sorting) { return load(getFirst(), getPageSize(), sorting); } @Override public void clearCache() { super.clearCache(); size = null; } /** * Returns the absolute data size (number of values). * * @return absolute data size */ @Override public int getSize() { if (size == null) { size = count(); } return size; } @Override public void firstPage() throws IllegalStateException { Validate.validState(countPages() > 0, "No first page"); setFirst(0); } @Override public void lastPage() throws IllegalStateException { final int pages = countPages(); Validate.validState(pages > 0, "No last page"); gotoPage(pages - 1); } @Override public void nextPage() throws IllegalStateException { Validate.validState(hasNextPage(), "No next page"); gotoPage(getPage() + 1); } /** * Goto previous page. * * @throws IllegalStateException * if there is no previous page */ @Override public void previousPage() throws IllegalStateException { Validate.validState(hasPreviousPage(), "No previous page"); gotoPage(getPage() - 1); } @Override public boolean hasNextPage() { return getPage() < countPages() - 1; } @Override public boolean hasPreviousPage() { return getPage() > 0; } @Override public int countPages() { return (int) Math.ceil((double) getSize() / (double) pageSize); } @Override public int getPage() { return first / pageSize; } @Override public void gotoPage(final int page) throws IndexOutOfBoundsException, IllegalStateException { final int pages = countPages(); Validate.validState(pages > 0, "No pages"); if (page < 0 || page >= pages) { throw new IndexOutOfBoundsException(String.format( "Page not in [0, %d[", pages)); } setFirst(pageSize * page); } @Override public int getFirst() { return first; } @Override public void setFirst(final int first) throws IndexOutOfBoundsException { // adjust to absolute index pointing to first row of page final int newFirst = first - (first % pageSize); if (this.first != newFirst) { final int size = getSize(); if (first != 0 && (first >= size || first < 0)) { throw new IndexOutOfBoundsException(String.format( "First not 0 or (> 0 and < %d): %d", size, first)); } this.first = newFirst; super.clearCache(); } } @Override public int getPageSize() { return pageSize; } @Override public void setPageSize(final int pageSize) { Validate.isTrue(pageSize > 0, "Page Size not > 0: %d", pageSize); if (this.pageSize != pageSize) { this.pageSize = pageSize; setFirst(0); super.clearCache(); } } /** * <p> * Returns the index of the first occurrence of the specified value in this * controller, or -1 if value was not found. This implementation does a * lookup on the current page's data, i.e. if the value wasn't found it * doesn't mean the value is not contained in the controller. * </p> * <p> * Override this method if you need an absolute lookup. * </p> * * @param value * @return found index, or -1 if value was not found * @throws NullPointerException * if parameter value is null * @throws IllegalStateException * if there is no data available */ @Override int getIndexOf(final V value) { Validate.notNull(value, "Value required"); Validate.validState(getSize() > 0, "No data"); // search for value in current page final List<V> data = getData(); final int currentPageSize = data.size(); final K valueKey = getKeyOf(value); for (int localIndex = 0; localIndex < currentPageSize; localIndex++) { if (getKeyOf(data.get(localIndex)).equals(valueKey)) { return first + localIndex; } } return -1; } @Override public void first() throws IllegalStateException { Validate.validState(getSize() > 0, "No data"); gotoPage(0); setSelection(getData().get(0)); selectionIndex = 0; } @Override public void last() throws IllegalStateException { Validate.validState(getSize() > 0, "No data"); lastPage(); final int newSelectionIndex = getSize() - 1; setSelection(getData().get(getData().size() - 1)); selectionIndex = newSelectionIndex; } @Override public void next() throws IllegalStateException { Validate.validState(hasNext(), "Has no next"); final int newSelectionIndex = selectionIndex + 1; setFirst(newSelectionIndex); setSelection(getData().get(newSelectionIndex % pageSize)); selectionIndex = newSelectionIndex; } @Override public void previous() throws IllegalStateException { Validate.validState(hasPrevious(), "Has no previous"); final int newSelectionIndex = selectionIndex - 1; setFirst(newSelectionIndex); setSelection(getData().get(newSelectionIndex % pageSize)); selectionIndex = newSelectionIndex; } }