/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.wicket.markup.repeater;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import org.apache.wicket.markup.html.navigation.paging.IPageableItems;
import org.apache.wicket.model.IModel;
/**
* An abstract repeater view that provides paging functionality to its subclasses.
* <p>
* The view is populated by overriding the <code>getItemModels(int offset, int count)</code> method
* and providing an iterator that returns models for items in the current page. The
* AbstractPageableView builds the items that will be rendered by looping over the models and
* calling the <code>newItem(String id, int index, IModel model)</code> to generate the child item
* container followed by <code>populateItem(Component item)</code> to let the user populate the
* newly created item container with with custom components.
* </p>
*
* @see org.apache.wicket.markup.repeater.RefreshingView
* @see org.apache.wicket.markup.html.navigation.paging.IPageable
*
* @author Igor Vaynberg (ivaynberg)
*
* @param <T>
* type of elements contained in the model's list
*/
public abstract class AbstractPageableView<T> extends RefreshingView<T> implements IPageableItems
{
/** */
private static final long serialVersionUID = 1L;
/**
* Keeps track of the number of items we show per page. The default is Integer.MAX_VALUE which
* effectively disables paging.
*/
private long itemsPerPage = Long.MAX_VALUE;
/**
* Keeps track of the current page number.
*/
private long currentPage;
/**
* <code>cachedItemCount</code> is used to cache the call to <code>internalGetItemCount()</code>
* for the duration of the request because that call can potentially be expensive ( a select
* count query ) and so we do not want to execute it multiple times.
*/
private transient long cachedItemCount;
/**
* Constructor
*
* @param id
* @param model
* @see org.apache.wicket.Component#Component(String, IModel)
*/
public AbstractPageableView(String id, IModel<? extends Collection<? extends T>> model)
{
super(id, model);
clearCachedItemCount();
}
/** @see org.apache.wicket.Component#Component(String) */
public AbstractPageableView(String id)
{
super(id);
clearCachedItemCount();
}
/**
* This method retrieves the subset of models for items in the current page and allows
* RefreshingView to generate items.
*
* @return iterator over models for items in the current page
*/
@Override
protected Iterator<IModel<T>> getItemModels()
{
long offset = getFirstItemOffset();
long size = getViewSize();
Iterator<IModel<T>> models = getItemModels(offset, size);
models = new CappedIteratorAdapter<T>(models, size);
return models;
}
/**
* @see org.apache.wicket.markup.repeater.AbstractRepeater#onBeforeRender()
*/
@Override
protected void onBeforeRender()
{
clearCachedItemCount();
super.onBeforeRender();
}
/**
* Returns an iterator over models for items in the current page
*
* @param offset
* index of first item in this page
* @param size
* number of items that will be shown in the current page
* @return an iterator over models for items in the current page
*/
protected abstract Iterator<IModel<T>> getItemModels(long offset, long size);
// /////////////////////////////////////////////////////////////////////////
// ITEM COUNT CACHE
// /////////////////////////////////////////////////////////////////////////
private void clearCachedItemCount()
{
cachedItemCount = -1;
}
private void setCachedItemCount(long itemCount)
{
cachedItemCount = itemCount;
}
private long getCachedItemCount()
{
if (cachedItemCount < 0)
{
throw new IllegalStateException("getItemCountCache() called when cache was not set");
}
return cachedItemCount;
}
private boolean isItemCountCached()
{
return cachedItemCount >= 0;
}
// /////////////////////////////////////////////////////////////////////////
// PAGING
// /////////////////////////////////////////////////////////////////////////
/**
* @return maximum number of items that will be shown per page
*/
@Override
public long getItemsPerPage()
{
return itemsPerPage;
}
/**
* Sets the maximum number of items to show per page. The current page will also be set to zero
*
* @param items
*/
public final void setItemsPerPage(long items)
{
if (items < 1)
{
throw new IllegalArgumentException("Argument [itemsPerPage] cannot be less than 1");
}
if (itemsPerPage != items)
{
if (isVersioned())
{
addStateChange();
}
}
itemsPerPage = items;
// because items per page can effect the total number of pages we always
// reset the current page back to zero
setCurrentPage(0);
}
/**
* @return total item count
*/
protected abstract long internalGetItemCount();
/**
* Get the row count.
*
* @see #getItemCount()
*
* @return total item count, but 0 if not visible in the hierarchy
*/
public final long getRowCount()
{
if (!isVisibleInHierarchy())
{
return 0;
}
return getItemCount();
}
/**
* Get the item count. Since dataprovider.size() could potentially be expensive, the item count
* is cached.
*
* @see #getRowCount()
*
* @return the item count
*/
@Override
public final long getItemCount()
{
if (isItemCountCached())
{
return getCachedItemCount();
}
long count = internalGetItemCount();
setCachedItemCount(count);
return count;
}
/**
* @see org.apache.wicket.markup.html.navigation.paging.IPageable#getCurrentPage()
*/
@Override
public final long getCurrentPage()
{
long page = currentPage;
/*
* trim current page if its out of bounds this can happen if items are added/deleted between
* requests
*/
if (page > 0 && page >= getPageCount())
{
page = Math.max(getPageCount() - 1, 0);
currentPage = page;
return page;
}
return page;
}
/**
* @see org.apache.wicket.markup.html.navigation.paging.IPageable#setCurrentPage(long)
*/
@Override
public final void setCurrentPage(long page)
{
if (currentPage != page)
{
if (isVersioned())
{
addStateChange();
}
}
currentPage = page;
}
/**
* @see org.apache.wicket.markup.html.navigation.paging.IPageable#getPageCount()
*/
@Override
public long getPageCount()
{
long total = getRowCount();
long itemsPerPage = getItemsPerPage();
long count = total / itemsPerPage;
if (itemsPerPage * count < total)
{
count++;
}
return count;
}
/**
* @return the index of the first visible item in the view
*/
public long getFirstItemOffset()
{
return getCurrentPage() * getItemsPerPage();
}
/**
* @return the number of items visible
*/
public long getViewSize()
{
return Math.min(getItemsPerPage(), getRowCount() - getFirstItemOffset());
}
// /////////////////////////////////////////////////////////////////////////
// HELPER CLASSES
// /////////////////////////////////////////////////////////////////////////
/**
* Iterator adapter that makes sure only the specified max number of items can be accessed from
* its delegate.
*
* @param <T>
* Model object type
*/
private static class CappedIteratorAdapter<T> implements Iterator<IModel<T>>
{
private final long max;
private long index;
private final Iterator<IModel<T>> delegate;
/**
* Constructor
*
* @param delegate
* delegate iterator
* @param max
* maximum number of items that can be accessed.
*/
public CappedIteratorAdapter(Iterator<IModel<T>> delegate, long max)
{
this.delegate = delegate;
this.max = max;
}
/**
* @see java.util.Iterator#remove()
*/
@Override
public void remove()
{
throw new UnsupportedOperationException();
}
/**
* @see java.util.Iterator#hasNext()
*/
@Override
public boolean hasNext()
{
return (index < max) && delegate.hasNext();
}
/**
* @see java.util.Iterator#next()
*/
@Override
public IModel<T> next()
{
if (index >= max)
{
throw new NoSuchElementException();
}
index++;
return delegate.next();
}
}
/**
* @see org.apache.wicket.Component#onDetach()
*/
@Override
protected void onDetach()
{
clearCachedItemCount();
super.onDetach();
}
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException,
ClassNotFoundException
{
// Read in all fields
s.defaultReadObject();
clearCachedItemCount();
}
}