Friday, June 4, 2010

More details of the exposer

As I said before, there is more to the exposer than I wrote before.

Lua is, as you already know, a language that supports multiple return values. Java however does not, and that makes integration slightly less straightforward.

It is not obvious how to do it, but it is possible to expose Java methods and have them return multiple values.

Exposing multiple return values
The workaround is to create your java method like this:
@LuaMethod
public void getUnitLocation(ReturnValues rv, String unitName) {
Unit unit = service.getUnit(unitName);
rv.push(unit.getX());
rv.push(unit.getY());
}
You can then use the function like this:
local x, y = getUnitLocation("theUnit")
This looks a lot like how it's done using a raw JavaFunction, you might exclaim! This is not by coincidence. In fact, ReturnValues is just a thin layer on top of the original LuaCallFrame.

In order for this to work, the exposer mechanism performs special handling if the first parameter of the method is of the type ReturnValues.

Varargs
If the java method is a vararg method, i.e. defined like this:
public String concat(String... strings) {
return Arrays.toString(strings);
}
then Lua scripts can call it with concat("a", "b", "c") and get "[a, b, c]" back. This may seem obvious and it should be, but I'll mention it anyway.

Error handling
What if you do something wrong, such as calling getUnitLocation(), thus not passing in the name? The exposer will detect an incorrect number of arguments and throw an error message at you.

This however, is ok: getUnitLocation("theName", "something else"). The exposer will simply disregard the extra parameter. You may expect it to similarly throw an error, but this is in fact in line with how Lua generally handles too many arguments.

If you supply the incorrect type, you'll also get an error (unless the type can be converted to the Java type of course). If you supply nil, it'll go through into the method - so you have to add the null checks yourself.

Method overloading
I mentioned method overloading earlier, and that the exposer can handle it to some extent. I'll clarify that here.

Assume you have the following class:
public class Example {
public void myMethod() {
}
public void myMethod(Object a, Object b, Object c) {
}
public void myMethod(String a, String b) {
}
public void myMethod(int a, int b) {
}
}
If all these methods are exposed, the exposer will detect that it's already been exposed, and will convert the generated JavaFunction to a dispatching JavaFunction.
This dispatcher will inspect the arguments passed in the call to try to determine which method to call. The actual algorithm for selecting is very naive, but most likely good enough for almost all cases. In any case, you shouldn't use overloading too much - that's bound to confuse your users.

The dispatcher first sorts the list of matching methods by decreasing parameter count. Varargs count as zero. In our case, this means the following ordering:
public void myMethod(Object a, Object b, Object c);
public void myMethod(String a, String b);
public void myMethod(int a, int b);
public void myMethod();
The middle two have the same number of parameters, and thus can occur in any order.
When getting a call, it simply goes through the list in order and tries to call it. If it can't be called, it simply tries the next.
A call like myMethod("a", "b") will fail the first method, since it doesn't have enough arguments, but it will match the second.
myMethod("a", 123) won't match 1, 2 or 3, but it could be reduced to the 4th.
myMethod(nil, nil, nil) will match the first (since nil matches all types).

Methods and functions
What is a method? What is a function?

I'd like to define method as a function that's associated with an object. In Java, all instance methods falls under that definition of method. Static methods in Java can be considered functions, since they are not associated with specific objects.

Real methods don't really exist in Lua - all functions are plain functions. However, you can simulate methods easily. There is a simple syntactic sugar in Lua for this:

Sugar for a method definition:
function obj:method(args...)
end
is equivalent to:
function obj.method(self, args...)
end
That means that in the first case, it simply injects a new local variabel "self" assigned to the first argument.

Similarly, there is sugar for a method call:
obj:method(args...)
is equivalent to:
obj.method(obj, args...)
which of course is a regular function call.

This also how the exposer differentiates between methods and functions. If it's annoted with @LuaMethod(global = true) or is static, it's a function, and arguments are passed straight through from Lua to Java.

If global = false (which is the default), the first argument is consumed by the exposer and used to find the actual object to invoke the method on, and it's not passed along to the method.

No comments:

Post a Comment