Thursday, October 21, 2010

Easy getter/setter interop with Clojure

1. There is also a bean function that turns a POJO into a map (with lazy map entries). There are subtle differences between setter-fn/getter-fn and bean - you can read in the comments to this post.
2. The setter-fn is used in a (map ..) to demonstrate the return values. Ideally you would call setter-fn in a doseq when working on a bunch of setters:

(doseq [each (seq {:name "Jerry Stone"
:address "39 Square, Bloomville"
:email ""
:birth-date (java.util.Date.) ; bad date for convenience
:married true
:country-code 346})
stfn [(setter-fn p)]]
(stfn each))

Java interoperability is one of the strong features of Clojure. This post shows how to use the Clj-ArgUtil library to further ease the calling of getter/setter methods on Java objects.

Let us say there is a Person class (Plain Old Java Object - POJO):

// filename: test/
package test;

import java.util.Date;

public class Person {
private String name = null;
private String address = null;
private String email = null;
private Date birthDate = null;
private boolean married = false;
private int countryCode = 0;

// getters
public String getName() { return name; }
public String getAddress() { return address; }
public String getEmail() { return email; }
public Date getBirthDate() { return birthDate; }
public boolean isMarried() { return married; }
public int getCountryCode() { return countryCode; }

// setters
public void setName(String name) { = name; }
public void setAddress(String address) { this.address = address; }
public void setEmail(String email) { = email; }
public void setBirthDate(Date birthDate) { this.birthDate = birthDate; }
public void setMarried(boolean married) { this.married = married; }
public void setCountryCode(int countryCode) { this.countryCode = countryCode; }

We can construct and set/get on a Person object as follows:

;; assuming we execute this code snippet in the REPL

(import 'test.Person)
(use 'org.bituf.clj-argutil)

;; instantiate a Person object
(def p (Person.))

;; call setters - returns (nil nil nil nil nil nil)
(map (setter-fn p) (seq {:name "Jerry Stone"
:address "39 Square, Bloomville"
:email ""
:birth-date (java.util.Date.) ; bad date for convenience
:married true
:country-code 346}))

;; call getters - returns
;; ("Jerry Stone" "39 Square, Bloomville" "" #<Date Fri Oct 22 01:03:42IST 2010> true 346)
(map (getter-fn p)
[:name :address :email :birth-date :is-married :country-code])

So what just happened? We used setter-fn and getter-fn functions from Clj-ArgUtil to call setters and getters on a Person object.

setter-fn and getter-fn wrap a POJO into respective functions so that setter and getter calls can be made on them easily.

When we call the setters, as you will notice

(map (setter-fn p) (seq {:name "Jerry Stone"
:address "39 Square, Bloomville"
:email ""
:birth-date (java.util.Date.) ; bad date for convenience
:married true
:country-code 346}))

is equivalent to the following:

(map (setter-fn p) [[:name "Jerry Stone"] ; becomes .setName("Jerry Stone")
[:address "39 Square, Bloomville"] ; and so on
[:email ""]
[:birth-date (java.util.Date.)] ; bad date for convenience
[:married true]
[:country-code 346]])

Somewhat similar things happen when calling getters. The following code

(map (getter-fn p)
[:name :address :email :birth-date :is-married :country-code])

gets internally converted into something like this:

(map (getter-fn p)
[[:name] ; .getName()
[:address] ; .getAddress()
[:email] ; .getEmail()
[:birth-date] ; .getBirthDate()
[:is-married] ; .isMarried()

This conversion is due to the as-vector function that is applied to every argument. as-vector wraps a non-collection argument into a vector, or else (if the argument is a collection) pulls the items into a vector.

Hope you have fun with Clj-ArgUtil. You can find more variants of functions for calling setters and getters in the tutorial/documentation:

Kindly share your comments/feedback about this post and the library.

No comments:

Post a Comment