Sunday, August 26, 2007

Lightweight Google Geocoder with Java

I often look for tools before deciding to build them myself for obvious reasons: saving time and labor. I was looking for a Java implementation of Google geocoder but there seems to be only this one GeoGoogle. My first impression is that the tool is a little heavy for its job. Further more, it chooses to deal with XML format and all the parsing and schema validation is a bit too much for my taste. To use Google geocode service, all you need is to send an HTTP request along with your address. The response will be in either XML or JSON format. It will include details about the address you sent such as city, state, zipcode, etc, but the most important things are longitude and latitude. So I decided to go with JSON and wrote this tool. Hopefully, someone else might find it useful. Many thanks to the folks that wrote JSON-LIB.


public class GGcoder {
private static final String URL = "http://maps.google.com/maps/geo?output=json";
private static final String DEFAULT_KEY = "YOUR_GOOGLE_API_KEY";

public static GAddress geocode(String address, String key) throws Exception {
URL url = new URL(URL + "&q=" + URLEncoder.encode(address, "UTF-8")
+ "&key=" + key);
URLConnection conn = url.openConnection();
ByteArrayOutputStream output = new ByteArrayOutputStream(1024);
IOUtils.copy(conn.getInputStream(), output);
output.close();

GAddress gaddr = new GAddress();
JSONObject json = JSONObject.fromString(output.toString());
JSONObject placemark = (JSONObject) query(json, "Placemark[0]");

final String commonId = "AddressDetails.Country.AdministrativeArea";

gaddr.setFullAddress(query(placemark, "address").toString());
gaddr.setZipCode(query(placemark,
commonId + ".SubAdministrativeArea.Locality.PostalCode.PostalCodeNumber")
.toString());
gaddr.setAddress(query(placemark,
commonId + ".SubAdministrativeArea.Locality.Thoroughfare.ThoroughfareName")
.toString());
gaddr.setCity(query(placemark,
commonId + ".SubAdministrativeArea.SubAdministrativeAreaName").toString());
gaddr.setState(query(placemark, commonId + ".AdministrativeAreaName").toString());
gaddr.setLat(Double.parseDouble(query(placemark, "Point.coordinates[1]")
.toString()));
gaddr.setLng(Double.parseDouble(query(placemark, "Point.coordinates[0]")
.toString()));
return gaddr;
}

public static GAddress geocode(String address) throws Exception {
return geocode(address, DEFAULT_KEY);
}

/* allow query for json nested objects, ie. Placemark[0].address */
private static Object query(JSONObject jo, String query) {
try {
String[] keys = query.split("\\.");
Object r = queryHelper(jo, keys[0]);
for (int i = 1; i < keys.length; i++) {
r = queryHelper(jo.fromObject(r), keys[i]);
}
return r;
} catch (JSONException e) {
return "";
}
}

/* help in query array objects: Placemark[0] */
private static Object queryHelper(JSONObject jo, String query) {
int openIndex = query.indexOf('[');
int endIndex = query.indexOf(']');
if (openIndex > 0) {
String key = query.substring(0, openIndex);
int index = Integer.parseInt(query.substring(openIndex + 1, endIndex));
return jo.getJSONArray(key).get(index);
}
return jo.get(query);
}

public static void main(String[] args) throws Exception {
System.out.println(GGcoder.geocode("650 Townsend st, San Francsico, CA"));
System.out.println(GGcoder.geocode("94103"));
}
}


/* Class to hold geocode result */
public class GAddress {
public String address;
public String fullAddress;
public String zipCode;
public String city;
public String state;
public double lat;
public double lng;

/* getters and setters */

Thursday, August 16, 2007

Make use of Java dynamic proxy

I didn't know about Java dynamic proxy before but finally got a chance to learn and found a good use for it. The test I was writing had the smell of repeating code:

private void testPutWithoutSynch(params...) throws Exception {
...

map.put("k1", "v1");

assertions...
}

private void testPutWithSynch(params...) throws Exception {
...

synchronized(map) {
map.put("k1", "v1");
}

assertions...
}


The test pattern repeated with other operations that you can do with Map like puttAll, remove, etc... Do_set_up() is actually some pre setup steps that I need to perform according to parameters list. As you can see, I have to repeat the test method twice for each Map operation I want to test, with and without the synchronize block.

So there is this code smell and I didn't know get rid of it. Then Tim Eck, my coworker, showed me how to use Java proxy. The idea is to wrap my map in a proxy, and use reflection to invoke methods of the map. By using a invocation handler, I can have a variable to control whether or not I want to use synchronize for that particular operation.

class Handler implements InvocationHandler {
private final Map map;
private boolean useSynch = false;

public Handler(Map map) {
this.map = map;
}

public void setUseSynch(boolean flag) {
this.useSynch = flag;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (useSynch) {
return invokeWithSynch(method, args);
} else {
return method.invoke(map, args);
}
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}

private Object invokeWithSynch(Method method, Object[] args) throws Throwable {
synchronized (map) {
return method.invoke(map, args);
}
}
}


Then the proxy can be created like this:

Map map = new HashMap();
Handler handler = new Handler(map);
Map mapProxy = (Map)Proxy.newProxyInstance(getClass().getClassLoader(), new Class[] { Map.class }, handler);


With that my test methods can be reduced to:

private void testPut(params..., boolean useSynch) throws Exception {
do_set_up(params)

handler.setUseSynch(useSynch);
mapProxy.put("k1", "v1");

assertions depends on useSynch
}


Then I just need to call this method twice and accomplish the same thing.

testPut(params..., false);
testPut(params..., true);


Pretty neat huh? Java proxy is nice tool to know.