/* * 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 ); } }