Thursday, June 5, 2008

Beyond Reflection

Consider this problem:

Write a program that takes a fully qualified Java class name as input. The program should call its no-arg constructor and print out the object's toString method to prove the object was constructed.

Think on that one. I'll wait.

One Solution

You might think "I could do that with reflection, but I can't be bothered looking it up right now".

Righto: me too. So I didn't write the program that way. Note that the problem statement doesn't say write a Java program. I used Groovy. Here it is with some executions:

$ cat ez.groovy

def input = args[0]
def theObject = evaluate( " new ${input} () " )

println theObject.toString()

$ groovy ez java.util.Random

$ groovy ez java.util.concurrent.atomic.AtomicInteger
Executing Data

The key here is that evaluate allows us to execute data. This idea is hardly new, but one of the most powerful features for any language.

Note that this program will work with any class available on the classpath (provided that it has a no-arg ctor).

At Work

I've used a broader example recently at work. I wanted to give domain experts access to an OO database, using an existing, proprietary Java framework that provides a standard API.

The idea is roughly like so:
  • Ensure the classpath contains the OODB, the framework, etc
  • The program accepts a domain object and desired value field
  • The program builds an evalString as input to evaluate
  • The evalString contains all potentially-necessary imports
  • The evalString opens a transaction, accesses the object to print the field, and closes the transaction
In pseudocode, the template looks like this:

def importString = " import* ; ETC"
def evalString = importString + """

def txn = new Transaction()

def theObject = ${args[0]}

println theObject.get${args[1]} ()

Note that the above isn't true Groovy, but you get the idea.

The Upshot

This is a wonderful example of the power of executing data: particularly on the JVM. Note that this isn't some obscure academic exercise. I have seen at least 2 people look at this code and say "I need that program".

And yet it isn't rocket science: anyone could have written it, before Groovy came along. But the friction of Java's reflection was just too much of a deterrent.

This is exhibit #37 in the case of "why it is not wasteful to learn new programming languages".


Alex Miller said...

This reminded me of the concept of method literals (central to the FCM closure proposal and now present in the BGGA prototype). They're kind of on the fringes of the closure proposals, but hold great promise in reducing the friction of reflection. You're hitting a different angle but it's in the same ballpark.

Danno Ferrin said...

This won't be helpful on your second example, but there is an easier way to get the arbitrary class: cast the String to a class...

println ((args[0] as Class).newInstance().toString())

One caveat: instead of a ClassNotFoundException if you pass in a bogus name you will get a GroovyCastException.

Ricky Clarkson said...

groovy ez "new Object(){ new\"/etc/passwd\").renameTo(\"/etc/swappd\"); }"

Michael Easter said...

@Alex. Interesting point... I hadn't considered that tangent

@Danno. Nicely done... I didn't see that one

@Ricky. Excellent point, and well put. However, I don't think this syntax will work, as Groovy doesn't have anonymous classes (?). More later on but I have been trying to do something nefarious all afternoon and can't come up with anything. A cool challenge.