To answer Josh's comment on why the implementation of list pagination is quite long, here's a brief background. This post may be beneficial to Java/OOP newbie, in terms of thinking in an OOP and working in a TDD (Test Driven Development) way.
It's all started with the modeling of what a Page object may look like. Well, it has to have a page number and the contents (list of records) itself. Then, we need to define what can we do with a Paginating List. We need to be able to get the first page, the last page, the previous & next page based on the current Page object. They're all described within the Paginating interface.
Then, I added two more information inside the Page class, which can be provided easily by any implementation class of Paginating interface, i.e. the total number of page and the total number of records shown per page. The addition of this two properties to the Page class proves to be a very beneficial to the PaginatingImpl class itself.
Now, before we dive into why the PaginatingImpl looks like it is, one thing needs to be restated here. The PaginatingImpl is just one example of how we can implement the Paginating interface. You can provide your own (which can be a very different) implementation. The only requirement for the implementation is only to support all of the methods declared in the Paginating interface.
When developing the PaginatingImpl, one thing I keep in mind is that I wanted this class to be immutable and thread-safe. By being an immutable class, the PaginatingImpl itself almost automatically becomes thread-safe and therefore sharable & cache-able. Hence I say that this solution can be used for a web application or a non-web application.
The implementation code within the PaginatingImpl itself is straightforward and is mainly driven by the test code. If you haven't download the test class, then I suggest you download them here first. This is the key driver which drives the PaginatingImpl & Page classes to evolve into what they are currently.
For example, to ease the assertion codes within the PaginatingTest class, I added the equals() and hashCode() methods in the Page class. Then, after performing several test cases, I remembered that the ArrayList implementation does not override the equals() and hashCode() method from Object class. Therefore, I may get an inconsistent (and invalid) result when comparing two List objects which contain exactly the same sequence of the same elements.
Hence I added two more methods, i.e. isListEqual() and listHashCode() which are merely my implementation of the equals() and hashCode() method for the List class. (Note: BTW, to all the Java newbies reading this post, if you don't know why equals() and hashCode() need to be overridden correctly at the same time, please leave me a comment, I'll try to post the explanation for them. Or you can Google them or read in your own javadoc API or search it in Javaranch's SCJP Forum.)
The toString() method was added in the Page class merely to support easy debugging which I used exactly only once before sharing this code to you all. So the method definitely has its uses.
If you read carefully enough within the PaginatingImpl, you may find strange arithmetic operation, e.g. minus 1, or minus 2, or plus 1. Well, those are small logic but they're crucial to prevent bugs occuring from this rather small implementation of List Pagination.
If you want to test yourself, just take three of my classes, Page, Paginating & PaginatingTest, then develop your own PaginatingImpl class. It's a good challenge, and you may just develop a better implementation than mine, and enrich the PaginatingTest with many more test cases.
I sure would love to hear if anyone would want to take the challenge. It's a good start to all three elements of today's software development, i.e. Java, OOP & TDD. You need to download JUnit libraries though, but it should be no big deal.
Designing just another solution maybe is an easy task, but designing a good, robust & extensible one maybe is not such an easy task.. :D
Anyway, let me know what you guys think.
I sure hope that this post is even more useful when compared to my previous one.
Further Reading:
Test Driven Development by Example
Head First Design Patterns
Design Patterns
JUnit in Action
JUnit Recipes
trying to relive the glory days of blogging as a java developer living in java
Chitika
Showing posts with label list. Show all posts
Showing posts with label list. Show all posts
Thursday, March 31, 2005
Wednesday, March 30, 2005
List Pagination (Value List Holder)
Someone at our JUG Indonesia has a problem with displaying a List in a number of pages (aka List Pagination). This problem can be solved using the Value List Holder design pattern. I'm attaching my solution to the problem for him (or anyone who may found this useful), so that it'd be easier for him to have a look and discuss.
This solution can be used for both web application and non-web application.
Anyway, just put a comment if you have one.. :D
You can download the zipped source code (plus the test class) here.
import java.util.Iterator;
import java.util.List;
public class Page {
private int pageNum;
private int totalPage;
private int pagesize;
private List contents;
public Page (final int pageNum,
final int totalPage,
final int pagesize,
final List contents) {
this.pageNum = pageNum;
this.totalPage = totalPage;
this.pagesize = pagesize;
this.contents = contents;
}
public int getPageNum() {
return pageNum;
}
public int getTotalPage() {
return totalPage;
}
public int getPagesize() {
return pagesize;
}
public List getContents() {
return contents;
}
public boolean isFirstPage () {
return pageNum == 1;
}
public boolean isLastPage () {
return pageNum == totalPage;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Page)) return false;
final Page page = (Page) o;
if (pageNum != page.pageNum) return false;
if (pagesize != page.pagesize) return false;
if (totalPage != page.totalPage) return false;
if (contents != null ?
!isListEqual (contents, page.contents)
: page.contents != null)
return false;
return true;
}
public int hashCode() {
int result;
result = pageNum;
result = 29 * result + totalPage;
result = 29 * result + pagesize;
result = 29 * result + (contents != null ?
listHashCode (contents) : 0);
return result;
}
private boolean isListEqual (
final List a, final List b) {
if (a == b || a.equals(b)) return true;
final Iterator ia = a.iterator ();
final Iterator ib = b.iterator ();
while (ia.hasNext() && ib.hasNext()) {
final Object oa = ia.next();
final Object ob = ib.next();
if (!oa.equals(ob)) {
return false;
}
}
if (ia.hasNext() || ib.hasNext()) {
return false;
}
return true;
}
private int listHashCode (final List a) {
int result = 0;
for (Iterator iterator = a.iterator();
iterator.hasNext();) {
final Object o = iterator.next();
result = 29 * result + o.hashCode();
}
return result;
}
public String toString () {
final StringBuffer sb = new StringBuffer ();
sb.append ("Page ").append (pageNum)
.append (" of ").append (totalPage);
sb.append ("\n");
for (Iterator it = contents.iterator();
it.hasNext();) {
final Object o = it.next();
sb.append (o).append ("\n");
}
return sb.toString ();
}
}
public interface Paginating {
Page getFirstPage ();
Page getLastPage ();
Page getNextPage (Page currentPage);
Page getPrevPage (Page currentPage);
}
import java.util.List;
public class PaginatingImpl implements Paginating {
private List originalList;
private int pagesize;
private static final String INVALID_PAGESIZE =
"Pagesize must be a positive integer.";
public PaginatingImpl (final List originalList,
final int pagesize)
throws IllegalArgumentException {
if (pagesize <= 0)
throw new IllegalArgumentException (INVALID_PAGESIZE);
this.originalList = originalList;
this.pagesize = pagesize;
}
public Page getFirstPage () {
Page result = null;
if (originalList != null && originalList.size () > 0) {
result = new Page (1, getTotalPage(), pagesize,
iterateFrom (0));
}
return result;
}
public Page getLastPage () {
Page result = null;
if (originalList != null && originalList.size() > 0) {
final int totalPage = getTotalPage();
final int startIndex = (totalPage - 1) * pagesize;
result = new Page (totalPage, totalPage, pagesize,
iterateFrom (startIndex));
}
return result;
}
public Page getNextPage (final Page currentPage) {
if (currentPage == null) return getFirstPage ();
if (currentPage.isLastPage()) return currentPage;
Page result = null;
if (originalList != null) {
result = new Page (currentPage.getPageNum() + 1,
currentPage.getTotalPage(),
pagesize,
iterateFrom (currentPage.getPageNum() * pagesize));
}
return result;
}
public Page getPrevPage (final Page currentPage) {
if (currentPage == null) return getFirstPage ();
if (currentPage.isFirstPage()) return currentPage;
Page result = null;
if (originalList != null) {
result = new Page (currentPage.getPageNum() - 1,
currentPage.getTotalPage(),
pagesize,
iterateFrom ((currentPage.getPageNum() - 2) *
pagesize));
}
return result;
}
private List iterateFrom (final int startIndex) {
final int totalSize = originalList.size ();
int endIndex = startIndex + pagesize;
if (endIndex > totalSize) endIndex = totalSize;
return originalList.subList (startIndex, endIndex);
}
private int getTotalPage () {
if (originalList == null || originalList.size() <= 0)
return 0;
final int totalSize = originalList.size();
return ((totalSize - 1) / pagesize) + 1;
}
}
Further Reading:
Head First Design Patterns
Design Patterns
JUnit in Action
JUnit Recipes
This solution can be used for both web application and non-web application.
Anyway, just put a comment if you have one.. :D
You can download the zipped source code (plus the test class) here.
import java.util.Iterator;
import java.util.List;
public class Page {
private int pageNum;
private int totalPage;
private int pagesize;
private List contents;
public Page (final int pageNum,
final int totalPage,
final int pagesize,
final List contents) {
this.pageNum = pageNum;
this.totalPage = totalPage;
this.pagesize = pagesize;
this.contents = contents;
}
public int getPageNum() {
return pageNum;
}
public int getTotalPage() {
return totalPage;
}
public int getPagesize() {
return pagesize;
}
public List getContents() {
return contents;
}
public boolean isFirstPage () {
return pageNum == 1;
}
public boolean isLastPage () {
return pageNum == totalPage;
}
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Page)) return false;
final Page page = (Page) o;
if (pageNum != page.pageNum) return false;
if (pagesize != page.pagesize) return false;
if (totalPage != page.totalPage) return false;
if (contents != null ?
!isListEqual (contents, page.contents)
: page.contents != null)
return false;
return true;
}
public int hashCode() {
int result;
result = pageNum;
result = 29 * result + totalPage;
result = 29 * result + pagesize;
result = 29 * result + (contents != null ?
listHashCode (contents) : 0);
return result;
}
private boolean isListEqual (
final List a, final List b) {
if (a == b || a.equals(b)) return true;
final Iterator ia = a.iterator ();
final Iterator ib = b.iterator ();
while (ia.hasNext() && ib.hasNext()) {
final Object oa = ia.next();
final Object ob = ib.next();
if (!oa.equals(ob)) {
return false;
}
}
if (ia.hasNext() || ib.hasNext()) {
return false;
}
return true;
}
private int listHashCode (final List a) {
int result = 0;
for (Iterator iterator = a.iterator();
iterator.hasNext();) {
final Object o = iterator.next();
result = 29 * result + o.hashCode();
}
return result;
}
public String toString () {
final StringBuffer sb = new StringBuffer ();
sb.append ("Page ").append (pageNum)
.append (" of ").append (totalPage);
sb.append ("\n");
for (Iterator it = contents.iterator();
it.hasNext();) {
final Object o = it.next();
sb.append (o).append ("\n");
}
return sb.toString ();
}
}
public interface Paginating {
Page getFirstPage ();
Page getLastPage ();
Page getNextPage (Page currentPage);
Page getPrevPage (Page currentPage);
}
import java.util.List;
public class PaginatingImpl implements Paginating {
private List originalList;
private int pagesize;
private static final String INVALID_PAGESIZE =
"Pagesize must be a positive integer.";
public PaginatingImpl (final List originalList,
final int pagesize)
throws IllegalArgumentException {
if (pagesize <= 0)
throw new IllegalArgumentException (INVALID_PAGESIZE);
this.originalList = originalList;
this.pagesize = pagesize;
}
public Page getFirstPage () {
Page result = null;
if (originalList != null && originalList.size () > 0) {
result = new Page (1, getTotalPage(), pagesize,
iterateFrom (0));
}
return result;
}
public Page getLastPage () {
Page result = null;
if (originalList != null && originalList.size() > 0) {
final int totalPage = getTotalPage();
final int startIndex = (totalPage - 1) * pagesize;
result = new Page (totalPage, totalPage, pagesize,
iterateFrom (startIndex));
}
return result;
}
public Page getNextPage (final Page currentPage) {
if (currentPage == null) return getFirstPage ();
if (currentPage.isLastPage()) return currentPage;
Page result = null;
if (originalList != null) {
result = new Page (currentPage.getPageNum() + 1,
currentPage.getTotalPage(),
pagesize,
iterateFrom (currentPage.getPageNum() * pagesize));
}
return result;
}
public Page getPrevPage (final Page currentPage) {
if (currentPage == null) return getFirstPage ();
if (currentPage.isFirstPage()) return currentPage;
Page result = null;
if (originalList != null) {
result = new Page (currentPage.getPageNum() - 1,
currentPage.getTotalPage(),
pagesize,
iterateFrom ((currentPage.getPageNum() - 2) *
pagesize));
}
return result;
}
private List iterateFrom (final int startIndex) {
final int totalSize = originalList.size ();
int endIndex = startIndex + pagesize;
if (endIndex > totalSize) endIndex = totalSize;
return originalList.subList (startIndex, endIndex);
}
private int getTotalPage () {
if (originalList == null || originalList.size() <= 0)
return 0;
final int totalSize = originalList.size();
return ((totalSize - 1) / pagesize) + 1;
}
}
Further Reading:
Head First Design Patterns
Design Patterns
JUnit in Action
JUnit Recipes
Subscribe to:
Posts (Atom)