/*
* 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.dataset;
import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Iterator;
import java.util.List;
import org.fluttercode.datavalve.Paginator;
/**
* This is an abstract dataset that provides common implementation for most of
* the {@link ObjectDataset} methods. It uses a template pattern for
* implementing the fetching of information by letting subclasses define that
* particular function.
* <p>
* The <code>fetchResultCount()</code> and <code>fetchResults()</code> methods
* should be overridden in subclasses to implement the fetching of data and the
* number of records in that data. Apart from these two methods, this class will
* handle all pagination, and the loading of data as needed by calling the two
* methods.
* <p>
* The record count mechanism is implemented such that it is lazy loaded, and
* not called until absolutely needed such as the user calls
* <code>getRecordCount()</code>, <code>getPageCount()</code> or the
* <code>last()</code> method. This is so we do not have to run this expensive
* query unless we absolutely have to.
* <p>
* Note that this class is NOT dependent on any kind of data provider, and you
* can subclass this and implement any kind of data fetching you want.
*
* @author Andy Gibson
*
* @param <T>
* Type of object this dataset contains.
*/
public abstract class AbstractDataset<T> implements ObjectDataset<T>,
Serializable, Iterable<T> {
private static final long serialVersionUID = 1L;
private int firstResult = 0;
private Integer maxRows;
private Integer resultCount;
private List<T> results;
private String orderKey;
private boolean orderAscending = true;
private Class<?> entityClass;
private boolean nextAvailable;
private boolean resetFirstResultOnRefresh = true;
public int getFirstResult() {
return firstResult;
}
public Integer getMaxRows() {
return maxRows;
}
public Integer getResultCount() {
if (resultCount == null) {
resultCount = loadResultCount();
}
return resultCount;
}
public void setResultCount(Integer resultCount) {
this.resultCount = resultCount;
}
protected abstract Integer loadResultCount();
protected abstract List<T> loadResults(Paginator paginator);
public List<T> getResultList() {
if (results == null) {
results = loadResults(this);
}
return results;
}
public void setFirstResult(int firstResult) {
this.firstResult = firstResult;
invalidateResults();
}
public void setMaxRows(Integer maxRows) {
if (maxRows != null && maxRows < 0) {
throw new IllegalArgumentException(
"Max rows returned from query cannot be negative");
}
this.maxRows = maxRows;
invalidateResults();
}
public void invalidateResultInfo() {
resultCount = null;
results = null;
}
public void invalidateResults() {
results = null;
}
public void first() {
firstResult = 0;
invalidateResults();
}
public boolean isPreviousAvailable() {
return getFirstResult() > 0;
}
public void previous() {
if (isPreviousAvailable() && allowPaging()) {
firstResult = firstResult - getMaxRows();
if (firstResult < 0) {
firstResult = 0;
}
invalidateResults();
}
}
public boolean allowPaging() {
return !includeAllResults();
}
public boolean includeAllResults() {
return getMaxRows() == null;
}
public void next() {
if (isNextAvailable() && allowPaging()) {
firstResult = firstResult + getMaxRows();
invalidateResults();
}
}
public int getPage() {
if (!allowPaging()) {
return 1;
}
return (firstResult / getMaxRows()) + 1;
}
public void last() {
// Check if we are returning all rows
if (includeAllResults()) {
setFirstResult(0);
return;
}
setFirstResult((getPageCount() - 1) * getMaxRows());
}
public int getPageCount() {
if (includeAllResults()) {
return 1;
}
float result = (float) getResultCount() / getMaxRows();
if ((int) result != result) {
result = result + 1;
}
return (int) result;
}
public boolean isMultiPage() {
return isNextAvailable() || isPreviousAvailable();
}
public Iterator<T> iterator() {
return new DatasetIterator<T>(this);
}
public String getOrderKey() {
return orderKey;
}
public void setOrderKey(String orderKey) {
if (this.orderKey == null) {
if (orderKey == null) {
return;
}
this.orderKey = orderKey;
invalidateResults();
return;
}
// this.order key is not null
if (!this.orderKey.equals(orderKey)) {
this.orderKey = orderKey;
invalidateResults();
}
}
/**
* This method sets the orderKey value, but also compares the existing value
* and if they are the same, then the ascending flag is toggled.
*
* @param orderKey
* new key value to order by
*/
public void changeOrderKey(String orderKey) {
// if not setting it to null check whether the current value matches the
// new value and set order ascending flag accordingly
if (orderKey != null) {
if (orderKey.equals(this.orderKey)) {
setOrderAscending(!isOrderAscending());
} else {
setOrderKey(orderKey);
setOrderAscending(true);
}
} else {
// otherwise we are just setting the order to null
this.orderKey = null;
}
}
public boolean isOrderAscending() {
return orderAscending;
}
public void setOrderAscending(boolean isAscending) {
// check the value is different before we invalidate the results
if (this.orderAscending != isAscending) {
this.orderAscending = isAscending;
invalidateResults();
}
}
public Class<?> getEntityClass() {
if (entityClass == null) {
ParameterizedType type = (ParameterizedType) getClass()
.getGenericSuperclass();
entityClass = (Class<?>) type.getActualTypeArguments()[0];
}
return entityClass;
}
public void refresh() {
invalidateResults();
if (getResetFirstResultOnRefresh()) {
firstResult = 0;
}
}
public boolean isNextAvailable() {
getResultList();
return nextAvailable;
}
public void setNextAvailable(boolean nextAvailable) {
this.nextAvailable = nextAvailable;
}
public void setResetFirstResultOnRefresh(boolean resetFirstResultOnRefresh) {
this.resetFirstResultOnRefresh = resetFirstResultOnRefresh;
}
public boolean getResetFirstResultOnRefresh() {
return resetFirstResultOnRefresh;
}
}