- Using the Apache commons-lang package is the generally accepted way to do object comparisons (e.g. equals) in Sakai tools
- Adding the commons-lang dependency to your project.xml
- Add the following to the dependencies block of your project.xml file to include the commons-lang:
<dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> <version>${sakai.commons.lang.version}</version> </dependency>
- The commons-lang package is included in shared/lib by Sakai itself so you do not need to package it in your tool war file (hence there is no <war.bundle>true</war.bundle>)
- Do not specify a version, use the version that Sakai is placing in the shared/lib by using the property (sakai.commons.lang.version) as shown above
- Add the following to the dependencies block of your project.xml file to include the commons-lang:
- Using the lang functions in your object or object implementation code - Apache commons-lang API
Note: All code below should go in the object or object implementation- Add the required imports
import org.apache.commons.lang.builder.CompareToBuilder; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
- Create an equals function using the EqualsBuilder
public boolean equals(Object obj) { if (null == obj) return false; if (obj instanceof org.sakaiproject.tool.mytool.MyObject == false) { return false; } if (this == obj) { return true; } org.sakaiproject.tool.mytool.MyObject castObj = (org.sakaiproject.tool.mytool.MyObject) obj; return new EqualsBuilder() .append(this.getId(), castObj.getId()) .isEquals(); }
- This simple example only compares the id values of the 2 objects, however you could compare many values by doing something like this:
return new EqualsBuilder() .append(this.field1, castObj.getField1()) .append(this.field2, castObj.getField2()) .append(this.field3, castObj.getField3()) .isEquals();
- This simple example only compares the id values of the 2 objects, however you could compare many values by doing something like this:
- Create a hashCode function using the HashCodeBuilder
public int hashCode() { // pick 2 hard-coded, odd, >0 ints as args return new HashCodeBuilder(1, 31) .append(this.id) .toHashCode(); }
- Like the previous example, this only uses the id for creating the hashCode but you can easily append more properties
- Create a compareTo function using the CompareToBuilder
public int compareTo(Object obj) { org.sakaiproject.tool.mytool.MyObject castObj = (org.sakaiproject.tool.mytool.MyObject) obj; return new CompareToBuilder() .append(this.getId(), castObj.getId()) .toComparison(); }
- Like the previous examples, this only uses the id for comparison but you can easily append more properties
- Create a toString function using the ToStringBuilder
public String toString() { return new ToStringBuilder(this) .append(this.id) .toString(); }
- Like the previous examples, this only uses the id for string generation but you can easily append more properties
- Add the required imports
- Your object should now be comparable to objects of the same type that have the same data
- A sample of an object which demostrates use of the commons-lang builders: TaskImpl.java
- A sample of implemented functions using commons-lang:
public boolean equals(Object obj) { if (obj == null) return false; if (obj instanceof Task == false) return false; if (this == obj) return true; Task castObj = (Task) obj; return new EqualsBuilder() .append(this.getId(), castObj.getId()) .append(this.getOwner(), castObj.getOwner()) .append(this.getSiteId(), castObj.getSiteId()) .isEquals(); } public int hashCode() { // pick 2 hard-coded, odd, >0 ints as args return new HashCodeBuilder(1, 31) .append(this.id) .append(this.owner) .append(this.siteId) .toHashCode(); } public int compareTo(Object obj) { Task castObj = (Task) obj; return new CompareToBuilder() .append(this.getId(), castObj.getId()) .append(this.getOwner(), castObj.getOwner()) .toComparison(); } public String toString() { return new ToStringBuilder(this) .append(this.id) .append(this.owner) .append(this.siteId) .append(this.creationDate) .append(this.task) .toString(); }
Comments (3)
May 22, 2006
Antranig Basman says:
While commonslang is probably the best way to implement this functionality if it...While commons-lang is probably the best way to implement this functionality if its needed, I do want to mention the following caveats:
i) The "builders" will cause an object construction on every comparison, and so will tend to make sorts, for example, on very large collections behave rather badly.
ii) In an ideal world, we would like to get these dependencies out of shared - and commons-lang is just "one more thing" that is hanging around there, as a result of being near-automatically pulled in by hbm2java. But I guess we can dream on about this for now...
iii) For many kinds of domain model, the kind of value-based equality performed by the "builders" just isn't right - in many cases, especially where Hibernate is involved, Java natural reference equality can be both fast and correct.
So yup, commons-lang can be great, but I'd like to encourage people to think twice before grabbing for it instinctively.
May 22, 2006
Aaron Zeckoski says:
You probably would want to use more lightweight comparisons like this then:You probably would want to use more lightweight comparisons like this then:
http://bugs.sakaiproject.org/confluence/x/kUU
May 22, 2006
Antranig Basman says:
Yes, but note that those comparisons are still capable of breaking container coh...Yes, but note that those comparisons are still capable of breaking container coherence, especially in an environment like Hibernate where the value returned by getId() may change during a request cycle. I really want to push people towards the really easy and completely reliable approach based around doing nothing! Doing nothing is what every programmer loves...
Only reimplement any of these methods (equals + hashCode, compareTo) if you find a compelling business reason to do so, and not as a matter of course. Some "business objects" really are "values" (e.g. Points, Permissions, Colors, etc.), whereas many, and often those that are stored in database tables, actually are not.