DZone

Friday, May 6, 2011

Groovy Builders and Modularity

Groovy Builders are a very nice way for hiding 3rd-party APIs complexity, and help you cleaning up a lot your code, but when the thing to be built is large, the method where the builder is used gets really huge. In this post I will explain you the way to split the builder block and make it modular.

Let's start analyzing how Builders work commonly. The builder uses to have an entry-point method which takes a closure as parameter, within the closure you can invoke any builder method to start configuring the target artifact.

Let me show it with an example, I'll use Grails' HibernateCriteriaBuilder and a well know domain for us (developers) which is Issue Tracking:

We have a Map populated from a form holding filtering values:

def filter = fromFilterForm()

def criteria = Issue.createCriteria()

def issues = criteria.list {

  if(filter.author) {
    eq('author', filter.author)
  }

  if(filter.title) {
    like('title', "%${filter.title}%")
  }

  if(filter.description) {
    like('description', "%${filter.description}%")
  }

  if(filter.priority) {
    ge('priority', filter.priority)
  }

  if(filter.created) {
    between('created', filter.created.from, filter.created.to)
  }

  if(filter.statuses) {
    inList('status', filter.statuses)
  }

}

It is not the case for a huge building closure, but you can figure out where I'm pointing to.

So now, I will change it to achieve the same result but in a modular way. For it, I will move all the builder related lines to separate closures held by a map which shares the same keys than the filter map. And I will use a very helpful method in Closure called delegate(Object) so I can propagate the same behavior in HibernateCriteriaBuilder#list(Closure) to all the tiny closures.
If a key is not present in filter map, because it's not desired to filter by it, the closure under the same key in the closures map won't be invoked so we can avoid using that much if statements.

void initClosures() {
  this.closures = [
    author: { eq('author', it) },
    title: { like('title', "%${it}%") },
    description: { like('description', "%${it}%") },
    priority: { ge('priority', it) },
    created: { between('created', it.from, it.to) },
    statuses: { inList('status', it) }
  ]
}

void buildCriteria(def criteria, def filter) {
  filter.each{
    if(it.value){
      def c = this.closures[it.key]
      c.delegate = criteria
      c(it.value)
    }
  }
}

void filterIssues() {
  def filter = fromFilterForm()

  def criteria = Issue.createCriteria()

  criteria.list {
    buildCriteria criteria, filter
  }
}

This is just an example for showing you up how to write a more modular builder code, but you can apply this approach everywhere you need it.

Questions, comments and corrections are welcome.
Good coding to everyone.

Gaston.

Monday, January 24, 2011

Bash Script to launch Java Application

If you develop or just use any Java standalone application, you may know it is not comfortable at all to launch it from a linux console. You have to invoke it with the java command this way.

user@host:~$ java -cp myapp.jar:any-thirdparty-lib.jar com.myapp.MainClass some parameters

You can simplify it a lot packaging your application as an executable jar. The file structure should be something like this.

app_home
  lib
    any-thirdparty-lib.jar
  myapp.jar

myapp.jar must contain the special file META-INF/MANIFEST.MF with the following content.

Manifest-Version: 1.0
Class-Path: . lib/any-thirdparty-lib.jar
Main-Class: com.myapp.MainClass

There are a lot of tools to auto-generate it, such as Eclipse export Wizard, Ant task and Maven jar plugin just to mention some.

Last step has reduced a lot the verbosity of the java command. You can invoke an executable jar this way

user@host:~$ java -jar myapp.jar some parameters

It is much better. Isn't it? but still has some problems. Firstly, it is path-dependent, you must be placed at app_home folder for it to work since in the manifest file the dependencies are listed in a relative to the main jar form. Secondly it is not that natural for linux command-line users, it should be much better to just invoke it this way

user@host:~$ myapp some parameters

So, in order to achieve it we will create a really simple yet useful bash script to launch our java app ala linux. I googled it for a while to find an example but without any luck, so I did it myself.
You will need to create a file called myapp in app_home folder with execute permission and with the following content

#!/bin/sh
SCRIPT_PATH=$(readlink -fn $0)
APP_HOME=$(dirname $SCRIPT_PATH)
java -jar $APP_HOME/myapp.jar $@

The first thing to do is to know the real path for the app_home, this way we will be able to invoke it even if we are not placed in the app_home folder. It is the preferred way for invoking a command which works with files, we go to the folder were the files are and it will be more comfortable for passing the file names as parameters.

So, thanks to the especial bash argument $0 we know the path which the script was invoked with. But it is not enough to use it directly, since another nice thing to do with scripts is to create a symbolic link in any folder included in the PATH environment variable, such as ~/bin or /usr/bin so we can just type the script name without including any folder. This way, if we create such link for our app, $0 will be /usr/bin/myapp and we cannot use it for getting the app_home real path. But we solve it with readlink, this way we resolve the link and we get the physical path the link points to.

Once we have the real script path, we use dirname to get the folder it is placed in. Finally, we use the APP_HOME variable to construct the absolute path to our app jar, it will let java to resolve all relative paths in the manifest Class-Path header.

Done. It is just a 3 lines bash script but it let invoke our java applications in a linux-like  manner. I don't know if it is the best solution, but it works, and it can be improved for sure.

Hope it helps you.
Gaston.

Wednesday, January 19, 2011

A Better Iterator

I've implemented an Iterator, which is a little better than the standard, in a way that it keeps reference to the last returned element.

So it can be used as a regular iterator since it implements java.util.Iterator<E>, but it adds a new method "public E current()", which returns the last element returned by the next() method.

The way it works is by wrapping any given iterator. It can be useful, for instance, when you work with the GoogleCollections API (now called Guava), since internally it is implemented with lazy iterators.

You can pass just this iterator as a parameter to other methods which may collaborate in the process while iterating the elements in a collection. Any method knows and can work with the current element, and also can move the iterator forward, in this case when it returns, the calling method will know the new current element.

Maybe you need it sometime, maybe not. I just want to share it.

Here is the class:


package org.lalala;

import java.util.Collection;
import java.util.Iterator;

public class ImprovedIterator<E> implements Iterator<E> {

 private Iterator<E> delegate;
 private E current;
 private boolean nextCalled;

 public ImprovedIterator(Iterator<E> iterator) {
  this.delegate = iterator;
  this.current = null;
  this.nextCalled = false;
 }

 @Override
 public boolean hasNext() {
  return this.delegate.hasNext();
 }

 @Override
 public E next() {
  this.current = this.delegate.next();
  this.nextCalled = true;
  return this.current;
 }

 @Override
 public void remove() {
  this.current = null;
  this.nextCalled = false;
  this.delegate.remove();
 }

 public E current() {
  if (!this.nextCalled) {
   throw new IllegalStateException("next() was never called or not called again after a remove().");
  }
  return this.current;
 }

 public static <E> ImprovedIterator<E> iteratorFor(Collection<E> collection) {
  return new ImprovedIterator<E>(collection.iterator());
 }

 public static <E> ImprovedIterator<E> iteratorFor(Iterator<E> iterator) {
  return new ImprovedIterator<E>(iterator);
 }

}

Gaston.