Extending Java Classes Using proxy

by Gregg Williams

One of the key advantages of Clojure over other versions of Lisp is its baked-in connection to Java. With Clojure, it's quite easy (in most cases) to write Clojure code that executes Java code or any of the thousands of Java libraries that exist today. However, the correspondence between a unit of Java code and its Clojure equivalent is not always one-to-one, and an unsuspecting Clojure programmer will inevitably move from competence to total cluelessness, completely without warning.

This was certainly the case when I decided to use a Java graphics library named Piccolo2D (http://piccolo2d.org/) for an application that uses an "electronic notecard" data format (http://www.InfoML.org) that I've created. Integrating Piccolo2D into my Clojure program was easy: just put its two JAR files on the Clojure classpath, and suddenly Piccolo2D was as much a part of my Clojure environment as Java was.

It was simplicity itself to call a Piccolo2D class from Clojure; subclassing (or, to use the preferred Clojure terminology, extending a base class), was something that existing Clojure documentation and web pages were strangely silent about. I eventually found that the proxy macro was the simplest way to do this in Clojure. The examples that I found showed how to redefine base class methods in the extended class, but I could find nothing on how to give an extended class its own constructor method.

This article is a tutorial on the basic use of proxy, showing you what to do and how to think about extending Java classes and using them within Clojure. The code you will be looking at is a Piccolo2D sample program (written in Java) and an equivalent Clojure program that I wrote. Even if you have no interest in Piccolo2D, there's still plenty of things to learn about in this article. And if you know anything about the Swing GUI framework, you'll be able to understand the Piccolo2D-specific code without any problems.

Introducing the Code

The Java and Clojure code that you will be studying in this article is shown below in Listings 1, 2, and 3; these are all you need to understand this article. If you're interested, however, you can see the complete Java and Clojure programs at http://gist.github.com/369239; the original Java program is explained at http://www.piccolo2d.org/learn/interface.html.

It will be helpful, though, to understand the context of the code will be examining. The complete InterfaceFrame program simply demonstrates how to use various parts of Piccolo2D to draw various kinds of images in a Swing window. In Listing 1 (below), the Piccolo2D class PPath is a type of object that can be drawn, and the ToggleShape class is an extension of PPath that has a new behavior added: When the mouse cursor is over an instance of ToggleShape and the mouse is pressed, the shape changes from an ellipse (its normal form) to a rectangle, and when the mouse is released, the shake resumes its original elliptical form. The Java variable fIsPressed represents the state of the mouse button, the paint method redraws the shape based on the value of fIsPressed, and the repaint() method forces the window to be redrawn to show the changes that have been made to the window.

java-subclass.png

Listing 1: The Java class ToggleShape.

clj-proxy.png

Listing 2: The Clojure code that implements the same functionality as Listing 1.

Reassembling the Onion

You've probably heard the phrase "peeling the onion" used as a way of describing the process of understanding something from the outside in. In this article, I'm going to reverse that process, in the hope of making this article interesting to people of different levels of Clojure experience. Part I will explain the basic minimum needed to understand how to use the Clojure proxy statement to implement the Clojure equivalent of extending a Java class. Part II gives further helpful details if your program uses Swing. Part III covers several additional topics that will be useful to some readers.

Part I: Understanding Proxies

In Clojure, proxy is a macro that you use when you want to extend a base class or implement an interface—but only when you don't need to add any new methods to the extended class. (For the latter, you must use gen-class, which is outside the scope of this article.) Java and Clojure implement this process in different ways: Java implements all the details in code that defines the extended class, while Clojure puts certain details inside the proxy form and other details elsewhere in the program. This article will explain how to extend classes in Clojure by first showing you the individual details, then explaining how they fit together.

The Proxy Division of Labor

The first step toward learning how to use proxies is to understand the following:

The First Law of Proxies

The proxy form itself can only redefine zero or more of the methods inherited from the Java base class (i.e., the superclass) or its ancestors.

The Second Law of Proxies

Everything else necessary to implement the extended class (i.e., the subclass) must be done in the environment surrounding an instance of the proxy class that was previously created from the base class. This includes Clojure equivalents of instance variables and the extended class's constructor method.

Given this understanding, it's time to look at each detail in turn.

The Syntax of proxy

The simplest version of a proxy form looks like this:

(proxy [baseclass-name] [baseclass-constructor-parameters]
    method-redefinition-1
    ...
    method-redefinition-N)

where a method redefinition looks like:

(method-name [parameters]
    method-body)

Studying Proxies in the Wild

Look at the Clojure definition of create-toggle-shape, in Listing 2, lines 30 to 41 (which will be abbreviated in this article as L2, 30-41). The proxy corresponding to the Java class ToggleShape (all of Listing 1) is at L2, 34-41. On line 34, you can see that the proxy form's base class is PPath and that PPath's constructor takes no arguments. The rest of the form comprises a redefinition of one Java method, paint.

When you use the proxy keyword, you are creating an instance of an anonymous class that "stands in for" (i.e., serves as a proxy for) the base class. When you call a method on this instance-of-an-anonymous-class (called the proxy class), if the method is defined within the proxy form, it is executed; otherwise, Clojure looks to the base class and its ancestors to find and execute the code corresponding to the method named. This is exactly the same thing that Java does when it finds a method not found in the class corresponding to the method's target object.

This is all that needs to be said about the proxy form itself (or, more accurately, it's just enough to get you started). But there's plenty more that needs to be done before this Clojure program will act like its Java counterpart.

Instance variables

One of the ways in which the Java class ToggleShape extends class PPath is that ToggleShape has an instance variable fIsPressed, which is used to keep track of whether or not the mouse button is currently pressed.

How do you create the Clojure equivalent of a Java instance variable? According to the Second Law of Proxies, you must make the appropriate binding in the environment surrounding an instance of the proxy class. (The binding of a value to a symbol is roughly equivalent to assigning a value to a variable in state-based languages.) In this case the binding is done at L2, 33, which is in the same environment as the proxy form of lines 34-41. (Note that this proxy form is bound to the symbol named shape.)

Operating on the Proxy Instance

If you look at the code for ToggleShape, you will see that the constructor (L1, 103-118) creates and gives a value to the instance variable fIsPressedq, then it executes two methods on the new ToggleShape object being created, setPathToEllipse (line 104) and addInputEventListener (line 106). I just finished describing how the Java instance variable is transformed into Clojure binding. The next question is: How do you translate these two method invocations?

The answer is again to be found in the Second Law of Proxies (since the First Law doesn't apply). Java uses a method operating on an object to get a lot of its work done. The Clojure equivalent of this is calling a function, one argument of which is a value representing the corresponding Java object.

Understanding how this occurs involves understanding the syntax that each language uses. On the Java side, line 104 reads setPathToEllipse (0, 0, 100, 80);, but this is assumed to be a shorthand for this.setPathToEllipse (0, 0, 100, 80);, where this represents the Java object being created. The Clojure equivalent is (.setPathToEllipse shape 0 0 100 80). To understand the code that is present at L2, 44, (.setPathToEllipse 0 0 100 80), look at the previous line, (doto shape. The doto macro is used when several Java methods are being called on the same object. By wrapping a series of Java-in-Clojure-syntax forms in a (doto object-value form, you can remove the value corresponding to the Java object (in this case, shape) from each method form. (A Clojure form, which is equivalent to a Java statement, is simply a unit of source code bounded by matching parentheses.)

Java Is super, Clojure Is super-proxy

Most Java method calls translate quite nicely into Clojure using a simple, well-known pattern (see "Clojure Syntax for Java Method Calls," in Part III of this article, if you're not already familiar with it). However, when the object is represented by the Java keyword super, this common pattern does not work.

Let's start with the following "simple" Java method call:

my-object.paint(paintContext);

The equivalent form in Clojure would be

(.paint my-object paintContext) ; note the dot in ".paint"

However, in situations where you would be using super as the "object" (referring to the superclass of the current Java object), the corresponding Clojure code is different from what you would expect. Take, for instance, the "super" Java method call in line 126 of Listing 1:

super.paint(paintContext);

The correct Clojure code for this Java method call is

(proxy-super paint paintContext)

Note that the pattern of symbols here, ("super-object" method-name argument), is different from that of the "simple" method call, (method-name object argument). It's very important to note that in the "super" method call, you do not place a dot after the method name—it's paint, not paint.!

Putting the Pieces Together

Now that we've seen all the pieces that comprise this Clojure program, it's important to see how they are arranged. In Java, the extended class (ToggleShape) is defined in one place as an extension of the base class, PPath; see L1, 99-118. The Java code contains an instance variable, fIsPressed (line 101), the constructor for ToggleShape (lines 103-118), and a redefinition of the paint method (lines 120-129).

Things are considerably different on the Clojure side, though. Only the redefinition of paint is contained within the body of the proxy form, as given by the First Law of Proxies; see L2, 34-41. The instance variable fIsPressed? (line 33) is bound within the scope that encloses the creation of the proxy class and its binding to the symbol named shape (line 34).

So where did the constructor go?

Before I answer this question, I want to draw attention to a fundamental difference between the two programs being studied here. The Java program (Listing 1) customizes the base class PPath by defining a new class, ToggleShape, that acts like the base class except for the extensions described in its own definition.

The Clojure program (Listing 2) does not create a new, named class that extends an existing base class. It can't, because proxy can't do that; creating a named class is another reason to use gen-class. Instead, proxy creates an anonymous instance that "proxies" (stands in the place of) the base class, and other parts of the program must add the remaining extended-class behaviors that you want.

To summarize, in Java, you extend an abstraction and give it a name (by defining the extended class), then make an instance of it. In Clojure, however, you make an instance of an abstraction, give it a name, and then extend the instance. The proxy form (and the First Law of Proxies) covers the first step, and additional coding (and the Second Law) covers the second and third steps.

Let's look at the details of this in Listing 2:

  • The proxy form (lines 34) creates an instance of a class that "proxies" for the base class, PPath, and does only that part of the class-extension process that can be accomplished by redefining existing methods (here, the redefinition of the paint method in lines 35-41)
  • This instance is given a name, shape, on line 34
  • The additional code that extends the instance by acting in the environment surrounding the instance occurs in several places: line 33 (creating the instance variable fIsPressed?), lines 43-44 (setting the physical appearance of the instance), and lines 45-56 (redefining the listener code for the mousePressed and mouseReleased events).

So where, within all this code, is the equivalent of the ToggleShape constructor? It's in the "additional code" described in the previous paragraph, lines 33 and 43-56. It corresponds directly to the Java code for the ToggleShape constructor in Listing 1, lines 106-117.

Mini-Conclusion

This ends the explanation of how proxy (at least, in its simplest form) works. If you've written a few Clojure programs, this may be all you need to start using proxy to extend (subclass) an existing Java base class.

Part II: Swing GUI Issues

It's likely that someday you will need to create software that other people will use—and that means providing a graphical user interface for a desktop application. The Swing GUI is part of the standard Java distribution, which means that it is always available from Clojure.

As with extending a class, implementing a Swing GUI is done differently in Java and Clojure. The rest of this section describes the differences found within the Java sample code and its Clojure equivalent.

Implementing Event Handlers

In Java, the constructor for ToggleShape adds its own event listeners by creating a new PBasicInputEventHandler and passing it to addInputEventListener (L1, 106-117). If you don't know Swing very well, this chunk of code may look impenetrable. Think of the PBasicInputEventHandler as an object that "packages" two event listeners, mousePressed and mouseReleased. PBasicInputEventHandler can be thought of as "installing" these event listeners into the program, so that they will be called when the user presses or releases the mouse button. Also remember that PBasicInputEventHandler is itself a Java method that is called on the implicit object named this, which represents the ToggleShape that has just been created.

An examination of the Java and Clojure code implementing the mousePressed event handler shows several differences that you will need to keep in mind when you write your own Swing event handlers in Clojure. (I assume that something similar would be true if you were to use the Eclipse SWT graphical interface as well.)

First, note how the addInputEventListener method is applied in each language. In Java, it is applied to the implicit this object (L1, 106). In Clojure, however, the method's object is shape (L2, 43), for reasons described earlier in this article.

Second, addInputEventListener requires a different kind of argument. In Java, you create a new PBasicInputEventHandler object (L1, 106). In Clojure, you extend (subclass) the PBasicInputEventHandler class and redefine two of its methods using proxy.

Third, event handlers are defined differently in the two languages. Java defines the mousePressed method using a simple method definition (L1, 106-111) defined in no particular context; it is understood that this replaces the default mousePressed method that Swing supplies. However, the equivalent definition in Clojure (L2, 47-51) is made in the context of its enclosing proxy form, which is itself in a specific context (that of a specific object, shape).

Part III: Other Topics of Interest

We Don't Need No Stinking Type Declarations

One small but welcome difference between Java and Clojure is the kind that make Clojure a joy to use. In Java, you must declare the type of methods, parameters, variables—well, everything. You can see this, for example, in the definition of mousePressed, which has one parameter (L1, 106). When calling Java code from Clojure, you don't have to do this; witness the absence of the method type (public void) and the argument type (PInputEvent) on the Clojure side (L2, 47-48). In most cases, Clojure infers the argument type that the situation requires.

This same ability also means that you can often use the desired value in its current form, rather than the one that Java requires. For example, if a method requires an argument of type float and the value you want to use is of type int, you don't need to coerce it into a float before using it—Clojure will usually do it for you! And if it doesn't, see the next topic, below.

Type Hints

If your Clojure code gives a particularly odd error message or unexpected results, consider the possibility that your code contains a typed value that Clojure isn't interpreting correctly. For such occasional events, Clojure provides type hints, which Clojure uses when it is provided.

A typed hint is an annotation that appears just before the value being annotated. It consists of the two characters "#^" followed, without space, by the required type name—for example, #^PPaintContext. In fact, you can see an example of this same type hint In Listing 2, line 35, where the parameter paintContext is given the type PPaintContext. (By the way, the "#^" notation is a shorthand for a longer notation that looks like this: #^{tag: PPaintContext}}.)

Addendum 8/20/10. Starting with Clojure 1.2 (which was formally released yesterday), the notation for type hints has changed. The old notation, "#^", has been deprecated in favor of a different notation, "^". Clojure 1.1 and before still use "#^". Clojure 1.2 can use either. In some future version, only "^" will work. Thanks to Clojure Hero Lars Nilsson for pointing this out. —Gregg

Clojure Syntax for Java Method Calls

Most Java code of the form

object1.method2(arguments3);

is "translated" into the following Clojure form:

(. object1 method2 arguments3)

(Be sure to remember the "dot" operator just after the opening parenthesis.) I like this syntax because the argument order, with the object first, reminds me that this code runs in the environment of Java, not Clojure.

However, most programmers in the Clojure community prefer the alternative syntax, which mimics the standard (function-name arguments) syntax of Clojure and all other Lisp-family languages. This is the syntax that is used throughout Listing 2. The above example translates to the following "idiomatic" Clojure code:

(.method2 object1 arguments3)

The method name is placed first (with a "dot" character placed before the name), followed by the object name, and ending with the zero or more arguments listed in the same order as they would be in Java.

UPDATE: The "dot" character goes before the method name, not after it. I've corrected the text above to reflect that. "Class." in Clojure is the same as "new Class()" in Java. Thanks to Walter Tetzner for pointing this out! -Gregg W (Nov 17, 2010)

Importing Java Classes into Your Clojure Program

There is, unfortunately, one way in which Java is better than Clojure—at least in the sense that chocolate is "better" (i.e., more pleasant) than broccoli. The Clojure way is "better" in the sense of "better for your program"; your program is smaller and (possibly) faster because no unused methods have been included, but it's a lot more hassle to the programmer.

In any case, here's the story: In Clojure, as in Java, you must specify each and every class you want to use in your program. (The one exception to this is that all classes in java.lang are automatically imported.) In Java, you can import all the classes in a given package by using the * wildcard character—for example, import javax.swing.*;.

Unfortunately, Rich Hickey (the inventor of Clojure) didn't think that importing all the classes from a package into the program's namespace was "a good idea"1, so that's what he implemented. As a result, you must explicitly name each Java class that you want to use.

Listing 3 shows the ns (short for namespace) form that begins my Clojure program (Listing 2 occurs later in the same Clojure source file). There is an import macro in Clojure that can also be used, but using the import: keyword within the ns form at the top of your program is the preferred method of importing in the Clojure community.

clj-imports.png

Listing 3: Java class import information, found within the Clojure ns (namespace) form.

Each form inside the (:import … ) "clause" has the following syntax:

(package-name method-name-1 … method-name-N)

Where all the methods listed belong to the package listed. You can see instances of this in L3, 19-28.

Ensuring that External Libraries Are Present

Novice and professional alike inevitably have the experience of writing a program that looks perfect in all respects but doesn't run. If the classes you are importing aren't part of the Java language (e.g., the Piccolo2D library used in this program), then you need to ensure that this external library is available for loading by your Clojure program. How this is done varies with the Clojure environment you're using, so you should consult the appropriate documentation for details. In most Clojure IDEs, your program is part of a group of resources called a project; the IDE's documentation will tell you how to add an external library (usually one or more .jar files) to a project.

Conclusions

Various Clojure bloggers and book authors praise the mechanism for calling Java code from Clojure as "simple and direct,"2 but we know that's not always the case. Java statements do not always translate one-to-one into Clojure forms, and the two languages handle class extension (i.e., subclassing) differently.

With its many differences from state-based, procedural languages, Clojure is, in itself, a challenge to most mainstream programmers. I can't deny the exhilaration of seeing Clojure code running, but being stuck is no fun at all. I wrote this article in the hope that it will prevent you from being stuck like I was, so you can spend more of your time enjoying the leverage that Clojure brings to programming.

I invite feedback, corrections, and alternative solutions in relation to this article; you can reach me at gregg4 at-sign GettingClosure dot-sign com. Until then, good hacking!


Comments

Add a New Comment
or Sign in as Wikidot user
(will not be published)
- +

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License