/*
* Created on Nov 23, 2004
*
* This file is part of susimail project, see http://susi.i2p/
*
* Copyright (C) 2004-2005 <susi23@mail.i2p>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* $Revision: 1.2 $
*/
package i2p.susi.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Folder object manages a array Object[] to support
* paging and sorting.
*
* You create a folder object, set the contents with setElements(),
* add Comparators with addSorter(), choose one with sortBy() and
* and then fetch the content of the current page with
* currentPageIterator().
*
* All public methods are synchronized.
*
* @author susi
*/
public class Folder<O extends Object> {
public static final String PAGESIZE = "pager.pagesize";
public static final int DEFAULT_PAGESIZE = 10;
public enum SortOrder {
/** lowest to highest */
DOWN,
/** reverse sort, highest to lowest */
UP;
}
private int pages, pageSize, currentPage;
private O[] elements;
private final Map<String, Comparator<O>> sorter;
private SortOrder sortingDirection;
private Comparator<O> currentSorter;
private String currentSortID;
public Folder()
{
pages = 1;
currentPage = 1;
sorter = new HashMap<String, Comparator<O>>();
sortingDirection = SortOrder.DOWN;
}
/**
* Returns the current page.
* Starts at 1, even if empty.
*
* @return Returns the current page.
*/
public synchronized int getCurrentPage() {
return currentPage;
}
/**
* Sets the current page to the given parameter.
* Starts at 1.
*
* @param currentPage The current page to set.
*/
public synchronized void setCurrentPage(int currentPage) {
if( currentPage >= 1 && currentPage <= pages )
this.currentPage = currentPage;
}
/**
* Returns the size of the folder.
*
* @return Returns the size of the folder.
*/
public synchronized int getSize() {
return elements != null ? elements.length : 0;
}
/**
* Returns the number of pages in the folder.
* Minimum of 1 even if empty.
* @return Returns the number of pages.
*/
public synchronized int getPages() {
return pages;
}
/**
* Returns page size. If no page size has been set, it returns property @link PAGESIZE.
* If no property is set @link DEFAULT_PAGESIZE is returned.
*
* @return Returns the pageSize.
*/
public synchronized int getPageSize() {
return pageSize > 0 ? pageSize : Config.getProperty( PAGESIZE, DEFAULT_PAGESIZE );
}
/**
* Set page size.
*
* @param pageSize The page size to set.
*/
public synchronized void setPageSize(int pageSize) {
if( pageSize > 0 )
this.pageSize = pageSize;
update();
}
/**
* Recalculates variables.
*/
private void update() {
if( elements != null ) {
pages = elements.length / getPageSize();
if( pages * getPageSize() < elements.length )
pages++;
if( currentPage > pages )
currentPage = pages;
}
else {
pages = 1;
currentPage = 1;
}
}
/**
* Sorts the elements according the order given by @link addSorter()
* and @link sortBy().
*/
private void sort()
{
if( currentSorter != null )
Arrays.sort( elements, currentSorter );
}
/**
* Set the array of objects the folder should manage.
* Does NOT copy the array.
*
* @param elements Array of Os.
*/
public synchronized void setElements( O[] elements )
{
if (elements.length > 0) {
this.elements = elements;
if( currentSorter != null )
sort();
} else {
this.elements = null;
}
update();
}
/**
* Remove an element
*
* @param element to remove
*/
public void removeElement(O element) {
removeElements(Collections.singleton(element));
}
/**
* Remove elements
*
* @param elems to remove
*/
@SuppressWarnings("unchecked")
public synchronized void removeElements(Collection<O> elems) {
if (elements != null) {
List<O> list = new ArrayList<O>(Arrays.asList(elements));
boolean shouldUpdate = false;
for (O e : elems) {
if (list.remove(e))
shouldUpdate = true;
}
if (shouldUpdate) {
elements = (O[]) list.toArray(new Object[list.size()]);
update(); // will still be sorted
}
}
}
/**
* Add an element only if it does not already exist
*
* @param element to add
*/
public void addElement(O element) {
addElements(Collections.singleton(element));
}
/**
* Add elements only if it they do not already exist
*
* @param elems to adde
*/
@SuppressWarnings("unchecked")
public synchronized void addElements(Collection<O> elems) {
if (elements != null) {
List<O> list = new ArrayList<O>(Arrays.asList(elements));
boolean shouldUpdate = false;
for (O e : elems) {
if (!list.contains(e)) {
list.add(e);
shouldUpdate = true;
}
}
if (shouldUpdate) {
elements = (O[]) list.toArray(new Object[list.size()]);
sort();
update();
}
}
}
/**
* Returns an iterator containing the elements on the current page.
* This iterator is over a copy of the current page, and so
* is thread safe w.r.t. other operations on this folder,
* but will not reflect subsequent changes, and iter.remove()
* will not change the folder.
*
* @return Iterator containing the elements on the current page.
*/
public synchronized Iterator<O> currentPageIterator()
{
ArrayList<O> list = new ArrayList<O>();
if( elements != null ) {
int pageSize = getPageSize();
int offset = ( currentPage - 1 ) * pageSize;
for( int i = 0; i < pageSize && offset >= 0 && offset < elements.length; i++ ) {
list.add( elements[offset] );
offset++;
}
}
return list.iterator();
}
/**
* Turns folder to next page.
*/
public synchronized void nextPage()
{
currentPage++;
if( currentPage > pages )
currentPage = pages;
}
/**
* Turns folder to previous page.
*/
public synchronized void previousPage()
{
currentPage--;
if( currentPage < 1 )
currentPage = 1;
}
/**
* Sets folder to display first page.
*/
public synchronized void firstPage()
{
currentPage = 1;
}
/**
* Sets folder to display last page.
*/
public synchronized void lastPage()
{
currentPage = pages;
}
/**
* Adds a new sorter to the folder. You can sort the folder by
* calling sortBy() and choose the given id there.
*
* @param id ID to identify the Comparator with @link sortBy()
* @param sorter a Comparator to sort the Array given by @link setElements()
*/
public synchronized void addSorter( String id, Comparator<O> sorter )
{
this.sorter.put( id, sorter );
}
/**
* Activates sorting by the choosen Comparator. The id must
* match the one, which the Comparator has been stored in the
* folder with @link addSorter().
*
* @param id ID to identify the Comparator stored with @link addSorter()
*/
public synchronized void sortBy( String id )
{
currentSorter = sorter.get( id );
if (currentSorter != null) {
if (sortingDirection == SortOrder.UP)
currentSorter = Collections.reverseOrder(currentSorter);
currentSortID = id;
} else {
currentSortID = null;
}
}
/**
* @since 0.9.13
*/
public synchronized String getCurrentSortBy() {
return currentSortID;
}
/**
* @since 0.9.13
*/
public synchronized SortOrder getCurrentSortingDirection() {
return sortingDirection;
}
/**
* Returns the element on the current page on the given position.
*
* @param x Position of the element on the current page.
* @return Element on the current page on the given position.
*/
public synchronized O getElementAtPosXonCurrentPage( int x )
{
O result = null;
if( elements != null ) {
int pageSize = getPageSize();
int offset = ( currentPage - 1 ) * pageSize;
offset += x;
if( offset >= 0 && offset < elements.length )
result = elements[offset];
}
return result;
}
/**
* Sets the sorting direction of the folder.
* Does not re-sort. Caller must call sortBy()
*
* @param direction UP or DOWN
*/
public synchronized void setSortingDirection(SortOrder direction)
{
sortingDirection = direction;
}
/**
* Returns the first element of the sorted folder.
*
* @return First element.
*/
public synchronized O getFirstElement()
{
return elements == null ? null : getElement( 0 );
}
/**
* Returns the last element of the sorted folder.
*
* @return Last element.
*/
public synchronized O getLastElement()
{
return elements == null ? null : getElement( elements.length - 1 );
}
/**
* Gets index of an element in the array regardless of sorting direction.
*
* @param element
* @return index
*/
private int getIndexOf( O element )
{
if( elements != null ) {
for( int i = 0; i < elements.length; i++ )
if( elements[i].equals( element ) )
return i;
}
return -1;
}
/**
* Retrieves the next element in the sorted array.
*
* @param element
* @return The next element
*/
public synchronized O getNextElement( O element )
{
O result = null;
int i = getIndexOf( element );
if( i != -1 && elements != null ) {
i++;
if( i >= 0 && i < elements.length )
result = elements[i];
}
return result;
}
/**
* Retrieves the previous element in the sorted array.
*
* @param element
* @return The previous element
*/
public synchronized O getPreviousElement( O element )
{
O result = null;
int i = getIndexOf( element );
if( i != -1 && elements != null ) {
i--;
if( i >= 0 && i < elements.length )
result = elements[i];
}
return result;
}
/**
* Retrieves element at index i.
*
* @param i
* @return Element at index i
*/
private O getElement( int i )
{
O result = null;
if( elements != null ) {
result = elements[i];
}
return result;
}
/**
* Returns true, if folder shows points to the last page.
*/
public synchronized boolean isLastPage()
{
return currentPage == pages;
}
/**
* Returns true, if folder shows points to the first page.
*/
public synchronized boolean isFirstPage()
{
return currentPage == 1;
}
/**
* Returns true, if elements.equals( lastElementOfTheSortedArray ).
*
* @param element
*/
public synchronized boolean isLastElement( O element )
{
if( elements == null )
return false;
return elements[elements.length - 1].equals( element );
}
/**
* Returns true, if elements.equals( firstElementOfTheSortedArray ).
*
* @param element
*/
public synchronized boolean isFirstElement( O element )
{
if( elements == null )
return false;
return elements[0].equals( element );
}
}