Proxy Caveats
WARNING
The following won't work when using LuaJ bindings.
Both the Java API and the Lua API provide a way to create Java proxies that delegate calls to an underlying Lua table.
The created Java object is passed as the first parameter to the member functions in Lua. If you want to get the backing Lua table, use java.unwrap
.
try (Lua L = new Lua54()) {
L.run("r = { run = function() print('Hello') end }; return r");
// With LuaValue API
Runnable r = L.get("r").toProxy(Runnable.class);
r.run();
// With stack-based API
Runnable s = (Runnable) L.createProxy(
new Class[]{Runnable.class},
Lua.Conversion.SEMI
);
s.run();
}
2
3
4
5
6
7
8
9
10
11
12
r = java.proxy('java.lang.Runnable', {
data = 'member data',
run = function(this)
assert(type(java.unwrap(this)) == 'table')
assert(java.unwrap(this).data == 'member data')
print('Hello')
end
})
r:run()
2
3
4
5
6
7
8
9
TL;DR
Things are finally working after all! Kudos to JNI.
Methods, if not implemented, will call the default methods in the interfaces. We also provide a default implementation of equals
, hashCode
and toString
.
Default Methods
Since Java 8, interfaces may choose to provide some default
methods.
As is mentioned above, methods, if not implemented in the provided Lua table, will call the default methods in the interfaces instead.
Moreover, we provide a way to explicitly call the default methods in the interface. See java.method
for more information.
Security Concerns
This is dangerous because it provides a way to (somehow) work around the Java security manager. This library does not sandbox Lua code currently, and JNI itself can be risky. Just don't execute unknown Lua code at all.
(Or you may open an issue on this if you consider sandboxing necessary.)
A Lua snippet to try things out
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
And Android...
TL;DR
- If you are creating a proxy implementing your own interfaces (i.e. not provided by Java runtime), you need a
minSdkVersion
of24
or higher. - If you are implementing interfaces provided by the Java runtime, it depends on the actual API level of the device that it runs on.
Default methods require API level 24 (Android 7.0). If your Lua code depend on some default methods, it not only means that the code will fail on API levels below, but also it will fail for all default methods implemented with Desugaring.
I am not going to detail on this. In short, if you have minSdkVersion
lower then 24
, your interfaces with default methods will get transformed into a normal interface and an abstract class containing the implementation:
// Written code:
interface DefaultedInterface { default int answer() { return 42; } }
// Transformed:
interface DefaultedInterface { int answer(); }
abstract class GeneratedAbstractBlahBlah implements DefaultedInterface {
int answer() { return 42; }
}
2
3
4
5
6
7
So any class Impl implements DefaultedInterface
is actually class Impl extends GeneratedAbstractBlahBlah
. Our proxy only implements the DefaultedInterface
, which has no default methods at all after desugaring, and knows nothing about GeneratedAbstractBlahBlah
. So it will fail.
Threads
Calling Lua#createProxy
actually means that the target function will be executed on that thread, on whose stack all the parameter conversions happen.
Don't close a thread with proxies still in use.