diff --git a/source/java/org/alfresco/repo/web/util/PagingCursor.java b/source/java/org/alfresco/repo/web/util/PagingCursor.java new file mode 100644 index 0000000000..d6d6f771b5 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/PagingCursor.java @@ -0,0 +1,454 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util; + +/** + * Paging cursor. A utility for maintaining paged indexes for a collection of N items. + * + * There are two types of cursor: + * + * a) Paged + * + * This type of cursor is driven from a page number and page size. Random access within + * the collection is possible by jumping straight to a page. A simple scroll through + * the collection is supported by iterating through each next page. + * + * b) Rows + * + * This type of cursor is driven from a skip row count and maximum number of rows. Random + * access is not supported. The collection of items is simply scrolled through from + * start to end by iterating through each next set of rows. + * + * In either case, a paging cursor provides a start row and end row which may be used + * to extract the items for the page from the collection of N items. + * + * A zero (or less) page size or row maximum means "unlimited". + * + * Zero or one based Page and Rows indexes are supported. By default, Pages are 1 based and + * Rows are 0 based. + * + * At any time, -1 is returned to represent "out of range" i.e. for next, previous, last page + * and next skip count. + * + * Pseudo-code for traversing through a collection of N items (10 at a time): + * + * PagingCursor cursor = new PagingCursor(); + * Page page = cursor.createPageCursor(N, 10, 1); + * while (page.isInRange()) + * { + * for (long i = page.getStartRow(); i <= page.getEndRow(); i++) + * { + * ...collection[i]... + * } + * page = cursor.createPageCursor(N, 10, page.getNextPage()); + * } + * + * Rows rows = cursor.createRowsCursor(N, 10, 0); + * while (rows.isInRange()) + * { + * for (long i = page.getStartRow(); i <= page.getEndRow(); i++) + * { + * ...collection[i]... + * } + * rows = cursor.createRowsCursor(N, 10, rows.getNextSkipRows()); + * } + * + * @author davidc + */ +public class PagingCursor +{ + boolean zeroBasedPage = false; + boolean zeroBasedRow = true; + + /** + * Sets zero based page index + * + * Note: scoped to this paging cursor instance + * + * @param zeroBasedPage true => 0 based, false => 1 based + */ + public void setZeroBasedPage(boolean zeroBasedPage) + { + this.zeroBasedPage = zeroBasedPage; + } + + /** + * Is zero based page index? + * + * Note: scoped to this paging cursor instance + * + * @return true => 0 based, false => 1 based + */ + public boolean isZeroBasedPage() + { + return zeroBasedPage; + } + + /** + * Sets zero based row index + * + * Note: scoped to this paging cursor instance + * + * @param zeroBasedRow true => 0 based, false => 1 based + */ + public void setZeroBasedRow(boolean zeroBasedRow) + { + this.zeroBasedRow = zeroBasedRow; + } + + /** + * Is zero based row index? + * + * Note: scoped to this paging cursor instance + * + * @return true => 0 based, false => 1 based + */ + public boolean isZeroBasedRow() + { + return zeroBasedRow; + } + + /** + * Create a Page based Cursor + * + * @param totalRows total rows in collection + * @param rowsPerPage page size + * @param page page number (0 or 1 based) + * @return Page Cursor + */ + public Page createPageCursor(long totalRows, int rowsPerPage, int page) + { + return new Page(totalRows, rowsPerPage, page, zeroBasedPage, zeroBasedRow); + } + + /** + * Create a Page based Cursor + * + * @param totalRows total rows in collection + * @param rowsPerPage page size + * @param page page number (0 or 1 based) + * @param zeroBasedPage true => 0 based, false => 1 based + * @param zeroBasedRow true => 0 based, false => 1 based + * @return Page Cursor + */ + public Page createPageCursor(long totalRows, int rowsPerPage, int page, boolean zeroBasedPage, boolean zeroBasedRow) + { + return new Page(totalRows, rowsPerPage, page, zeroBasedPage, zeroBasedRow); + } + + /** + * Create a Rows based Cursor + * + * @param totalRows total rows in collection + * @param maxRows maximum number of rows in page + * @param skipRows number of rows to skip (0 - none) + * @return Rows Cursor + */ + public Rows createRowsCursor(long totalRows, long maxRows, long skipRows) + { + return new Rows(totalRows, maxRows, skipRows, zeroBasedRow); + } + + /** + * Create a Rows based Cursor + * + * @param totalRows total rows in collection + * @param maxRows maximum number of rows in page + * @param skipRows number of rows to skip (0 - none) + * @param zeroBasedRow true => 0 based, false => 1 based + * @return Rows Cursor + */ + public Rows createRowsCursor(long totalRows, long maxRows, long skipRows, boolean zeroBasedRow) + { + return new Rows(totalRows, maxRows, skipRows, zeroBasedRow); + } + + + /** + * Page based Cursor + */ + public static class Page + { + boolean zeroBasedPage; + boolean zeroBasedRow; + long totalRows; + int rowsPerPage; + long pageSize; + int currentPage; + int currentRow; + + /** + * Create a Page based Cursor + * + * @param totalRows total rows in collection + * @param rowsPerPage page size + * @param page page number (0 or 1 based) + * @param zeroBasedPage true => 0 based, false => 1 based + * @param zeroBasedRow true => 0 based, false => 1 based + */ + public Page(long totalRows, int rowsPerPage, int page, boolean zeroBasedPage, boolean zeroBasedRow) + { + this.zeroBasedPage = zeroBasedPage; + this.zeroBasedRow = zeroBasedRow; + this.totalRows = totalRows; + this.rowsPerPage = rowsPerPage; + this.pageSize = (rowsPerPage <=0) ? totalRows : rowsPerPage; + this.currentPage = (zeroBasedPage) ? page : page - 1; + } + + /** + * Gets total rows + * + * @return total rows + */ + public long getTotalRows() + { + return totalRows; + } + + /** + * Gets total number of pages + * + * @return total number of pages + */ + public int getTotalPages() + { + if (totalRows == 0) + return 0; + + int totalPages = (int)(totalRows / pageSize); + totalPages += (totalRows % pageSize != 0) ? 1 : 0; + return totalPages; + } + + /** + * Gets page size + * + * @return page size + */ + public int getRowsPerPage() + { + return rowsPerPage; + } + + /** + * Is the cursor within range of the total number of rows + * + * @return true => within range of total rows + */ + public boolean isInRange() + { + return currentPage >= 0 && getCurrentPage() <= getLastPage(); + } + + /** + * Gets the current page number + * + * @return current page number + */ + public int getCurrentPage() + { + return currentPage + (zeroBasedPage ? 0 : 1); + } + + /** + * Gets the next page number + * + * @return next page number (-1 if no more pages) + */ + public int getNextPage() + { + return getCurrentPage() < getLastPage() ? getCurrentPage() + 1 : - 1; + } + + /** + * Gets the previous page number + * + * @return previous page number (-1 if no previous pages) + */ + public int getPreviousPage() + { + return currentPage > 0 ? getCurrentPage() - 1 : - 1; + } + + /** + * Gets the first page number + * + * @return first page number + */ + public int getFirstPage() + { + if (totalRows == 0) + return -1; + + return zeroBasedPage ? 0 : 1; + } + + /** + * Gets the last page number + * + * @return last page number + */ + public int getLastPage() + { + if (totalRows == 0) + return -1; + + return getTotalPages() - (zeroBasedPage ? 1 : 0); + } + + /** + * Gets the start row within collection for this page + * + * @return start row index + */ + public long getStartRow() + { + if (totalRows == 0) + return -1; + + return (currentPage * pageSize) + (zeroBasedRow ? 0 : 1); + } + + /** + * Gets the end row within collection for this page + * + * @return end row index + */ + public long getEndRow() + { + if (totalRows == 0) + return -1; + + return getStartRow() + Math.min(pageSize, totalRows - (currentPage * pageSize)) - 1; + } + } + + /** + * Rows based Cursor + */ + public static class Rows + { + boolean zeroBasedRow; + long totalRows; + long skipRows; + long maxRows; + long pageSize; + + /** + * Create a Rows based Cursor + * + * @param totalRows total rows in collection + * @param maxRows maximum number of rows in page + * @param skipRows number of rows to skip (0 - none) + * @param zeroBasedRow true => 0 based, false => 1 based + */ + public Rows(long totalRows, long maxRows, long skipRows, boolean zeroBasedRow) + { + this.zeroBasedRow = zeroBasedRow; + this.totalRows = totalRows; + this.maxRows = maxRows; + this.skipRows = skipRows; + this.pageSize = (maxRows <= 0) ? totalRows - skipRows : maxRows; + } + + /** + * Gets the total number of rows + * + * @return total rows + */ + public long getTotalRows() + { + return totalRows; + } + + /** + * Gets the number rows skipped + * + * @return skipped row count + */ + public long getSkipRows() + { + return skipRows; + } + + /** + * Gets the maximum number of rows to include in this page + * + * @return maximum of numbers + */ + public long getMaxRows() + { + return maxRows; + } + + /** + * Is the cursor within range of the total number of rows + * + * @return true => within range of total rows + */ + public boolean isInRange() + { + return skipRows >= 0 && skipRows < totalRows; + } + + /** + * Gets the start row within collection for this page + * + * @return start row index + */ + public long getStartRow() + { + if (totalRows == 0) + return -1; + + return skipRows + (zeroBasedRow ? 0 : 1); + } + + /** + * Gets the end row within collection for this page + * + * @return end row index + */ + public long getEndRow() + { + if (totalRows == 0) + return -1; + + return getStartRow() + Math.min(pageSize, totalRows - skipRows) - 1; + } + + /** + * Gets the next skip count + * + * @return next skip row + */ + public long getNextSkipRows() + { + return (skipRows + pageSize < totalRows) ? skipRows + pageSize : -1; + } + } + +} diff --git a/source/java/org/alfresco/repo/web/util/PagingCursorTest.java b/source/java/org/alfresco/repo/web/util/PagingCursorTest.java new file mode 100644 index 0000000000..be0578ece8 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/PagingCursorTest.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util; + +import junit.framework.TestCase; + +import org.alfresco.repo.web.util.PagingCursor.Page; +import org.alfresco.repo.web.util.PagingCursor.Rows; + + +/** + * Test Paged and Row Based Cursors + * + * @author davidc + */ +public class PagingCursorTest extends TestCase +{ + protected PagingCursor pageCursor; + + @Override + protected void setUp() throws Exception + { + pageCursor = new PagingCursor(); + } + + public void testZeroBasedBooleans() + { + assertFalse(pageCursor.isZeroBasedPage()); + pageCursor.setZeroBasedPage(true); + assertTrue(pageCursor.isZeroBasedPage()); + assertTrue(pageCursor.isZeroBasedRow()); + pageCursor.setZeroBasedRow(false); + assertFalse(pageCursor.isZeroBasedRow()); + } + + public void testZeroRowsPageCursor() + { + Page page = pageCursor.createPageCursor(0, 10, 1); + assertNotNull(page); + assertEquals(0, page.getTotalRows()); + assertEquals(0, page.getTotalPages()); + assertEquals(10, page.getRowsPerPage()); + assertFalse(page.isInRange()); + assertEquals(1, page.getCurrentPage()); + assertEquals(-1, page.getFirstPage()); + assertEquals(-1, page.getLastPage()); + assertEquals(-1, page.getPreviousPage()); + assertEquals(-1, page.getNextPage()); + assertEquals(-1, page.getStartRow()); + assertEquals(-1, page.getEndRow()); + } + + public void testOutOfBoundsPageCursor() + { + Page page1 = pageCursor.createPageCursor(1, 1, 1); + assertNotNull(page1); + assertTrue(page1.isInRange()); + Page page2 = pageCursor.createPageCursor(1, 1, 2); + assertNotNull(page2); + assertFalse(page2.isInRange()); + Page page3 = pageCursor.createPageCursor(1, 2, 1); + assertNotNull(page3); + assertTrue(page3.isInRange()); + Page page4 = pageCursor.createPageCursor(10, 2, 5); + assertNotNull(page4); + assertTrue(page4.isInRange()); + Page page5 = pageCursor.createPageCursor(10, 2, 6); + assertNotNull(page5); + assertFalse(page5.isInRange()); + Page page6 = pageCursor.createPageCursor(11, 2, 5); + assertNotNull(page6); + assertTrue(page6.isInRange()); + Page page7 = pageCursor.createPageCursor(11, 2, 6); + assertNotNull(page7); + assertTrue(page7.isInRange()); + Page page8 = pageCursor.createPageCursor(11, 2, 0); + assertNotNull(page8); + assertFalse(page8.isInRange()); + + pageCursor.setZeroBasedPage(true); + Page page10 = pageCursor.createPageCursor(1, 1, 0); + assertNotNull(page10); + assertTrue(page10.isInRange()); + Page page11 = pageCursor.createPageCursor(1, 1, 1); + assertNotNull(page11); + assertFalse(page11.isInRange()); + Page page12 = pageCursor.createPageCursor(1, 2, 0); + assertNotNull(page12); + assertTrue(page12.isInRange()); + Page page13 = pageCursor.createPageCursor(10, 2, 4); + assertNotNull(page13); + assertTrue(page13.isInRange()); + Page page14 = pageCursor.createPageCursor(10, 2, 5); + assertNotNull(page14); + assertFalse(page14.isInRange()); + Page page15 = pageCursor.createPageCursor(11, 2, 4); + assertNotNull(page15); + assertTrue(page15.isInRange()); + Page page16 = pageCursor.createPageCursor(11, 2, 5); + assertNotNull(page16); + assertTrue(page16.isInRange()); + Page page17 = pageCursor.createPageCursor(11, 2, -1); + assertNotNull(page17); + assertFalse(page17.isInRange()); + } + + public void testTotalPageCursor() + { + Page page1 = pageCursor.createPageCursor(10, 1, 1); + assertEquals(10, page1.getTotalRows()); + assertEquals(10, page1.getTotalPages()); + Page page2 = pageCursor.createPageCursor(10, 10, 1); + assertEquals(10, page2.getTotalRows()); + assertEquals(1, page2.getTotalPages()); + Page page3 = pageCursor.createPageCursor(9, 10, 1); + assertEquals(9, page3.getTotalRows()); + assertEquals(1, page3.getTotalPages()); + Page page4 = pageCursor.createPageCursor(11, 10, 1); + assertEquals(11, page4.getTotalRows()); + assertEquals(2, page4.getTotalPages()); + Page page5 = pageCursor.createPageCursor(20, 10, 1); + assertEquals(20, page5.getTotalRows()); + assertEquals(2, page5.getTotalPages()); + } + + public void testPagingPageCursor() + { + Page page1 = pageCursor.createPageCursor(10, 1, 1); + assertEquals(1, page1.getCurrentPage()); + assertEquals(1, page1.getFirstPage()); + assertEquals(10, page1.getLastPage()); + assertEquals(-1, page1.getPreviousPage()); + assertEquals(2, page1.getNextPage()); + assertEquals(0, page1.getStartRow()); + assertEquals(0, page1.getEndRow()); + Page page2 = pageCursor.createPageCursor(10, 1, 2); + assertEquals(2, page2.getCurrentPage()); + assertEquals(1, page2.getFirstPage()); + assertEquals(10, page2.getLastPage()); + assertEquals(1, page2.getPreviousPage()); + assertEquals(3, page2.getNextPage()); + assertEquals(1, page2.getStartRow()); + assertEquals(1, page2.getEndRow()); + Page page3 = pageCursor.createPageCursor(10, 10, 1); + assertEquals(1, page3.getCurrentPage()); + assertEquals(1, page3.getFirstPage()); + assertEquals(1, page3.getLastPage()); + assertEquals(-1, page3.getPreviousPage()); + assertEquals(-1, page3.getNextPage()); + assertEquals(0, page3.getStartRow()); + assertEquals(9, page3.getEndRow()); + Page page4 = pageCursor.createPageCursor(9, 10, 1); + assertEquals(1, page4.getCurrentPage()); + assertEquals(1, page4.getFirstPage()); + assertEquals(1, page4.getLastPage()); + assertEquals(-1, page4.getPreviousPage()); + assertEquals(-1, page4.getNextPage()); + assertEquals(0, page4.getStartRow()); + assertEquals(8, page4.getEndRow()); + Page page5 = pageCursor.createPageCursor(11, 10, 1); + assertEquals(1, page5.getCurrentPage()); + assertEquals(1, page5.getFirstPage()); + assertEquals(2, page5.getLastPage()); + assertEquals(-1, page5.getPreviousPage()); + assertEquals(2, page5.getNextPage()); + assertEquals(0, page5.getStartRow()); + assertEquals(9, page5.getEndRow()); + Page page6 = pageCursor.createPageCursor(20, 10, 1); + assertEquals(1, page6.getCurrentPage()); + assertEquals(1, page6.getFirstPage()); + assertEquals(2, page6.getLastPage()); + assertEquals(-1, page6.getPreviousPage()); + assertEquals(2, page6.getNextPage()); + assertEquals(0, page6.getStartRow()); + assertEquals(9, page6.getEndRow()); + Page page7 = pageCursor.createPageCursor(20, 10, 2); + assertEquals(2, page7.getCurrentPage()); + assertEquals(1, page7.getFirstPage()); + assertEquals(2, page7.getLastPage()); + assertEquals(1, page7.getPreviousPage()); + assertEquals(-1, page7.getNextPage()); + assertEquals(10, page7.getStartRow()); + assertEquals(19, page7.getEndRow()); + Page page8 = pageCursor.createPageCursor(11, 10, 2); + assertEquals(2, page8.getCurrentPage()); + assertEquals(1, page8.getFirstPage()); + assertEquals(2, page8.getLastPage()); + assertEquals(1, page8.getPreviousPage()); + assertEquals(-1, page8.getNextPage()); + assertEquals(10, page8.getStartRow()); + assertEquals(10, page8.getEndRow()); + } + + public void testUnlimitedPageCursor() + { + Page page1 = pageCursor.createPageCursor(100, 0, 1); + assertTrue(page1.isInRange()); + assertEquals(1, page1.getCurrentPage()); + assertEquals(1, page1.getFirstPage()); + assertEquals(1, page1.getLastPage()); + assertEquals(-1, page1.getPreviousPage()); + assertEquals(-1, page1.getNextPage()); + assertEquals(0, page1.getStartRow()); + assertEquals(99, page1.getEndRow()); + Page page2 = pageCursor.createPageCursor(100, 0, 2); + assertFalse(page2.isInRange()); + } + + public void testScrollPageCursor() + { + int count = 0; + long[] coll = new long[100]; + + Page page = pageCursor.createPageCursor(100, 10, 1); + while (page.isInRange()) + { + for (long i = page.getStartRow(); i <= page.getEndRow(); i++) + { + coll[(int)i] = i; + count++; + } + page = pageCursor.createPageCursor(100, 10, page.getNextPage()); + } + + assertEquals(100, count); + for (int test = 0; test < count; test++) + { + assertEquals(test, coll[test]); + } + } + + public void testZeroRowsIndexCursor() + { + Rows rows = pageCursor.createRowsCursor(0, 10, 0); + assertNotNull(rows); + assertEquals(0, rows.getTotalRows()); + assertFalse(rows.isInRange()); + assertEquals(10, rows.getMaxRows()); + assertEquals(-1, rows.getStartRow()); + assertEquals(-1, rows.getEndRow()); + assertEquals(-1, rows.getNextSkipRows()); + } + + public void testOutOfBoundsRowsCursor() + { + Rows rows1 = pageCursor.createRowsCursor(1, 1, 0); + assertNotNull(rows1); + assertTrue(rows1.isInRange()); + Rows rows2 = pageCursor.createRowsCursor(1, 1, 1); + assertNotNull(rows2); + assertFalse(rows2.isInRange()); + Rows rows3 = pageCursor.createRowsCursor(1, -1, 0); + assertNotNull(rows3); + assertTrue(rows3.isInRange()); + } + + public void testTotalRowsCursor() + { + Rows rows1 = pageCursor.createRowsCursor(10, 1, 1); + assertEquals(10, rows1.getTotalRows()); + } + + public void testPagingRowsCursor() + { + Rows rows1 = pageCursor.createRowsCursor(10, 1, 0); + assertEquals(0, rows1.getStartRow()); + assertEquals(0, rows1.getEndRow()); + assertEquals(1, rows1.getNextSkipRows()); + Rows rows2 = pageCursor.createRowsCursor(10, 7, 0); + assertEquals(0, rows2.getStartRow()); + assertEquals(6, rows2.getEndRow()); + assertEquals(7, rows2.getNextSkipRows()); + Rows rows3 = pageCursor.createRowsCursor(10, 7, 7); + assertEquals(7, rows3.getStartRow()); + assertEquals(9, rows3.getEndRow()); + assertEquals(-1, rows3.getNextSkipRows()); + Rows rows4 = pageCursor.createRowsCursor(10, 10, 0); + assertEquals(0, rows4.getStartRow()); + assertEquals(9, rows4.getEndRow()); + assertEquals(-1, rows4.getNextSkipRows()); + Rows rows5 = pageCursor.createRowsCursor(10, 11, 0); + assertEquals(0, rows5.getStartRow()); + assertEquals(9, rows5.getEndRow()); + assertEquals(-1, rows5.getNextSkipRows()); + } + + public void testUnlimitedRowsCursor() + { + Rows rows1 = pageCursor.createRowsCursor(100, 0, 0); + assertTrue(rows1.isInRange()); + assertEquals(0, rows1.getStartRow()); + assertEquals(99, rows1.getEndRow()); + assertEquals(-1, rows1.getNextSkipRows()); + } + + public void testScrollRowsCursor() + { + int count = 0; + long[] coll = new long[100]; + + Rows rows = pageCursor.createRowsCursor(100, 10, 0); + while (rows.isInRange()) + { + for (long i = rows.getStartRow(); i <= rows.getEndRow(); i++) + { + coll[(int)i] = i; + count++; + } + rows = pageCursor.createRowsCursor(100, 10, rows.getNextSkipRows()); + } + + assertEquals(100, count); + for (int test = 0; test < count; test++) + { + assertEquals(test, coll[test]); + } + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/ArrayPagedResults.java b/source/java/org/alfresco/repo/web/util/paging/ArrayPagedResults.java new file mode 100644 index 0000000000..094d67879c --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/ArrayPagedResults.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + +import java.io.Serializable; + + +/** + * Implementation of Paged Results based on an array result set. + * + * @author davidc + */ +public class ArrayPagedResults implements PagedResults, Serializable +{ + private static final long serialVersionUID = 5905699888354619269L; + + private Object[] results; + private Cursor cursor; + + + /** + * Construct + * + * @param results results for the page within cursor + * @param cursor the cursor + */ + /*Package*/ ArrayPagedResults(Object[] results, Cursor cursor) + { + this.results = results; + this.cursor = cursor; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.PagedResults#getResults() + */ + public Object[] getResults() + { + return results; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.PagedResults#getCursor() + */ + public Cursor getCursor() + { + return cursor; + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/Cursor.java b/source/java/org/alfresco/repo/web/util/paging/Cursor.java new file mode 100644 index 0000000000..74a6b40846 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/Cursor.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + + +/** + * Cursor - Allows for scrolling through a row set. + * + * @author davidc + */ +public interface Cursor +{ + /** + * Gets the page type + * + * @return page type + */ + public String getPageType(); + + /** + * Gets the page size + * + * @return page size + */ + int getPageSize(); + + /** + * Gets total number of pages + * + * @return total number of pages + */ + int getTotalPages(); + + /** + * Gets total rows + * + * @return total rows + */ + int getTotalRows(); + + /** + * Gets the current page number + * + * @return current page number + */ + int getCurrentPage(); + + /** + * Gets the first page number + * + * @return first page number + */ + int getFirstPage(); + + /** + * Gets the last page number + * + * @return last page number + */ + int getLastPage(); + + /** + * Gets the next page number + * + * @return next page number (-1 if no more pages) + */ + int getNextPage(); + + /** + * Gets the previous page number + * + * @return previous page number (-1 if no previous pages) + */ + int getPrevPage(); + + /** + * Is the page within range of the result set + * + * @return true => page is within range + */ + boolean isInRange(); + + /** + * Is there a known first page? + * + * @return true => getFirstPage() will succeed + */ + boolean getHasFirstPage(); + + /** + * Is there a known last page? + * + * @return true => getLastPage() will succeed + */ + boolean getHasLastPage(); + + /** + * Is there a known next page? + * + * @return true => getNextPage() will succeed + */ + boolean getHasNextPage(); + + /** + * Is there a known prev page? + * + * @return true => getPrevPage() will succeed + */ + boolean getHasPrevPage(); + + /** + * Gets the start row within result set for this page + * + * @return start row index + */ + int getStartRow(); + + /** + * Gets the end row within result set for this page + * + * @return end row index + */ + int getEndRow(); + + /** + * Gets the count of rows within result set for this page + * + * @return row count + */ + int getRowCount(); + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/Page.java b/source/java/org/alfresco/repo/web/util/paging/Page.java new file mode 100644 index 0000000000..ff468613c8 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/Page.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + + +/** + * A Page within a Cursor. + * + * @author davidc + */ +public class Page +{ + Paging.PageType pageType; + boolean zeroBasedIdx; + int startIdx; + int pageSize; + + /** + * Construct + * + * @param pageType Page or Window + * @param zeroBasedIdx true => start index from 0 + * @param startIdx start index + * @param pageSize page size + */ + /*package*/ Page(Paging.PageType pageType, boolean zeroBasedIdx, int startIdx, int pageSize) + { + this.pageType = pageType; + this.zeroBasedIdx = zeroBasedIdx; + this.startIdx = startIdx; + this.pageSize = pageSize; + } + + /** + * Gets the Page Type + * + * @return page type + */ + /*package*/ Paging.PageType getType() + { + return pageType; + } + + /** + * Gets the page number + * + * @return page number + */ + public int getNumber() + { + return startIdx; + } + + /** + * Gets the page size + * + * @return page size + */ + public int getSize() + { + return pageSize; + } + + /** + * Is zero based page index + * + * @return true => page number starts from zero + */ + public boolean isZeroBasedIdx() + { + return zeroBasedIdx; + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/PagedCursor.java b/source/java/org/alfresco/repo/web/util/paging/PagedCursor.java new file mode 100644 index 0000000000..4d025cb7f8 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/PagedCursor.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + +import java.io.Serializable; + +import org.alfresco.repo.web.util.paging.Paging.PageType; + + +/** + * Implementation of cursor based on notion of a Page. + * + * @author davidc + */ +public class PagedCursor implements Cursor, Serializable +{ + private static final long serialVersionUID = -1041155610387669590L; + + private boolean zeroBasedPage; + private boolean zeroBasedRow; + private int totalRows; + private int pageSize; + private int rowsPerPage; + private int page; + + + /** + * Construct + * + * @param zeroBasedRow true => row index starts at zero + * @param totalRows total number of rows (-1 for don't know) + * @param zeroBasedPage true => page number starts at zero + * @param page page number + * @param pageSize page size + */ + /*package*/ PagedCursor(boolean zeroBasedRow, int totalRows, boolean zeroBasedPage, int page, int pageSize) + { + this.zeroBasedRow = zeroBasedRow; + this.totalRows = totalRows; + this.zeroBasedPage = zeroBasedPage; + this.page = (zeroBasedPage) ? page : page - 1; + this.pageSize = pageSize; + this.rowsPerPage = (pageSize <=0) ? totalRows : pageSize; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPageType() + */ + public String getPageType() + { + return PageType.PAGE.toString(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPageSize() + */ + public int getPageSize() + { + return pageSize; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getTotalPages() + */ + public int getTotalPages() + { + if (totalRows <= 0) + return 0; + + int totalPages = (int)(totalRows / rowsPerPage); + totalPages += (totalRows % rowsPerPage != 0) ? 1 : 0; + return totalPages; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getTotalRows() + */ + public int getTotalRows() + { + return totalRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getCurrentPage() + */ + public int getCurrentPage() + { + return page + (zeroBasedPage ? 0 : 1); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getFirstPage() + */ + public int getFirstPage() + { + if (totalRows <= 0) + return -1; + + return zeroBasedPage ? 0 : 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getLastPage() + */ + public int getLastPage() + { + if (totalRows <= 0) + return -1; + + return getTotalPages() - (zeroBasedPage ? 1 : 0); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getNextPage() + */ + public int getNextPage() + { + return getCurrentPage() < getLastPage() ? getCurrentPage() + 1 : - 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPrevPage() + */ + public int getPrevPage() + { + return page > 0 ? getCurrentPage() - 1 : - 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#isInRange() + */ + public boolean isInRange() + { + return page >= 0 && getCurrentPage() <= getLastPage(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasFirstPage() + */ + public boolean getHasFirstPage() + { + return getFirstPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasLastPage() + */ + public boolean getHasLastPage() + { + return getLastPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasNextPage() + */ + public boolean getHasNextPage() + { + return getNextPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasPrevPage() + */ + public boolean getHasPrevPage() + { + return getPrevPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getStartRow() + */ + public int getStartRow() + { + if (totalRows <= 0) + return 0; + + return (page * rowsPerPage) + (zeroBasedRow ? 0 : 1); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getEndRow() + */ + public int getEndRow() + { + if (totalRows <= 0) + return -1; + + return getStartRow() + Math.min(rowsPerPage, totalRows - (page * rowsPerPage)) - 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getRowCount() + */ + public int getRowCount() + { + if (totalRows <= 0) + return 0; + + return getEndRow() - getStartRow() + 1; + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/PagedResults.java b/source/java/org/alfresco/repo/web/util/paging/PagedResults.java new file mode 100644 index 0000000000..b3fb7e6f93 --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/PagedResults.java @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + + +/** + * A Paged Result Set + * + * @author davidc + */ +public interface PagedResults +{ + /** + * An array of the results for the given page within the cursor + * @return the paged results + */ + Object[] getResults(); + + /** + * Gets the cursor + * + * @return cursor + */ + Cursor getCursor(); +} diff --git a/source/java/org/alfresco/repo/web/util/paging/Paging.java b/source/java/org/alfresco/repo/web/util/paging/Paging.java new file mode 100644 index 0000000000..a3bcaee02b --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/Paging.java @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + +/** + * Paging. A utility for maintaining paged indexes for a collection of N items. + * + * There are two types of cursor: + * + * a) Paged + * + * This type of cursor is driven from a page number and page size. Random access within + * the collection is possible by jumping straight to a page. A simple scroll through + * the collection is supported by iterating through each next page. + * + * b) Windowed + * + * This type of cursor is driven from a skip row count and maximum number of rows. Random + * access is not supported. The collection of items is simply scrolled through from + * start to end by iterating through each next set of rows. + * + * In either case, a paging cursor provides a start row and end row which may be used + * to extract the items for the page from the collection of N items. + * + * A zero (or less) page size or row maximum means "unlimited". + * + * Zero or one based Page and Rows indexes are supported. By default, Pages are 1 based and + * Rows are 0 based. + * + * At any time, -1 is returned to represent "out of range" i.e. for next, previous, last page. + * + * Pseudo-code for traversing through a collection of N items (10 at a time): + * + * Paging paging = new Paging(); + * Cursor page = paging.createCursor(N, paging.createPage(1, 10)); + * while (page.isInRange()) + * { + * for (long i = page.getStartRow(); i <= page.getEndRow(); i++) + * { + * ...collection[i]... + * } + * page = paging.createCursor(N, paging.createPage(page.getNextPage(), page.getPageSize()); + * } + * + * Cursor window = paging.createCursor(N, paging.createWindow(0, 10)); + * while (window.isInRange()) + * { + * for (long i = window.getStartRow(); i <= window.getEndRow(); i++) + * { + * ...collection[i]... + * } + * window = paging.createCursor(N, paging.createWindow(window.getNextPage(), window.getPageSize()); + * } + * + * @author davidc + */ +public class Paging +{ + public enum PageType + { + PAGE, + WINDOW + }; + + boolean zeroBasedPage = false; + boolean zeroBasedRow = true; + + /** + * Sets zero based page index + * + * Note: scoped to this paging cursor instance + * + * @param zeroBasedPage true => 0 based, false => 1 based + */ + public void setZeroBasedPage(boolean zeroBasedPage) + { + this.zeroBasedPage = zeroBasedPage; + } + + /** + * Is zero based page index? + * + * Note: scoped to this paging cursor instance + * + * @return true => 0 based, false => 1 based + */ + public boolean isZeroBasedPage() + { + return zeroBasedPage; + } + + /** + * Sets zero based row index + * + * Note: scoped to this paging cursor instance + * + * @param zeroBasedRow true => 0 based, false => 1 based + */ + public void setZeroBasedRow(boolean zeroBasedRow) + { + this.zeroBasedRow = zeroBasedRow; + } + + /** + * Is zero based row index? + * + * Note: scoped to this paging cursor instance + * + * @return true => 0 based, false => 1 based + */ + public boolean isZeroBasedRow() + { + return zeroBasedRow; + } + + /** + * Create a Page + * + * @param pageNumber page number + * @param pageSize page size + * @return the page + */ + public Page createPage(int pageNumber, int pageSize) + { + return new Page(PageType.PAGE, zeroBasedPage, pageNumber, pageSize); + } + + /** + * Create a Window + * @param skipRows number of rows to skip + * @param maxRows maximum number of rows in window + * @return the window + */ + public Page createWindow(int skipRows, int maxRows) + { + return new Page(PageType.WINDOW, zeroBasedRow, skipRows, maxRows); + } + + /** + * Create a Cursor + * + * @param totalRows total number of rows in cursor (< 0 for don't know) + * @param page the page / window within cursor + * @return the cursor + */ + public Cursor createCursor(int totalRows, Page page) + { + if (page.getType() == PageType.PAGE) + { + return new PagedCursor(zeroBasedRow, totalRows, page.zeroBasedIdx, page.startIdx, page.pageSize); + } + else if (page.getType() == PageType.WINDOW) + { + return new WindowedCursor(zeroBasedRow, totalRows, page.startIdx, page.pageSize); + } + return null; + } + + /** + * Create a Paged Result Set + * + * @param results the results for the page within the cursor + * @param cursor the cursor + * @return the paged result set + */ + public PagedResults createPagedResults(Object[] results, Cursor cursor) + { + return new ArrayPagedResults(results, cursor); + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/PagingTest.java b/source/java/org/alfresco/repo/web/util/paging/PagingTest.java new file mode 100644 index 0000000000..6efefb492b --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/PagingTest.java @@ -0,0 +1,371 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + +import junit.framework.TestCase; + + +/** + * Test Paged and Window Based Cursors + * + * @author davidc + */ +public class PagingTest extends TestCase +{ + protected Paging paging; + + @Override + protected void setUp() throws Exception + { + paging = new Paging(); + } + + public void testZeroBasedBooleans() + { + assertFalse(paging.isZeroBasedPage()); + paging.setZeroBasedPage(true); + assertTrue(paging.isZeroBasedPage()); + assertTrue(paging.isZeroBasedRow()); + paging.setZeroBasedRow(false); + assertFalse(paging.isZeroBasedRow()); + } + + public void testCreatePage() + { + Page page = paging.createPage(1, 10); + assertNotNull(page); + assertEquals(Paging.PageType.PAGE, page.getType()); + assertFalse(page.isZeroBasedIdx()); + assertEquals(1, page.getNumber()); + assertEquals(10, page.getSize()); + + Page window = paging.createWindow(1, 10); + assertNotNull(window); + assertEquals(Paging.PageType.WINDOW, window.getType()); + assertTrue(window.isZeroBasedIdx()); + assertEquals(1, window.getNumber()); + assertEquals(10, window.getSize()); + } + + public void testZeroRowsPage() + { + Cursor cursor = paging.createCursor(0, paging.createPage(1, 10)); + assertNotNull(cursor); + assertEquals(0, cursor.getTotalRows()); + assertEquals(0, cursor.getTotalPages()); + assertEquals(10, cursor.getPageSize()); + assertFalse(cursor.isInRange()); + assertEquals(1, cursor.getCurrentPage()); + assertEquals(-1, cursor.getFirstPage()); + assertEquals(-1, cursor.getLastPage()); + assertEquals(-1, cursor.getPrevPage()); + assertEquals(-1, cursor.getNextPage()); + assertEquals(0, cursor.getStartRow()); + assertEquals(-1, cursor.getEndRow()); + assertEquals(0, cursor.getRowCount()); + } + + public void testOutOfBoundsPage() + { + Cursor cursor1 = paging.createCursor(1, paging.createPage(1, 1)); + assertNotNull(cursor1); + assertTrue(cursor1.isInRange()); + Cursor cursor2 = paging.createCursor(1, paging.createPage(2, 1)); + assertNotNull(cursor2); + assertFalse(cursor2.isInRange()); + Cursor cursor3 = paging.createCursor(1, paging.createPage(1, 2)); + assertNotNull(cursor3); + assertTrue(cursor3.isInRange()); + Cursor cursor4 = paging.createCursor(10, paging.createPage(5, 2)); + assertNotNull(cursor4); + assertTrue(cursor4.isInRange()); + Cursor cursor5 = paging.createCursor(10, paging.createPage(6, 2)); + assertNotNull(cursor5); + assertFalse(cursor5.isInRange()); + Cursor cursor6 = paging.createCursor(11, paging.createPage(5, 2)); + assertNotNull(cursor6); + assertTrue(cursor6.isInRange()); + Cursor cursor7 = paging.createCursor(11, paging.createPage(6, 2)); + assertNotNull(cursor7); + assertTrue(cursor7.isInRange()); + Cursor cursor8 = paging.createCursor(11, paging.createPage(0, 2)); + assertNotNull(cursor8); + assertFalse(cursor8.isInRange()); + + paging.setZeroBasedPage(true); + Cursor cursor10 = paging.createCursor(1, paging.createPage(0, 1)); + assertNotNull(cursor10); + assertTrue(cursor10.isInRange()); + Cursor cursor11 = paging.createCursor(1, paging.createPage(1, 1)); + assertNotNull(cursor11); + assertFalse(cursor11.isInRange()); + Cursor cursor12 = paging.createCursor(1, paging.createPage(0, 2)); + assertNotNull(cursor12); + assertTrue(cursor12.isInRange()); + Cursor cursor13 = paging.createCursor(10, paging.createPage(4, 2)); + assertNotNull(cursor13); + assertTrue(cursor13.isInRange()); + Cursor cursor14 = paging.createCursor(10, paging.createPage(5, 2)); + assertNotNull(cursor14); + assertFalse(cursor14.isInRange()); + Cursor cursor15 = paging.createCursor(11, paging.createPage(4, 2)); + assertNotNull(cursor15); + assertTrue(cursor15.isInRange()); + Cursor cursor16 = paging.createCursor(11, paging.createPage(5, 2)); + assertNotNull(cursor16); + assertTrue(cursor16.isInRange()); + Cursor cursor17 = paging.createCursor(11, paging.createPage(-1, 2)); + assertNotNull(cursor17); + assertFalse(cursor17.isInRange()); + } + + public void testTotalPage() + { + Cursor cursor1 = paging.createCursor(10, paging.createPage(1, 1)); + assertEquals(10, cursor1.getTotalRows()); + assertEquals(10, cursor1.getTotalPages()); + Cursor cursor2 = paging.createCursor(10, paging.createPage(1, 10)); + assertEquals(10, cursor2.getTotalRows()); + assertEquals(1, cursor2.getTotalPages()); + Cursor cursor3 = paging.createCursor(9, paging.createPage(1, 10)); + assertEquals(9, cursor3.getTotalRows()); + assertEquals(1, cursor3.getTotalPages()); + Cursor cursor4 = paging.createCursor(11, paging.createPage(1, 10)); + assertEquals(11, cursor4.getTotalRows()); + assertEquals(2, cursor4.getTotalPages()); + Cursor cursor5 = paging.createCursor(20, paging.createPage(1, 10)); + assertEquals(20, cursor5.getTotalRows()); + assertEquals(2, cursor5.getTotalPages()); + } + + public void testCursorPage() + { + Cursor cursor1 = paging.createCursor(10, paging.createPage(1, 1)); + assertEquals(1, cursor1.getCurrentPage()); + assertEquals(1, cursor1.getFirstPage()); + assertEquals(10, cursor1.getLastPage()); + assertEquals(-1, cursor1.getPrevPage()); + assertEquals(2, cursor1.getNextPage()); + assertEquals(0, cursor1.getStartRow()); + assertEquals(0, cursor1.getEndRow()); + assertEquals(1, cursor1.getRowCount()); + Cursor cursor2 = paging.createCursor(10, paging.createPage(2, 1)); + assertEquals(2, cursor2.getCurrentPage()); + assertEquals(1, cursor2.getFirstPage()); + assertEquals(10, cursor2.getLastPage()); + assertEquals(1, cursor2.getPrevPage()); + assertEquals(3, cursor2.getNextPage()); + assertEquals(1, cursor2.getStartRow()); + assertEquals(1, cursor2.getEndRow()); + assertEquals(1, cursor2.getRowCount()); + Cursor cursor3 = paging.createCursor(10, paging.createPage(1, 10)); + assertEquals(1, cursor3.getCurrentPage()); + assertEquals(1, cursor3.getFirstPage()); + assertEquals(1, cursor3.getLastPage()); + assertEquals(-1, cursor3.getPrevPage()); + assertEquals(-1, cursor3.getNextPage()); + assertEquals(0, cursor3.getStartRow()); + assertEquals(9, cursor3.getEndRow()); + assertEquals(10, cursor3.getRowCount()); + Cursor cursor4 = paging.createCursor(9, paging.createPage(1, 10)); + assertEquals(1, cursor4.getCurrentPage()); + assertEquals(1, cursor4.getFirstPage()); + assertEquals(1, cursor4.getLastPage()); + assertEquals(-1, cursor4.getPrevPage()); + assertEquals(-1, cursor4.getNextPage()); + assertEquals(0, cursor4.getStartRow()); + assertEquals(8, cursor4.getEndRow()); + assertEquals(9, cursor4.getRowCount()); + Cursor cursor5 = paging.createCursor(11, paging.createPage(1, 10)); + assertEquals(1, cursor5.getCurrentPage()); + assertEquals(1, cursor5.getFirstPage()); + assertEquals(2, cursor5.getLastPage()); + assertEquals(-1, cursor5.getPrevPage()); + assertEquals(2, cursor5.getNextPage()); + assertEquals(0, cursor5.getStartRow()); + assertEquals(9, cursor5.getEndRow()); + assertEquals(10, cursor5.getRowCount()); + Cursor cursor6 = paging.createCursor(20, paging.createPage(1, 10)); + assertEquals(1, cursor6.getCurrentPage()); + assertEquals(1, cursor6.getFirstPage()); + assertEquals(2, cursor6.getLastPage()); + assertEquals(-1, cursor6.getPrevPage()); + assertEquals(2, cursor6.getNextPage()); + assertEquals(0, cursor6.getStartRow()); + assertEquals(9, cursor6.getEndRow()); + assertEquals(10, cursor6.getRowCount()); + Cursor cursor7 = paging.createCursor(20, paging.createPage(2, 10)); + assertEquals(2, cursor7.getCurrentPage()); + assertEquals(1, cursor7.getFirstPage()); + assertEquals(2, cursor7.getLastPage()); + assertEquals(1, cursor7.getPrevPage()); + assertEquals(-1, cursor7.getNextPage()); + assertEquals(10, cursor7.getStartRow()); + assertEquals(19, cursor7.getEndRow()); + assertEquals(10, cursor7.getRowCount()); + Cursor cursor8 = paging.createCursor(11, paging.createPage(2, 10)); + assertEquals(2, cursor8.getCurrentPage()); + assertEquals(1, cursor8.getFirstPage()); + assertEquals(2, cursor8.getLastPage()); + assertEquals(1, cursor8.getPrevPage()); + assertEquals(-1, cursor8.getNextPage()); + assertEquals(10, cursor8.getStartRow()); + assertEquals(10, cursor8.getEndRow()); + assertEquals(1, cursor8.getRowCount()); + } + + public void testUnlimitedPage() + { + Cursor cursor1 = paging.createCursor(100, paging.createPage(1, 0)); + assertTrue(cursor1.isInRange()); + assertEquals(1, cursor1.getCurrentPage()); + assertEquals(1, cursor1.getFirstPage()); + assertEquals(1, cursor1.getLastPage()); + assertEquals(-1, cursor1.getPrevPage()); + assertEquals(-1, cursor1.getNextPage()); + assertEquals(0, cursor1.getStartRow()); + assertEquals(99, cursor1.getEndRow()); + Cursor cursor2 = paging.createCursor(100, paging.createPage(2, 0)); + assertFalse(cursor2.isInRange()); + } + + public void testScrollPage() + { + int count = 0; + long[] coll = new long[100]; + + Cursor cursor = paging.createCursor(coll.length, paging.createPage(1, 10)); + while (cursor.isInRange()) + { + for (long i = cursor.getStartRow(); i <= cursor.getEndRow(); i++) + { + coll[(int)i] = i; + count++; + } + cursor = paging.createCursor(coll.length, paging.createPage(cursor.getNextPage(), cursor.getPageSize())); + } + + assertEquals(100, count); + for (int test = 0; test < count; test++) + { + assertEquals(test, coll[test]); + } + } + + public void testZeroRowsWindow() + { + Cursor rows = paging.createCursor(0, paging.createWindow(0, 10)); + assertNotNull(rows); + assertEquals(0, rows.getTotalRows()); + assertFalse(rows.isInRange()); + assertEquals(10, rows.getPageSize()); + assertEquals(0, rows.getStartRow()); + assertEquals(-1, rows.getEndRow()); + assertEquals(0, rows.getRowCount()); + assertEquals(-1, rows.getNextPage()); + } + + public void testOutOfBoundsWindow() + { + Cursor cursor1 = paging.createCursor(1, paging.createWindow(0, 1)); + assertNotNull(cursor1); + assertTrue(cursor1.isInRange()); + Cursor cursor2 = paging.createCursor(1, paging.createWindow(1, 1)); + assertNotNull(cursor2); + assertFalse(cursor2.isInRange()); + Cursor cursor3 = paging.createCursor(1, paging.createWindow(0, -1)); + assertNotNull(cursor3); + assertTrue(cursor3.isInRange()); + } + + public void testTotalWindow() + { + Cursor cursor1 = paging.createCursor(10, paging.createWindow(1, 1)); + assertEquals(10, cursor1.getTotalRows()); + } + + public void testCursorWindow() + { + Cursor cursor1 = paging.createCursor(10, paging.createWindow(0, 1)); + assertEquals(0, cursor1.getStartRow()); + assertEquals(0, cursor1.getEndRow()); + assertEquals(1, cursor1.getRowCount()); + assertEquals(1, cursor1.getNextPage()); + Cursor cursor2 = paging.createCursor(10, paging.createWindow(0, 7)); + assertEquals(0, cursor2.getStartRow()); + assertEquals(6, cursor2.getEndRow()); + assertEquals(7, cursor2.getRowCount()); + assertEquals(7, cursor2.getNextPage()); + Cursor cursor3 = paging.createCursor(10, paging.createWindow(7, 7)); + assertEquals(7, cursor3.getStartRow()); + assertEquals(9, cursor3.getEndRow()); + assertEquals(3, cursor3.getRowCount()); + assertEquals(-1, cursor3.getNextPage()); + Cursor cursor4 = paging.createCursor(10, paging.createWindow(0, 10)); + assertEquals(0, cursor4.getStartRow()); + assertEquals(9, cursor4.getEndRow()); + assertEquals(10, cursor4.getRowCount()); + assertEquals(-1, cursor4.getNextPage()); + Cursor cursor5 = paging.createCursor(10, paging.createWindow(0, 11)); + assertEquals(0, cursor5.getStartRow()); + assertEquals(9, cursor5.getEndRow()); + assertEquals(10, cursor5.getRowCount()); + assertEquals(-1, cursor5.getNextPage()); + } + + public void testUnlimitedWindow() + { + Cursor cursor1 = paging.createCursor(100, paging.createWindow(0, 0)); + assertTrue(cursor1.isInRange()); + assertEquals(0, cursor1.getStartRow()); + assertEquals(99, cursor1.getEndRow()); + assertEquals(100, cursor1.getRowCount()); + assertEquals(-1, cursor1.getNextPage()); + } + + public void testScrollWindow() + { + int count = 0; + long[] coll = new long[100]; + + Cursor cursor = paging.createCursor(coll.length, paging.createWindow(0, 10)); + while (cursor.isInRange()) + { + for (long i = cursor.getStartRow(); i <= cursor.getEndRow(); i++) + { + coll[(int)i] = i; + count++; + } + cursor = paging.createCursor(coll.length, paging.createWindow(cursor.getNextPage(), cursor.getPageSize())); + } + + assertEquals(100, count); + for (int test = 0; test < count; test++) + { + assertEquals(test, coll[test]); + } + } + +} diff --git a/source/java/org/alfresco/repo/web/util/paging/WindowedCursor.java b/source/java/org/alfresco/repo/web/util/paging/WindowedCursor.java new file mode 100644 index 0000000000..df59cd5c3d --- /dev/null +++ b/source/java/org/alfresco/repo/web/util/paging/WindowedCursor.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.util.paging; + +import java.io.Serializable; + +import org.alfresco.repo.web.util.paging.Paging.PageType; + + +/** + * Cursor implementation based on notion of a Window. + * + * @author davidc + */ +public class WindowedCursor implements Cursor, Serializable +{ + private static final long serialVersionUID = 521131539938276413L; + + private boolean zeroBasedRow; + private int totalRows; + private int skipRows; + private int maxRows; + private int rowsPerPage; + + /** + * Construct + * + * @param zeroBasedRow true => 0 based, false => 1 based + * @param totalRows total rows in collection + * @param skipRows number of rows to skip (0 - none) + * @param maxRows maximum number of rows in window + */ + WindowedCursor(boolean zeroBasedRow, int totalRows, int skipRows, int maxRows) + { + this.zeroBasedRow = zeroBasedRow; + this.totalRows = totalRows; + this.skipRows = skipRows; + this.maxRows = maxRows; + this.rowsPerPage = (maxRows <= 0) ? totalRows - skipRows : maxRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPageType() + */ + public String getPageType() + { + return PageType.WINDOW.toString(); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPageSize() + */ + public int getPageSize() + { + return maxRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getTotalPages() + */ + public int getTotalPages() + { + return -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getTotalRows() + */ + public int getTotalRows() + { + return totalRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getCurrentPage() + */ + public int getCurrentPage() + { + return skipRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getFirstPage() + */ + public int getFirstPage() + { + if (totalRows <=0) + return -1; + + return zeroBasedRow ? 0 : 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getLastPage() + */ + public int getLastPage() + { + return -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getNextPage() + */ + public int getNextPage() + { + return (skipRows + rowsPerPage < totalRows) ? skipRows + maxRows : -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getPrevPage() + */ + public int getPrevPage() + { + return (skipRows > 0) ? Math.max(0, skipRows - maxRows) : -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#isInRange() + */ + public boolean isInRange() + { + return skipRows >= 0 && skipRows < totalRows; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasFirstPage() + */ + public boolean getHasFirstPage() + { + return getFirstPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasLastPage() + */ + public boolean getHasLastPage() + { + return getLastPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasNextPage() + */ + public boolean getHasNextPage() + { + return getNextPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#hasPrevPage() + */ + public boolean getHasPrevPage() + { + return getPrevPage() != -1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getStartRow() + */ + public int getStartRow() + { + if (totalRows <= 0) + return 0; + + return skipRows + (zeroBasedRow ? 0 : 1); + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getEndRow() + */ + public int getEndRow() + { + if (totalRows <= 0) + return -1; + + return getStartRow() + Math.min(rowsPerPage, totalRows - skipRows) - 1; + } + + /* (non-Javadoc) + * @see org.alfresco.repo.web.util.paging.Cursor#getRowCount() + */ + public int getRowCount() + { + if (totalRows <= 0) + return 0; + + return getEndRow() - getStartRow() + 1; + } + +}