Aug 21 2009

Snippet: Automatic Proxy Creation in Clojure

Published by tim at 3:20 pm under clojure, coding

The proxy function makes it easy for Clojure to interface with the Java layer, but I was dealing with an interface (the AIM Java API) that had an punitive number of things that needed to be overridden…

public void OnIdleStateChange(AccSession arg0, int arg1) {
}

public void OnInstanceChange(AccSession arg0, AccInstance arg1, AccInstance arg2, AccInstanceProp arg3) {
}

public void OnLookupUsersResult(AccSession arg0, String[] arg1, int arg2, AccResult arg3, AccUser[] arg4) {
}

public void OnSearchDirectoryResult(AccSession arg0, int arg1, AccResult arg2, AccDirEntry arg3) {
}

// ... go on like this for pages

The Java code is here, if you’re interested in the entire set of calls. Now, I didn’t care about most of those events, but I had to override them, since they didn’t have a default implementation. What made this seem painful was that I was really only interested in two of the callbacks. So I started to record an Emacs macro to convert the Java code to the equivalent Clojure proxy statement, and then I realized that I didn’t have to — I was using a Lisp.

(defmacro auto-proxy [interfaces variables & args]
  (let [defined (set (map #(str (first %)) args))
        names (fn [i] (map #(.getName %) (.getMethods i)))
        all-names (into #{} (apply concat (map names (map resolve interfaces))))
        undefined (difference all-names defined)
        auto-gen (map (fn [x] `(~(symbol x) [& ~'args])) undefined)]
    `(proxy ~interfaces ~variables ~@args ~@auto-gen)))

Auto-proxy works just like proxy, but it makes an empty implementation for any call that wasn’t defined. So, suddenly, what would have been a bunch of lines collapsed into just:

(defn create-aim-proxy []
  (auto-proxy [com.aol.acc.AccEvents] []
     (OnImReceived [session imSession participant im]
        (handle-im session imSession participant im))
     (OnStateChange [arg0 arg1 arg2]
        (handle-state-change arg0 arg1 arg2))))

Macros ftw. The nice thing about Clojure/Lisp is that it makes coding up this kind of reusable framework stuff really easy.

6 responses so far

6 Responses to “Snippet: Automatic Proxy Creation in Clojure”

  1. Duncanon 23 Aug 2009 at 9:14 am

    Why do you need to do this, doesn’t the proxy form already do this for you?

    “If a method fn is not provided for an interface method, an UnsupportedOperationException will be thrown should it be called.”

    http://clojure.org/java_interop#toc25

  2. timon 23 Aug 2009 at 1:10 pm

    @Duncan: I didn’t want it to throw an exception, I wanted it to just have an empty default implementation. Since it was being invoked as a callback from the API, I didn’t even have an intervening level in which I could put a try statement.

  3. Jetpackon 04 Jan 2010 at 8:14 am

    Tim, this is a very useful macro for me and I bet for plenty of others that want to migrate from Java to Clojure. Have you submitted it to Rich for inclusion in clojure-contrib or core?

  4. timon 15 Jan 2010 at 9:28 am

    @Jetpack: Yah, I’d be interested in that, but last time I looked it wasn’t obvious how to go about submitting stuff. I’ll definitely take a look.

  5. Jetpackon 09 Mar 2010 at 6:11 am

    Tim, I’m trying to extend a class as well as implement an interface in the same proxy. Would you have an idea how I could do this with your auto-proxy macro? Simply adding the extended class to the auto-proxy arguments gives me an excpetion: “java.lang.IncompatibleClassChangeError: Implementing class”.

  6. Jetpackon 09 Mar 2010 at 6:24 am

    Got the solution from #clojure: the extended class name needs to be put at the front of the interfaces argument of the macro. works like a charm.

    Again, in my opinion this macro would be a very useful addition to clojure-contrib or core.

Trackback URI | Comments RSS

Leave a Reply