Monday, June 25, 2012

let me introduce you project Lombok

Problem: Have you ever been in a situation, when you tough that creating all those boring equals(), hashCode(), toString() methods is quite time consuming and maintaining these is error prone?
SolutionIf so, read on :) Project Lombok might be the answer.

Yesterday, while searching for some solution on this (with having in mind the tutorial of spring roo I've read quite some time ago where these could be handled by AOP using annotations) I've found as a possible solution project Lombok:
http://projectlombok.org

Lombok demo and features list
I really like their demo video, if you want an introduction to this project, make sure you watch it:
http://projectlombok.org/
And there is also quite nice feature list available: 
http://projectlombok.org/features/index.html

Encouragement
OK, so what's next? Am I brave enough to go for it? While doubing it I've found out I'm not the only one:
http://stackoverflow.com/questions/3852091/is-it-safe-to-use-project-lombok

That gave me some encouragement. So let's go and try it.

I have some small open source project, so let me show you my code prio and after the change to give you an overview (or rather motivation :)).

Code snippets comparison
See:
  • eclipse generated methods:
    // eclipse generated methods way
    public class Schedule implements Serializable {
    
    ...
    
     @Override
     public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + ((bgImage == null) ? 0 : bgImage.hashCode());
      result = prime * result + (blank ? 1231 : 1237);
      result = prime * result + (clear ? 1231 : 1237);
      result = prime * result + (live ? 1231 : 1237);
      result = prime * result + ((name == null) ? 0 : name.hashCode());
      result = prime * result
        + ((presentations == null) ? 0 : presentations.hashCode());
      return result;
     }
    
     @Override
     public boolean equals(Object obj) {
      if (this == obj)
       return true;
      if (obj == null)
       return false;
      if (getClass() != obj.getClass())
       return false;
      Schedule other = (Schedule) obj;
      if (bgImage == null) {
       if (other.bgImage != null)
        return false;
      } else if (!bgImage.equals(other.bgImage))
       return false;
      if (blank != other.blank)
       return false;
      if (clear != other.clear)
       return false;
      if (live != other.live)
       return false;
      if (name == null) {
       if (other.name != null)
        return false;
      } else if (!name.equals(other.name))
       return false;
      if (presentations == null) {
       if (other.presentations != null)
        return false;
      } else if (!presentations.equals(other.presentations))
       return false;
      return true;
     }
    
     @Override
     public String toString() {
      return "Schedule [presentations=" + presentations + ", bgImage="
        + bgImage + ", name=" + name + ", blank=" + blank + ", clear="
        + clear + ", live=" + live + "]";
     }
    
    }
    
  • Lombok way:
    // lombok way
    @ToString
    @EqualsAndHashCode
    public class Schedule implements Serializable {
    
    ...
    
    }
    
  • manually written ones with Equals/ToString/HashCode Builders:
    // manual way using Equals/ToString/HashCode Builders
    public class Schedule implements Serializable {
    
    ...
    
     @Override
     public int hashCode() {
      return new HashCodeBuilder()
      .append(this.getPresentations())
      .append(this.getBgImage())
      .append(this.getName())
      .append(this.isBlank())
      .append(this.isClear())
      .append(this.isLive())
      .toHashCode();
     }
    
     @Override
     public boolean equals(Object obj) {
      if ( !(obj instanceof Schedule) ) return false;
      Schedule castOther = (Schedule) obj;
      return new EqualsBuilder()
       .append(this.getPresentations(), castOther.getPresentations())
       .append(this.getBgImage(), castOther.getBgImage())
       .append(this.getName(), castOther.getName())
       .append(this.isBlank(), castOther.isBlank())
       .append(this.isClear(), castOther.isClear())
       .append(this.isLive(), castOther.isLive())
       .isEquals();
     }
    
     @Override
     public String toString() {
      return new ToStringBuilder(this)
      .append("\n presentations", this.getPresentations())
      .append("\n bgImage", this.getBgImage())
      .append("\n name", this.getName())
      .append("\n blank", this.isBlank())
      .append("\n clear", this.isClear())
      .append("\n live", this.isLive())
      .append("\n")
      .toString();
     }
    
    }
    
So as obvious from the samples, instead of writing/maintaining the code you can rather just annotate. (Btw. if there were no lombok way, I'd go for Builders way => manual writing + maintaining).

Integration
The great thing is that there is a simple integration with maven (that is the build tool of my choice). See instructions:
http://projectlombok.org/mavenrepo/index.html
Moreover integration with Eclipse seems really straitforward too (all the autocompletition as well as Outline view work as expected or rather unexpectedly well).

Some more motivation
To encourage lazy developers (like me) even more, there are things available for:
- getters and setters (@Getter, @Setter)
- logging (depending on your logging library: @CommonsLog, @Log, @Log4j, @Slf4j)
- there is even more, but for the rest I might not be brave enough ( yet :))

Behind the scenes
OK, sounds nice, but how it works internally?
In fact lombok generates expected methods/fields to the target class. So the nice thing is that you don't need it during runtime/deployment. Once compiled, classes will contain the stuff.

How do you guys feel about it? Would you give it a try? Or do you have some other approach on this? Feel free to share your ideas/feelings.

No comments: