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
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
Thursday, March 17, 2005
migrating from WebLogic in Linux to OC4J in XP
A couple of weeks ago I was trying to resurrect a 3 yr-old project, implemented using WebLogic 6 in Linux, into a working web application using OC4J 10gAS in Windows XP. Here are some notes I made during the process, which might help anyone doing or planning to do the same:
1. Do not rely on vendor specific deployment descriptor. If you have to, use the same name everywhere. Back in 2001 when EJB was still a new thing, WebLogic offers weblogic-ejb-jar.xml as a vendor specific deployment descriptor, in combination with the default ejb-jar.xml. The mapping between the names in the default ejb-jar.xml and the weblogic-ejb-jar.xml differed in our implementation. This has caused a great deal of problem for me migrating it, because OC4J does not read the weblogic-ejb-jar.xml.
2. OC4J requires adding "java:comp/env" prefix when performing a JNDI lookup to find the instance of local ejb. This certainly is different from the WebLogic implementation.
3. There was a bug in OC4J 9.0.3, following are the extract information from their official site:
If you guys have any other experience in migrating J2EE application from one application server in one platform to another application server in another platform, please collaborate..
I'd really like to know.. :D
Further Reading:
J2EE Performance Testing with BEA WebLogic Server
Oracle Application Server 10g Web Development
1. Do not rely on vendor specific deployment descriptor. If you have to, use the same name everywhere. Back in 2001 when EJB was still a new thing, WebLogic offers weblogic-ejb-jar.xml as a vendor specific deployment descriptor, in combination with the default ejb-jar.xml. The mapping between the names in the default ejb-jar.xml and the weblogic-ejb-jar.xml differed in our implementation. This has caused a great deal of problem for me migrating it, because OC4J does not read the weblogic-ejb-jar.xml.
2. OC4J requires adding "java:comp/env" prefix when performing a JNDI lookup to find the instance of local ejb. This certainly is different from the WebLogic implementation.
3. There was a bug in OC4J 9.0.3, following are the extract information from their official site:
Using <jsp:include page="xxx.html"> with html files greater than 20K causes an exception. If the included HTML file size is > 20k you will encounter java.lang.StackOverflowError. Currently, there is no workaround, other than to keep the file size < 20KB. The bug is being fixed and will be part of next update.This certainly caused me trouble, since the application used a lot of <jsp:include> where one or more of them may be of 20 KB size or more. The only solution was to upgrade to OC4J 10.x.
If you guys have any other experience in migrating J2EE application from one application server in one platform to another application server in another platform, please collaborate..
I'd really like to know.. :D
Further Reading:
J2EE Performance Testing with BEA WebLogic Server
Oracle Application Server 10g Web Development
Wednesday, March 16, 2005
using JSPWiki
Over the last two months, I've been busy doing many improvements within the company, with one of them is creating a company Wiki to store our lessons learned through the years. After examining through a number of Java Wiki Engine, my final decision was to go with JSPWiki. Compared against XWiki, it's much easier to install and to maintain. It's very light, requires no RDBMS, and almost anyone could immediately be productive.
Some notes about JSPWiki:
1. It's very fast to install, 5 mins.
2. It's very easy to use & customize. I'm currently looking for a better template, other than the default one and the contributed "red-man" template (created by Matt Raible).
3. Currently I'm figuring how I can add user authentication and authorization easily to the Wiki (without having to code and all).
Overall, it's a nice product.
Can't wait to explore more about its true potential.
Further Reading:
The Wiki Way: Collaboration and Sharing on the Internet
Some notes about JSPWiki:
1. It's very fast to install, 5 mins.
2. It's very easy to use & customize. I'm currently looking for a better template, other than the default one and the contributed "red-man" template (created by Matt Raible).
3. Currently I'm figuring how I can add user authentication and authorization easily to the Wiki (without having to code and all).
Overall, it's a nice product.
Can't wait to explore more about its true potential.
Further Reading:
The Wiki Way: Collaboration and Sharing on the Internet
Subscribe to:
Posts (Atom)