brool

brool \brool\ (n.) : a low roar; a deep murmur or humming

Snippet: Automatic Proxy Creation in Clojure

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.

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

  1. Duncan ()

    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. tim ()

    @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. Jetpack ()

    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. tim ()

    @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. Jetpack ()

    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. Jetpack ()

    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.

  7. Automatically create wrapper object in Clojure | LispCast ()

    [...] Brool shared a macro for creating a proxy that doesn’t throw exceptions for methods that aren’t define. It is called autoproxy. I [...]

Leave a Reply