Lua API
Extra Lua Types
We provide three extra userdata
types that correspond to three Java concepts. We use their abbreviations in the following documentation.
Java classes | Java objects | Java arrays | |
---|---|---|---|
Abbreviation | jclass | jobject | jarray |
jclass
type
For a jclass
clazz
:
clazz.memberVar
performs the following in sequence:- It looks for a field named
memberVar
. It returns the public static member if it finds it.- If you have an inner class also named
memberVar
, you will have to manuallyjava.import
it.
- If you have an inner class also named
- It then looks for an inner class named
memberVar
, and returns that if it is available.- If you have a method also named
memberVar
, you need to usejava.method
to look that up.
- If you have a method also named
- Otherwise, it prepares for a method call. See
clazz:memberMethod(...)
below.
- It looks for a field named
clazz.memberVar = value
assigns to the public static member. If exceptions occur, a Lua error is generated.clazz:memberMethod(...)
calls the public static member methodmemberMethod
. See Proxied Method Calls for more info.clazz(...)
:- For an interface, this expects a table as the parameter and creates a proxy for it. See
java.proxy
. - Otherwise, it calls the corresponding constructor. See
java.new
.
- For an interface, this expects a table as the parameter and creates a proxy for it. See
clazz.class
returns ajobject
, wrapping an instance ofjava.lang.Class<clazz>
.
Integer = java.import('java.lang.Integer')
-- Accessing a static member
print(Integer.TYPE)
-- Calling a static method
print(Integer:parseInt('1024'))
-- Construct an instance
print(Integer('1024'))
-- Get a Class<Integer> instance
print(Integer.class:getName())
2
3
4
5
6
7
8
9
TIP
Don't confuse jclass
with an instance of java.lang.Class<?>
. The former one corresponds to Java classes, i.e., java.lang.String
in Java. The latter one is just a jobject
, i.e., java.lang.String.class
in Java.
jobject
type
For a jobject
object
:
object.memberVar
returns the public member namedmemberVar
.object.memberVar = value
assigns to the public static member. If exceptions occur, a Lua error is generated.object:memberMethod(...)
calls the public member methodmemberMethod
. See Proxied Method Calls for more info.
Integer = java.import('java.lang.Integer')
i = java.new(Integer, 1024)
-- Calling a method
print(i:toString())
2
3
4
jarray
type
For a jarray
array
:
array[i]
returnsarray[i - 1]
. Unlike Lua tables, we raise Lua errors if the index goes out of bounds.array[i] = value
assigns toarray[i - 1]
. If exceptions occur, a Lua error is generated.array:memberMethod(...)
calls the public member methodmemberMethod
(ofjava.lang.Object
of course), for example,array:getClass()
.
TIP
Lua tables usually start the index from 1, while Java arrays from 0.
java
module
Functions | Signature | Returns | Description |
---|---|---|---|
array | (jclass, dim1, ...) | jarray | Create an array with specified dimensions |
caught | () | jobject | Return the latest captured Java Throwable |
detach | (thread) | nil | Detach the sub-thread from registry to allow for GC |
import | (string) | jclass | table | Import a Java class or package |
loadlib | (string, string) | function | Load a Java method, similar to package.loadlib |
luaify | (jobject) | any | Convert an object to Lua types if possible |
method | (jobject, string, string) | function | Find a method |
new | (jclass, ...) | jobject | Call the constructor of the given Java type |
proxy | (string, ..., table) | jobject | Create an object with all calls proxied to a Lua table |
unwrap | (jobject) | table | Return the backing table of a proxy object |
There's more!
Actually, if you load the built-in package
library (either by Lua#openLibraries()
or Lua#openLibrary("package")
), you can use the Lua require
functions to load Java side things.
See Java-Side Modules for a brief introduction.
array (jclass, dim1, ...)
function
Creates a Java array.
Parameters:
jclass
: (jclass | jobject) The component type. One may pass ajclass
or ajobject
ofClass<?>
.dim1
: (number) The size of the first dimension.dim2
: (optional) (number) The size of the second dimension.dimN
: (optional) (number) The size of the N-th dimension.
Returns:
- (jarray)
new "jclass"[dim1][dim2]...[dimN]
- (jarray)
Generates a Lua error if types mismatch or some dimensions are negative.
int = java.import('int')
arr = java.array(int, 2, 16)
assert(#arr == 2)
assert(#arr[1] == 16)
2
3
4
caught ()
function
Return the latest captured Java java.lang.Throwable
during a Java method call.
Parameters:
- none
Returns:
(jobject) If some recent Java method call threw a
java.lang.Throwable
.(nil) No
Throwable
was thrown, or they were cleared.
detach (thread)
function
Detach the sub-thread from registry to allow for GC.
Parameters:
- thread The thread (e.g., a return value of
coroutine.create
)
- thread The thread (e.g., a return value of
Returns:
- (nil)
Generates a Lua error if the thread is a main thread.
Check before detaching
- Most often, you only want to use
java.detach
on threads created on the Lua side. - You need to ensure that proxies created on that thread is no longer used.
- If you are not creating tons of sub-threads, you can worry less about GC by letting
mainThread#close
handle it all instead of manuallydetach
ing.
Thread interface explained
In LuaJava, an AbstractLua
instance just wraps around a lua_State *
. We ensure that one lua_State *
maps to no more than one AbstractLua
instance by assigning each state an ID when:
- a main state is created;
- or when a sub-thread is created on the Java side (with
Lua#newThread
); - or when a sub-thread, created on the Lua side (with
coroutine.create
), eventually requests for an ID if it finds it necessary.
IDs are stored both on:
- the Java side: IDs are stored in
AbstractLua
instances. - and the Lua side: IDs are stored in the table at
LUA_REGISTRYINDEX
, with the thread itself as the key.
However, since we keep references to the thread in the LUA_REGISTRYINDEX
, it prevents the thread from garbage collection (which is intentional though, as you need threads alive for proxies).
If you are sure that neither the Java side (proxies, Java API, etc.) nor the Lua side uses the thread anymore, you may manually call java.detach
or Lua#close
to free the thread from the global registry.
import (name)
function
Import a Java class or package.
Parameters:
name
: (string) Either of the followingThe full name, including the package part, of the class.
Any string, appended with possibly multiple
.*
.
Returns:
(jclass) If
name
is the name of a class, return ajclass
of the class.(table) If
name
is a string appended with.*
, return a Lua table, which looks up classes directly under a package or inner classes inside a class when indexed. See the following example for details.
Generates a Lua error if class not found.
lang = java.import('java.lang.*')
print(lang.System:currentTimeMillis())
R = java.import('android.R.*')
print(R.id.input)
j = java.import('java.*.*')
print(j.lang.System:currentTimeMillis())
-- Both works
j = java.import('java.*')
print(j.lang.System:currentTimeMillis())
System = java.import('java.lang.System')
print(System:currentTimeMillis())
2
3
4
5
6
7
8
9
10
11
12
13
14
loadlib (classname, method)
function
This function provides similar functionalities to Lua's loadlib
. It looks for a method static public int yourSuppliedMethodName(Lua L);
inside the class, and returns it as a C function.
Parameters:
classname
: (string) The class name.method
: (string) The method name.- We expect the method to accept a single
Lua
parameter and return an integer.
- We expect the method to accept a single
Returns:
(function) If the method is found, we wrap it up with a C function wrapper and return it.
(nil, string) If no valid method is found, we return
nil
plus a error message. Similar topackage.loadlib
, we do not generate a Lua error in this case.
You might also want to check out Java-Side Modules to see how we use this function to extend the Lua require
.
package party.iroiro.luajava.docs;
import party.iroiro.luajava.Lua;
@SuppressWarnings("unused")
public class JavaSideExampleModule {
public static int open(Lua L) {
L.createTable(0, 1);
L.push(l -> {
l.push(1024);
return 1;
});
L.setField(-2, "getNumber");
return 1;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
try (Lua L = new Lua51()) {
L.openLibrary("package");
L.run("local LuaLib = require('party.iroiro.luajava.docs.JavaSideExampleModule.open');" +
"assert(1024 == LuaLib.getNumber())");
}
2
3
4
5
local LuaLibOpen = java.loadlib('party.iroiro.luajava.docs.JavaSideExampleModule', 'open')
assert(1024 == LuaLibOpen().getNumber())
2
luaify (jobject)
function
Converts a Java object into its Lua equivalence. It does a FULL
conversion. See Type Conversions for more information.
Parameters:
jobject
: (jobject) The object to get converted.
Returns:
- (boolean | integer | number | table | jclass) Depending on the Java type of
jobject
.- Notably, it converts
Map<?, ?>
andCollection<?>
to Lua tables, andClass<?>
tojclass
.
- Notably, it converts
- (boolean | integer | number | table | jclass) Depending on the Java type of
method (jobject, method[, signature])
function
Finds a method of the jobject
or jclass
matching the name and signature. See Method Resolution.
Parameters:
jobject
: (jobject | jclass) The object.method
: (string) The method name. Usenew
to refer to the constructor.For proxy object, it is possible to explicitly call the default methods in the interfaces. Use
complete.interface.name:methodName
to refer to the method. See the examples below.signature
: (optional) (string) Comma separated argument type list. If not supplied, treated as an empty one.
Returns:
- (function) Never
nil
. The real method lookup begins after you supply arguments to this returned function.
- (function) Never
AtomicInteger = java.import('java.util.concurrent.atomic.AtomicInteger')
Constructor = java.method(AtomicInteger, 'new', 'int')
integer = Constructor(100)
compareAndSet = java.method(integer, 'compareAndSet', 'int,int')
compareAndSet(100, 200)
compareAndSet(200, 400)
assert(integer:get() == 400)
iter = java.proxy('java.util.Iterator', {
remove = function(this)
java.method(iter, 'java.util.Iterator:remove')()
end
})
-- iter:remove() -- This throws an exception
2
3
4
5
6
7
8
9
10
11
12
13
14
new (jclass, ...)
function
Call the constructor of the given Java type.
Parameters:
jclass
: (jclass | jobject) The class. One may pass ajclass
or ajobject
ofClass<?>
....
: (any) Extra parameters are passed to the constructor. See also Type Conversions to find out how we locate a matching method.
Returns:
- (jobject) The created object.
Generates a Lua error if exceptions occur or unable to locate a matching constructor.
Examples:
String = java.import('java.lang.String')
-- new String ("This is the content of the String")
str = java.new(String, 'This is the content of the String')
assert(str:toString() == 'This is the content of the String')
2
3
4
5
proxy (jclass, ..., table)
function
Creates a Java object implementing the specified interfaces, proxying calls to the underlying Lua table. See also Proxy Caveats.
Parameters:
jclass1
: (jclass | string | jobject) The first interface. One may pass ajclass
or astring
or ajobject
ofClass<?>
.jclass2
: (jclass | string | jobject) The second interface.jclassN
: (jclass | string | jobject) The N-th interface.table
: (table | function)This parameter can be a table implementing the all the methods in the interfaces.
Or, if the interfaces sum up to a functional interface of wider sense (that is, we allow different signatures as long as they share the same name), an intermediate table will be created and back the actual proxy automatically.
Returns:
- (jobject) The created object.
Generates a Lua error if exceptions occur or unable to find the interfaces.
button = java.new(java.import('java.awt.Button'), 'Execute')
callback = {}
function callback:actionPerformed(ev)
-- do something
end
buttonProxy = java.proxy('java.awt.ActionListener', callback)
button:addActionListener(buttonProxy)
2
3
4
5
6
7
8
unwrap (jobject)
function
Return the backing table of a proxy object. See also Proxy Caveats.
Parameters:
jobject
: (jobject) The proxy object created withjava.proxy
orparty.iroiro.luajava.Lua#createProxy
Returns:
- (jobject) The backing Lua table of the Lua proxy.
Generates a Lua error if the object is not a Lua proxy object, or belongs to another irrelevant Lua state.
Proxied Method Calls
Java allows method overloading, which means we cannot know which method you are calling until you supply the parameters. Method finding and parameter supplying is an integrated (or, as one may call it, atomic) process in Java.
However, for calls in Lua, the two steps can get separated:
obj:method(param1)
-- The above is actually:
m = obj.method
m(obj, param1)
2
3
4
To proxy calls to Java, we treat all missing fields, such as obj.method
, obj.notAField
, obj.whatever
as a possible method call. The real resolution starts only after you supply the parameters.
The side effect of this is that a missing field is never nil
but always a possible function
call, so don't depend on this.
assert(type(jobject.notAField) == 'function')
Method resolution
In either case, if no method matches, a Lua error is raised.
With jobject:method(...)
For method resolution, see Type Conversions.
Since a Lua type maps to different Java types (for example, lua_Number
may be mapped to any Java numerical type), we have to iterate through every method to find one matching Lua parameters. For each possible method, we try to convert the values on stack from Lua to Java. If such conversion is possible, the call is then proxied to this method and the remaining methods are never tried.
WARNING
By the nature of this procedure, we do not prioritize any of the method.
For example, if you are calling java.lang.Math.max
, which can be Math.max(int, int)
, Math.max(double, double)
, etc., then nobody knows which will ever get called.
WARNING
We do not support varargs. You will need to combine java.method
and java.array
to make that happen.
For Object... object
however, things are easier:
String = java.import('java.lang.String')
-- We automatically convert lua tables into Object[]
assert(String:format('>>> %s', { 'content' }) == '>>> content')
2
3
With java.method
To help with precisely calling a specific method, we provide java.method
, to which you may specify the signature of the method that you intend to call.
TIP
Take the above java.lang.Math.max
as an example. You may call Math.max(int, int)
with the following:
Math = java.import('java.lang.Math')
max = java.method(Math, 'max', 'int,int')
assert(max(1.2, 2.3) == 2)
2
3
You may call Math.max(double, double)
with the following:
Math = java.import('java.lang.Math')
max = java.method(Math, 'max', 'double,double')
assert(max(1.2, 2.3) == 2.3)
2
3
If you would like to access an overridden default method from a proxy object, you may also use:
iter1 = java.proxy('java.util.Iterator', {})
-- Calls the default method
pcall(function() iter1:remove() end)
-- What if we want to access the default method from a overridden one?
iterImpl = {
next = function()
i = i - 1
return i
end,
hasNext = function()
return i > 0
end,
remove = function(this)
-- Calls the default method from java.util.Iterator.
java.method(this, 'java.util.Iterator:remove', '')()
-- Equivalent to the following in Java
-- Iterator.super.remove();
end
}
iter = java.proxy('java.util.Iterator', iterImpl)
-- Calls the implemented `remove`, which then calls the default one
pcall(function() iter:remove() end)
-- Or explicitly calling `remove`
pcall(function() java.method(iter, 'java.util.Iterator:remove')() end)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
WARNING
Calling default methods is not available with LuaJ bindings, since the Java reflection does not provide a way to do so. (We use JNI functions to achieve this within binary bindings.)