/*
* Copyright 2010, Andrew M Gibson
*
* www.andygibson.net
*
* This file is part of DataValve.
*
* DataValve is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* DataValve is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
*
* You should have received a copy of the GNU Lesser General Public License
* along with DataValve. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.fluttercode.datavalve.provider;
import java.io.Serializable;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.fluttercode.datavalve.DataProvider;
import org.fluttercode.datavalve.Paginator;
import org.fluttercode.datavalve.util.LazyList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* <p>
* Implementation of a {@link DataProvider} that holds its own data list in
* memory that is used to back the dataset. Introduces a new abstract method to
* be overridden that allows subclasses to return the <code>List</code> that
* backs this dataset.
* </p>
* <p>
* This class can be used to provide an {@link DataProvider} interface to any
* data even if it is held in any kind of list. For example, you can create a
* paginated dataset from a list of <code>File</code> objects. If the content
* you need is too big to be held in memory, then you could use a
* {@link LazyList} class to back the list.
* </p>
* <p>
* Note that regardless of whether the dataset is paged or not, the
* {@link InMemoryDataProvider#fetchBackingData()} method <b>must</b> return all
* values in the whole dataset. It is only from examining the complete list that
* we determine whether or not paging is available.
* </p>
*
* @author Andy Gibson
*
* @param <T>
* The type of object this dataset contains.
*/
public abstract class InMemoryDataProvider<T> implements DataProvider<T>,
Serializable {
private static final long serialVersionUID = 1L;
private static Logger log = LoggerFactory
.getLogger(InMemoryDataProvider.class);
private Map<String, Comparator<T>> orderKeyMap = new HashMap<String, Comparator<T>>();
private Comparator<T> activeSortOrder;
private boolean orderedAscendingFlag = true;
private List<T> backingData;
/**
* Implements the getResultCount function by fetching the backing data and
* returning the size of that.
*
* @see #getBackingData()
*
* @see org.fluttercode.datavalve.AbstractDataset#fetchResultCount()
*/
public Integer fetchResultCount() {
return Integer.valueOf(getBackingData().size());
}
/**
* Lazy loads the data that backs the dataset result set from the
* <code>fetchBackingData()</code> method.
*
* @see InMemoryDataProvider#fetchBackingData()
*/
public List<T> getBackingData() {
if (backingData == null) {
backingData = fetchBackingData();
if (backingData == null) {
backingData = Collections.emptyList();
}
}
return backingData;
}
/**
* Method that the user must override to return the backing data for this
* dataset. This is the strategy method for implementing this in-memory
* dataset and providing the dataset with all the results that back this
* dataset.
* <p>
* Subclasses should override this method and return the complete list of
* data that this dataset contains
* <p>
* If there is too much data to load at once, consider using a
* {@link LazyList} to back the dataset.
*
* @return The list of type <code>List<T></code> containing all the data
* that is used to provide data to the dataset.
*/
protected abstract List<T> fetchBackingData();
public List<T> fetchResults(Paginator paginator) {
// make this method final since
// make sure we fetch the data
getBackingData();
// check sorting hasn't changed
defineOrdering(paginator.getOrderKey());
defineOrderDirection(paginator.isOrderAscending());
sort();
int startPos = paginator.getFirstResult();
if (startPos > backingData.size()) {
startPos = backingData.size();
}
int endPos = paginator.getMaxRows() == null ? backingData.size()
: startPos + paginator.getMaxRows();
if (endPos >= backingData.size()) {
endPos = backingData.size();
}
List<T> results = backingData.subList(startPos, endPos);
paginator
.setNextAvailable(paginator.getFirstResult() + results.size() < fetchResultCount());
return results;
}
public void invalidateData() {
backingData = null;
}
public Map<String, Comparator<T>> getOrderKeyMap() {
return orderKeyMap;
}
protected boolean isSorted() {
return activeSortOrder != null;
}
private void defineOrderDirection(boolean ascending) {
if (ascending != orderedAscendingFlag) {
orderedAscendingFlag = ascending;
// flip the list
// log.debug("Flipping list for sort order");
// Collections.reverse(getBackingData());
}
}
private void defineOrdering(String key) {
if (key == null) {
activeSortOrder = null;
return;
}
Comparator<T> sorter = translateOrderKey(key);
// if there is no matching sort order, just clear it and leave the order
// as is.
if (sorter == null) {
activeSortOrder = null;
return;
}
// return if we are already sorted by this value then do nothing
if (sorter.equals(activeSortOrder)) {
return;
}
// when we changed the order, it defaults to ascending
// orderedAscendingFlag = true;
activeSortOrder = sorter;
// sort();
}
protected Comparator<T> translateOrderKey(String key) {
return getOrderKeyMap().get(key);
}
public void sort() {
if (isSorted()) {
log.debug("Sorting list using {} ", activeSortOrder);
Collections.sort(getBackingData(), activeSortOrder);
if (!orderedAscendingFlag) {
Collections.reverse(getBackingData());
}
}
}
}