mirror of
https://github.com/Anime-Game-Servers/AnimeGamesLua.git
synced 2024-11-23 04:19:41 +00:00
[Fix] Fix LuaName being ignored for fields passed to the Lua engine
* Also added a test to verfiy it working and updated some other tests * Improved LuaValue support for values returned from lua function calls
This commit is contained in:
parent
631cf4f080
commit
457378f83c
@ -26,6 +26,9 @@ dependencies {
|
||||
compileOnly(libs.jvm.lombok)
|
||||
annotationProcessor(libs.jvm.lombok)
|
||||
implementation(libs.jvm.rtree.multi)
|
||||
testImplementation(project(":base"))
|
||||
testImplementation(project(":LuaJEngine"))
|
||||
testImplementation(project(":JNLuaEngine"))
|
||||
}
|
||||
tasks.withType<KotlinCompile>().configureEach {
|
||||
compilerOptions {
|
||||
|
@ -0,0 +1,150 @@
|
||||
package org.anime_game_servers.gi_lua.test.models
|
||||
|
||||
import kotlinx.io.Source
|
||||
import org.anime_game_servers.core.base.annotations.lua.LuaStatic
|
||||
import org.anime_game_servers.gi_lua.models.ScriptArgs
|
||||
import org.anime_game_servers.gi_lua.models.loader.GIScriptLoader
|
||||
import org.anime_game_servers.jnlua_engine.JNLuaEngine
|
||||
import org.anime_game_servers.lua.engine.*
|
||||
import org.anime_game_servers.lua.models.ScriptType
|
||||
import org.anime_game_servers.lua.utils.asSource
|
||||
import org.anime_game_servers.luaj_engine.LuaJEngine
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
data class LuaContext(val engine: LuaEngine)
|
||||
|
||||
@LuaStatic
|
||||
object KotlinFunctions{
|
||||
@JvmStatic
|
||||
fun checkScriptArgs(context:LuaContext, luaTable:Any) : Any{
|
||||
// retrieve the int array as lua table from the lua side
|
||||
val table = context.engine.getTable(luaTable)
|
||||
val array = table.getAsIntArray()
|
||||
val result = context.engine.createTable()
|
||||
|
||||
assert(array.size == 5)
|
||||
array.forEachIndexed { index, value ->
|
||||
assert(index+1 == value)
|
||||
result.set(index, value)
|
||||
}
|
||||
|
||||
// convert the int array back to a luaTable for lua and return it
|
||||
return result.getRawTable()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getScriptArgs(context:LuaContext) : Any{
|
||||
// retrieve the int array as lua table from the lua side
|
||||
/*val table = context.engine.getTable(luaTable)
|
||||
val x = table.optInt("x",0)
|
||||
val y = table.optInt("y",0)
|
||||
val z = table.optInt("z",0)*/
|
||||
|
||||
val result = context.engine.createTable()
|
||||
/*result.set("x", x)
|
||||
result.set("y", y)
|
||||
result.set("z", z)*/
|
||||
|
||||
// convert the int array back to a luaTable for lua and return it
|
||||
return result.getRawTable()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class TestScriptLoader : GIScriptLoader {
|
||||
abstract val engine: LuaEngine
|
||||
override fun getScriptPath(scriptName: String): Path? {
|
||||
val uri = ClassLoader.getSystemResource(scriptName).toURI()
|
||||
return Path.of(uri)
|
||||
}
|
||||
|
||||
override fun openScript(params: BaseScriptLoader.ScriptLoadParams): Source? {
|
||||
val basePath: String = params.getBasePath()
|
||||
val scriptPath = getScriptPath(basePath) ?: return null
|
||||
if(Files.exists(scriptPath)){
|
||||
return scriptPath.asSource()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getScript(scriptLoadParams: BaseScriptLoader.ScriptLoadParams): LuaScript? {
|
||||
val basePath: String = scriptLoadParams.getBasePath()
|
||||
val scriptPath = getScriptPath(basePath) ?: return null
|
||||
return engine.getScript(scriptPath, scriptLoadParams.getScriptType());
|
||||
}
|
||||
}
|
||||
|
||||
class LuaJTest : ScriptArgsTest() {
|
||||
|
||||
override val scriptLoader = object : TestScriptLoader(){
|
||||
override val engine: LuaEngine = LuaJEngine(ScriptConfig(this, RequireMode.DISABLED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runTest(){
|
||||
checkScriptArgs()
|
||||
}
|
||||
}
|
||||
|
||||
class JNLuaTest : ScriptArgsTest() {
|
||||
|
||||
override val scriptLoader = object : TestScriptLoader(){
|
||||
override val engine: LuaEngine = JNLuaEngine(ScriptConfig(this, RequireMode.DISABLED))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runTest(){
|
||||
checkScriptArgs()
|
||||
}
|
||||
}
|
||||
|
||||
abstract class ScriptArgsTest{
|
||||
abstract val scriptLoader : TestScriptLoader
|
||||
|
||||
fun getEngine(): LuaEngine {
|
||||
return scriptLoader.engine
|
||||
}
|
||||
|
||||
fun checkScriptArgs() {
|
||||
val engine = getEngine()
|
||||
LuaEngine.registerNamespace(this::class.java.packageName)
|
||||
engine.addGlobals()
|
||||
val uri = ClassLoader.getSystemResource("ScriptArgsTest.lua").toURI()
|
||||
val path = Path.of(uri);
|
||||
val script = engine.getScript(path, ScriptType.EXECUTABLE)
|
||||
|
||||
assert(script != null)
|
||||
|
||||
script!!.evaluate()
|
||||
val context = LuaContext(engine)
|
||||
val args = ScriptArgs(7,8).apply {
|
||||
param1 = 1
|
||||
param2 = 2
|
||||
param3 = 3
|
||||
param4 = 4
|
||||
sourceEid = 5
|
||||
targetEid = 6
|
||||
uid = 9
|
||||
paramString1 = "paramString1"
|
||||
source = "source"
|
||||
}
|
||||
|
||||
assert(script.hasMethod("testScriptArgs"))
|
||||
val luaValue = script.callMethod("testScriptArgs", context, args)!!
|
||||
assert(luaValue.isTable())
|
||||
val result = luaValue.asObject(ScriptArgs::class.java)
|
||||
assert(result != null)
|
||||
assert(result!!.param1 == args.param1)
|
||||
assert(result.param2 == args.param2)
|
||||
assert(result.param3 == args.param3)
|
||||
assert(result.param4 == args.param4)
|
||||
assert(result.sourceEid == args.sourceEid)
|
||||
assert(result.targetEid == args.targetEid)
|
||||
assert(result.groupId == args.groupId)
|
||||
assert(result.uid == args.uid)
|
||||
assert(result.type == args.type)
|
||||
assert(result.paramString1 == args.paramString1)
|
||||
assert(result.source == args.source)
|
||||
}
|
||||
}
|
39
GILua/src/test/resources/ScriptArgsTest.lua
Normal file
39
GILua/src/test/resources/ScriptArgsTest.lua
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
function testScriptArgs(ctx, evt)
|
||||
if(evt.param1 ~= 1) then
|
||||
return -1
|
||||
end
|
||||
if(evt.param2 ~= 2) then
|
||||
return -2
|
||||
end
|
||||
if(evt.param3 ~= 3) then
|
||||
return -3
|
||||
end
|
||||
if(evt.param4 ~= 4) then
|
||||
return -4
|
||||
end
|
||||
if(evt.source_eid ~= 5) then
|
||||
return -5
|
||||
end
|
||||
if(evt.target_eid ~= 6) then
|
||||
return -6
|
||||
end
|
||||
if(evt.group_id ~= 7) then
|
||||
return -7
|
||||
end
|
||||
if(evt.type ~= 8) then
|
||||
return -8
|
||||
end
|
||||
if(evt.uid ~= 9) then
|
||||
return -9
|
||||
end
|
||||
|
||||
if(evt.param_str1 ~= "paramString1") then
|
||||
return -10
|
||||
end
|
||||
if(evt.source_name ~= "source") then
|
||||
return -11
|
||||
end
|
||||
|
||||
return evt
|
||||
end
|
@ -0,0 +1,153 @@
|
||||
package org.anime_game_servers.jnlua_engine;
|
||||
|
||||
import io.github.oshai.kotlinlogging.KLogger;
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging;
|
||||
import lombok.Getter;
|
||||
import org.anime_game_servers.lua.utils.LuaHelpersJvmKt;
|
||||
import org.terasology.jnlua.*;
|
||||
|
||||
import java.beans.BeanInfo;
|
||||
import java.beans.IntrospectionException;
|
||||
import java.beans.Introspector;
|
||||
import java.beans.PropertyDescriptor;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.*;
|
||||
|
||||
public class JNLuaReflector extends DefaultJavaReflector {
|
||||
private static KLogger logger = KotlinLogging.INSTANCE.logger(JNLuaReflector.class.getName());
|
||||
@Getter
|
||||
private static final JNLuaReflector INSTANCE = new JNLuaReflector();
|
||||
|
||||
@Override
|
||||
protected Map<String, Accessor> createClassAccessors(Class<?> clazz) {
|
||||
Map<String, Accessor> result = new HashMap<String, Accessor>();
|
||||
|
||||
// Fields
|
||||
Field[] fields = clazz.getFields();
|
||||
for (int i = 0; i < fields.length; i++) {
|
||||
var field = fields[i];
|
||||
var luaNames = LuaHelpersJvmKt.getLuaNames(field);
|
||||
for (var luaName : luaNames) {
|
||||
result.put(luaName, new FieldAccessor(field));
|
||||
}
|
||||
}
|
||||
|
||||
// Methods
|
||||
Map<String, Map<List<Class<?>>, Invocable>> accessibleMethods = new HashMap<String, Map<List<Class<?>>, Invocable>>();
|
||||
Method[] methods = clazz.getMethods();
|
||||
for (int i = 0; i < methods.length; i++) {
|
||||
// Do not overwrite fields
|
||||
Method method = methods[i];
|
||||
if (result.containsKey(method.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt to find the method in a public class if the declaring
|
||||
// class is not public
|
||||
if (!Modifier.isPublic(method.getDeclaringClass().getModifiers())) {
|
||||
method = getPublicClassMethod(clazz, method.getName(),
|
||||
method.getParameterTypes());
|
||||
if (method == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// For each method name and parameter type list, keep
|
||||
// only the method declared by the most specific class
|
||||
Map<List<Class<?>>, Invocable> overloaded = accessibleMethods
|
||||
.get(method.getName());
|
||||
if (overloaded == null) {
|
||||
overloaded = new HashMap<List<Class<?>>, Invocable>();
|
||||
accessibleMethods.put(method.getName(), overloaded);
|
||||
}
|
||||
List<Class<?>> parameterTypes = Arrays.asList(method
|
||||
.getParameterTypes());
|
||||
Invocable currentInvocable = overloaded.get(parameterTypes);
|
||||
if (currentInvocable != null
|
||||
&& method.getDeclaringClass().isAssignableFrom(
|
||||
currentInvocable.getDeclaringClass())) {
|
||||
continue;
|
||||
}
|
||||
overloaded.put(parameterTypes, new InvocableMethod(method));
|
||||
}
|
||||
for (Map.Entry<String, Map<List<Class<?>>, Invocable>> entry : accessibleMethods
|
||||
.entrySet()) {
|
||||
result.put(entry.getKey(), new InvocableAccessor(clazz, entry
|
||||
.getValue().values()));
|
||||
}
|
||||
|
||||
// Constructors
|
||||
Constructor<?>[] constructors = clazz.getConstructors();
|
||||
List<Invocable> accessibleConstructors = new ArrayList<Invocable>(
|
||||
constructors.length);
|
||||
for (int i = 0; i < constructors.length; i++) {
|
||||
// Ignore constructor if the declaring class is not public
|
||||
if (!Modifier.isPublic(constructors[i].getDeclaringClass()
|
||||
.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
accessibleConstructors
|
||||
.add(new InvocableConstructor(constructors[i]));
|
||||
}
|
||||
if (clazz.isInterface()) {
|
||||
accessibleConstructors.add(new InvocableProxy(clazz));
|
||||
}
|
||||
if (!accessibleConstructors.isEmpty()) {
|
||||
result.put("new", new InvocableAccessor(clazz,
|
||||
accessibleConstructors));
|
||||
}
|
||||
|
||||
// Properties
|
||||
BeanInfo beanInfo;
|
||||
try {
|
||||
beanInfo = Introspector.getBeanInfo(clazz);
|
||||
} catch (IntrospectionException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
PropertyDescriptor[] propertyDescriptors = beanInfo
|
||||
.getPropertyDescriptors();
|
||||
for (int i = 0; i < propertyDescriptors.length; i++) {
|
||||
// Do not overwrite fields or methods
|
||||
if (result.containsKey(propertyDescriptors[i].getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Attempt to find the read/write methods in a public class if the
|
||||
// declaring class is not public
|
||||
Method method = propertyDescriptors[i].getReadMethod();
|
||||
if (method != null
|
||||
&& !Modifier.isPublic(method.getDeclaringClass()
|
||||
.getModifiers())) {
|
||||
method = getPublicClassMethod(clazz, method.getName(),
|
||||
method.getParameterTypes());
|
||||
try {
|
||||
propertyDescriptors[i].setReadMethod(method);
|
||||
} catch (IntrospectionException e) {
|
||||
}
|
||||
}
|
||||
method = propertyDescriptors[i].getWriteMethod();
|
||||
if (method != null
|
||||
&& !Modifier.isPublic(method.getDeclaringClass()
|
||||
.getModifiers())) {
|
||||
method = getPublicClassMethod(clazz, method.getName(),
|
||||
method.getParameterTypes());
|
||||
try {
|
||||
propertyDescriptors[i].setWriteMethod(method);
|
||||
} catch (IntrospectionException e) {
|
||||
}
|
||||
}
|
||||
|
||||
// Do not process properties without a read and a write method
|
||||
if (propertyDescriptors[i].getReadMethod() == null
|
||||
&& propertyDescriptors[i].getWriteMethod() == null) {
|
||||
continue;
|
||||
}
|
||||
result.put(propertyDescriptors[i].getName(), new PropertyAccessor(
|
||||
clazz, propertyDescriptors[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -8,10 +8,7 @@ import org.anime_game_servers.lua.engine.LuaEngine;
|
||||
import org.anime_game_servers.lua.engine.LuaScript;
|
||||
import org.anime_game_servers.lua.engine.LuaValue;
|
||||
import org.anime_game_servers.lua.engine.RequireMode;
|
||||
import org.anime_game_servers.lua.models.BooleanLuaValue;
|
||||
import org.anime_game_servers.lua.models.IntLuaValue;
|
||||
import org.anime_game_servers.lua.models.MutableBoolean;
|
||||
import org.anime_game_servers.lua.models.ScriptType;
|
||||
import org.anime_game_servers.lua.models.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.terasology.jnlua.script.CompiledLuaScript;
|
||||
@ -51,6 +48,7 @@ public class JNLuaScript implements LuaScript {
|
||||
context.setBindings(binding, ScriptContext.ENGINE_SCOPE);
|
||||
val luaState = binding.getLuaState();
|
||||
luaState.setConverter(JNLuaConverter.getINSTANCE());
|
||||
luaState.setJavaReflector(JNLuaReflector.getINSTANCE());
|
||||
|
||||
val requireFunction = JNLuaRequireCommonFunction.getInstance(engine.getScriptConfig());
|
||||
binding.put(requireFunction.getName(), requireFunction);
|
||||
@ -121,13 +119,11 @@ public class JNLuaScript implements LuaScript {
|
||||
@Override
|
||||
public LuaValue callMethod(@NotNull String methodName, Object... args) throws ScriptException, NoSuchMethodException {
|
||||
val result = ((Invocable) scriptEngine).invokeFunction(methodName, args);
|
||||
if (result instanceof Boolean) {
|
||||
return ((Boolean) result) ? BooleanLuaValue.TRUE : BooleanLuaValue.FALSE;
|
||||
} else if (result instanceof Integer) {
|
||||
return new IntLuaValue((Integer) result);
|
||||
if (result instanceof Boolean boolVal) {
|
||||
return boolVal ? BooleanLuaValue.TRUE : BooleanLuaValue.FALSE;
|
||||
}
|
||||
//TODO
|
||||
return null;
|
||||
|
||||
return new JNLuaValue(engine, result);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,104 @@
|
||||
package org.anime_game_servers.jnlua_engine
|
||||
|
||||
import org.anime_game_servers.lua.engine.LuaValue
|
||||
|
||||
class JNLuaValue internal constructor(private val engine: JNLuaEngine, private val value: Any?) : LuaValue {
|
||||
override fun isNull(): Boolean {
|
||||
return value == null
|
||||
}
|
||||
|
||||
override fun isBoolean(): Boolean {
|
||||
return value is Boolean
|
||||
}
|
||||
|
||||
override fun isInteger(): Boolean {
|
||||
return value is Int
|
||||
}
|
||||
|
||||
override fun isLong(): Boolean {
|
||||
return value is Long
|
||||
}
|
||||
|
||||
override fun isDouble(): Boolean {
|
||||
return value is Double
|
||||
}
|
||||
|
||||
override fun isFloat(): Boolean {
|
||||
return value is Float
|
||||
}
|
||||
|
||||
override fun isString(): Boolean {
|
||||
return value is String
|
||||
}
|
||||
|
||||
override fun isTable(): Boolean {
|
||||
return value is Map<*, *> || value is JNLuaTable || value is Collection<*> || value != null && value !is Number && value !is Boolean && value !is String
|
||||
}
|
||||
|
||||
override fun asBoolean(): Boolean {
|
||||
return when(value){
|
||||
is Boolean -> value
|
||||
is Number -> value.toInt() != 0
|
||||
is String -> value.toBoolean()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun asInteger(): Int {
|
||||
return when(value){
|
||||
is Number -> value.toInt()
|
||||
is Boolean -> if(value) 1 else 0
|
||||
is String -> value.toInt()
|
||||
else -> 0
|
||||
}
|
||||
}
|
||||
|
||||
override fun asLong(): Long {
|
||||
return when(value){
|
||||
is Number -> value.toLong()
|
||||
is Boolean -> if(value) 1L else 0L
|
||||
is String -> value.toLong()
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
|
||||
override fun asDouble(): Double {
|
||||
return when(value){
|
||||
is Number -> value.toDouble()
|
||||
is Boolean -> if(value) 1.0 else 0.0
|
||||
is String -> value.toDouble()
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
|
||||
override fun asFloat(): Float {
|
||||
return when(value){
|
||||
is Number -> value.toFloat()
|
||||
is Boolean -> if(value) 1f else 0f
|
||||
is String -> value.toFloat()
|
||||
else -> 0f
|
||||
}
|
||||
}
|
||||
|
||||
override fun asString(): String? {
|
||||
return when(value){
|
||||
is String -> value
|
||||
is Number -> value.toString()
|
||||
is Boolean -> value.toString()
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T> asObject(type: Class<T>): T? {
|
||||
if (value == null || !isTable()) {
|
||||
return null
|
||||
}
|
||||
|
||||
if(type.isAssignableFrom(value.javaClass)){
|
||||
return value as T
|
||||
}
|
||||
|
||||
// TODO expects LuaValueProxy
|
||||
return engine.serializer.toObject(type, value)
|
||||
}
|
||||
}
|
@ -5,8 +5,8 @@ import kotlinx.io.asInputStream
|
||||
import kotlinx.io.buffered
|
||||
import org.anime_game_servers.lua.engine.*
|
||||
import org.anime_game_servers.lua.models.ScriptType
|
||||
import org.anime_game_servers.luaj_engine.coerse.CoerceJavaToLua
|
||||
import org.luaj.vm2.lib.ResourceFinder
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua
|
||||
import org.luaj.vm2.script.LuajContext
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.IOException
|
||||
|
@ -3,7 +3,6 @@ package org.anime_game_servers.luaj_engine;
|
||||
import io.github.oshai.kotlinlogging.KLogger;
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging;
|
||||
import kotlin.Pair;
|
||||
import kotlin.text.Regex;
|
||||
import lombok.val;
|
||||
import org.anime_game_servers.lua.engine.LuaEngine;
|
||||
import org.anime_game_servers.lua.engine.LuaScript;
|
||||
@ -12,7 +11,7 @@ import org.anime_game_servers.lua.engine.RequireMode;
|
||||
import org.anime_game_servers.lua.models.BooleanLuaValue;
|
||||
import org.anime_game_servers.lua.models.MutableBoolean;
|
||||
import org.anime_game_servers.lua.models.ScriptType;
|
||||
import org.luaj.vm2.lib.jse.CoerceJavaToLua;
|
||||
import org.anime_game_servers.luaj_engine.coerse.CoerceJavaToLua;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.script.Bindings;
|
||||
@ -24,7 +23,6 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public class LuaJScript implements LuaScript {
|
||||
|
@ -48,7 +48,7 @@ public class LuaJValue implements LuaValue {
|
||||
|
||||
@Override
|
||||
public boolean isTable() {
|
||||
return value.istable();
|
||||
return value.istable() || value.isuserdata();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -83,10 +83,14 @@ public class LuaJValue implements LuaValue {
|
||||
|
||||
@Override
|
||||
public <T> T asObject(Class<T> type) {
|
||||
if (!value.istable()) {
|
||||
if (!value.istable() && !value.isuserdata()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if(value.isuserdata()) {
|
||||
return (T) value.checkuserdata(type);
|
||||
}
|
||||
|
||||
return engine.getSerializer().toObject(type, value.checktable());
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,170 @@
|
||||
package org.anime_game_servers.luaj_engine.coerse;
|
||||
|
||||
import org.luaj.vm2.*;
|
||||
import org.luaj.vm2.lib.jse.JavaArray;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Helper class to coerce values from Java to lua within the luajava library.
|
||||
* <p>
|
||||
* This class is primarily used by the {@link org.luaj.vm2.lib.jse.LuajavaLib},
|
||||
* but can also be used directly when working with Java/lua bindings.
|
||||
* <p>
|
||||
* To coerce scalar types, the various, generally the {@code valueOf(type)} methods
|
||||
* on {@link LuaValue} may be used:
|
||||
* <ul>
|
||||
* <li>{@link LuaValue#valueOf(boolean)}</li>
|
||||
* <li>{@link LuaValue#valueOf(byte[])}</li>
|
||||
* <li>{@link LuaValue#valueOf(double)}</li>
|
||||
* <li>{@link LuaValue#valueOf(int)}</li>
|
||||
* <li>{@link LuaValue#valueOf(String)}</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* To coerce arrays of objects and lists, the {@code listOf(..)} and {@code tableOf(...)} methods
|
||||
* on {@link LuaValue} may be used:
|
||||
* <ul>
|
||||
* <li>{@link LuaValue#listOf(LuaValue[])}</li>
|
||||
* <li>{@link LuaValue#listOf(LuaValue[], org.luaj.vm2.Varargs)}</li>
|
||||
* <li>{@link LuaValue#tableOf(LuaValue[])}</li>
|
||||
* <li>{@link LuaValue#tableOf(LuaValue[], LuaValue[], org.luaj.vm2.Varargs)}</li>
|
||||
* </ul>
|
||||
* The method {@link org.luaj.vm2.lib.jse.CoerceJavaToLua#coerce(Object)} looks as the type and dimesioning
|
||||
* of the argument and tries to guess the best fit for corrsponding lua scalar,
|
||||
* table, or table of tables.
|
||||
*
|
||||
* @see org.luaj.vm2.lib.jse.CoerceJavaToLua#coerce(Object)
|
||||
* @see org.luaj.vm2.lib.jse.LuajavaLib
|
||||
*/
|
||||
public class CoerceJavaToLua {
|
||||
|
||||
static final Map<Class<?>, Coercion> COERCIONS = Collections.synchronizedMap(new HashMap<>());
|
||||
|
||||
static final Coercion instanceCoercion = new InstanceCoercion();
|
||||
static final Coercion arrayCoercion = new ArrayCoercion();
|
||||
static final Coercion luaCoercion = new LuaCoercion();
|
||||
|
||||
static {
|
||||
Coercion boolCoercion = new BoolCoercion();
|
||||
Coercion intCoercion = new IntCoercion();
|
||||
Coercion charCoercion = new CharCoercion();
|
||||
Coercion doubleCoercion = new DoubleCoercion();
|
||||
Coercion stringCoercion = new StringCoercion();
|
||||
Coercion bytesCoercion = new BytesCoercion();
|
||||
Coercion classCoercion = new ClassCoercion();
|
||||
COERCIONS.put(Boolean.class, boolCoercion);
|
||||
COERCIONS.put(Byte.class, intCoercion);
|
||||
COERCIONS.put(Character.class, charCoercion);
|
||||
COERCIONS.put(Short.class, intCoercion);
|
||||
COERCIONS.put(Integer.class, intCoercion);
|
||||
COERCIONS.put(Long.class, doubleCoercion);
|
||||
COERCIONS.put(Float.class, doubleCoercion);
|
||||
COERCIONS.put(Double.class, doubleCoercion);
|
||||
COERCIONS.put(String.class, stringCoercion);
|
||||
COERCIONS.put(byte[].class, bytesCoercion);
|
||||
COERCIONS.put(Class.class, classCoercion);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerse a Java object to a corresponding lua value.
|
||||
* <p>
|
||||
* Integral types {@code boolean}, {@code byte}, {@code char}, and {@code int}
|
||||
* will become {@link LuaInteger};
|
||||
* {@code long}, {@code float}, and {@code double} will become {@link LuaDouble};
|
||||
* {@code String} and {@code byte[]} will become {@link LuaString};
|
||||
* types inheriting from {@link LuaValue} will be returned without coercion;
|
||||
* other types will become {@link LuaUserdata}.
|
||||
*
|
||||
* @param o Java object needing conversion
|
||||
* @return {@link LuaValue} corresponding to the supplied Java value.
|
||||
* @see LuaValue
|
||||
* @see LuaInteger
|
||||
* @see LuaDouble
|
||||
* @see LuaString
|
||||
* @see LuaUserdata
|
||||
*/
|
||||
public static LuaValue coerce(Object o) {
|
||||
if (o == null)
|
||||
return LuaValue.NIL;
|
||||
Class<?> clazz = o.getClass();
|
||||
Coercion c = COERCIONS.get(clazz);
|
||||
if (c == null) {
|
||||
c = clazz.isArray() ? arrayCoercion :
|
||||
o instanceof LuaValue ? luaCoercion :
|
||||
instanceCoercion;
|
||||
COERCIONS.put(clazz, c);
|
||||
}
|
||||
return c.coerce(o);
|
||||
}
|
||||
|
||||
interface Coercion {
|
||||
LuaValue coerce(Object javaValue);
|
||||
}
|
||||
|
||||
private static final class BoolCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
boolean b = (Boolean) javaValue;
|
||||
return b ? LuaValue.TRUE : LuaValue.FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IntCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
Number n = (Number) javaValue;
|
||||
return LuaInteger.valueOf(n.intValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class CharCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
Character c = (Character) javaValue;
|
||||
return LuaInteger.valueOf(c);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class DoubleCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
Number n = (Number) javaValue;
|
||||
return LuaDouble.valueOf(n.doubleValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class StringCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
return LuaString.valueOf(javaValue.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class BytesCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
return LuaValue.valueOf((byte[]) javaValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ClassCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
return JavaClass.forClass((Class<?>) javaValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class InstanceCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
return new JavaInstance(javaValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ArrayCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
// should be userdata?
|
||||
return new JavaArray(javaValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class LuaCoercion implements Coercion {
|
||||
public LuaValue coerce(Object javaValue) {
|
||||
return (LuaValue) javaValue;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package org.anime_game_servers.luaj_engine.coerse;
|
||||
|
||||
import org.anime_game_servers.lua.utils.LuaHelpersJvmKt;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class JavaClass extends org.luaj.vm2.lib.jse.JavaClass {
|
||||
|
||||
protected static final Map<Class<?>, JavaClass> classes = Collections.synchronizedMap(new HashMap<>());
|
||||
JavaClass(Class c) {
|
||||
super(c);
|
||||
}
|
||||
|
||||
public static JavaClass forClass(Class<?> c) {
|
||||
return classes.computeIfAbsent(c, JavaClass::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addField(Field fi, Map<LuaValue, Field> m) {
|
||||
if (Modifier.isPublic(fi.getModifiers())) {
|
||||
var names = LuaHelpersJvmKt.getLuaNames(fi);
|
||||
for (var name : names) {
|
||||
m.put(LuaValue.valueOf(name), fi);
|
||||
}
|
||||
try {
|
||||
if (!fi.isAccessible())
|
||||
fi.setAccessible(true);
|
||||
} catch (SecurityException s) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package org.anime_game_servers.luaj_engine.coerse;
|
||||
|
||||
import org.luaj.vm2.LuaError;
|
||||
import org.luaj.vm2.LuaUserdata;
|
||||
import org.luaj.vm2.LuaValue;
|
||||
import org.luaj.vm2.lib.jse.CoerceLuaToJava;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
public class JavaInstance extends LuaUserdata {
|
||||
JavaClass jclass;
|
||||
|
||||
public JavaInstance(Object instance) {
|
||||
super(instance);
|
||||
}
|
||||
|
||||
public LuaValue get(LuaValue key) {
|
||||
if (jclass == null)
|
||||
jclass = JavaClass.forClass(m_instance.getClass());
|
||||
Field f = jclass.getField(key);
|
||||
if (f != null)
|
||||
try {
|
||||
return CoerceJavaToLua.coerce(f.get(m_instance));
|
||||
} catch (Exception e) {
|
||||
throw new LuaError(e);
|
||||
}
|
||||
LuaValue m = jclass.getMethod(key);
|
||||
if (m != null)
|
||||
return m;
|
||||
Class<?> c = jclass.getInnerClass(key);
|
||||
if (c != null)
|
||||
return JavaClass.forClass(c);
|
||||
return super.get(key);
|
||||
}
|
||||
|
||||
public void set(LuaValue key, LuaValue value) {
|
||||
if (jclass == null)
|
||||
jclass = JavaClass.forClass(m_instance.getClass());
|
||||
Field f = jclass.getField(key);
|
||||
if (f != null)
|
||||
try {
|
||||
f.set(m_instance, CoerceLuaToJava.coerce(value, f.getType()));
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
throw new LuaError(e);
|
||||
}
|
||||
super.set(key, value);
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.anime_game_servers.lua.models
|
||||
|
||||
import org.anime_game_servers.lua.engine.Class
|
||||
import kotlin.jvm.JvmField
|
||||
|
||||
open class StringLuaValue(protected val value: String) : MockLuaValue {
|
||||
override fun isInteger() = true
|
||||
|
||||
override fun asBoolean() = when(value.lowercase()){
|
||||
"true" -> true
|
||||
"false" -> false
|
||||
else -> try {
|
||||
value.toInt() != 0
|
||||
} catch (e: NumberFormatException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
override fun asInteger() = value.toInt()
|
||||
override fun asLong() = value.toLong()
|
||||
override fun asDouble() = value.toDouble()
|
||||
override fun asFloat() = value.toFloat()
|
||||
override fun asString() = value
|
||||
|
||||
override fun <T> asObject(type: Class<T>): T? = null
|
||||
|
||||
companion object {
|
||||
@JvmField
|
||||
val EMPTY = StringLuaValue("")
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import org.anime_game_servers.core.base.interfaces.IntKey
|
||||
import org.anime_game_servers.core.base.interfaces.IntValueEnum
|
||||
import org.anime_game_servers.lua.models.ScriptType
|
||||
import org.anime_game_servers.lua.serialize.Serializer
|
||||
import org.anime_game_servers.lua.utils.getLuaName
|
||||
import org.reflections.Reflections
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -44,19 +45,13 @@ interface LuaEngine {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLuaName(classObject: Class<*>): String {
|
||||
return classObject.getAnnotation(LuaNames::class.java)?.let {
|
||||
it.value.firstOrNull() ?: classObject.simpleName
|
||||
} ?: classObject.simpleName
|
||||
}
|
||||
|
||||
fun addGlobals() {
|
||||
registeredEnums.forEach { enumClass ->
|
||||
val enumArray = enumClass.enumConstants
|
||||
addGlobalEnum(getLuaName(enumClass), enumArray)
|
||||
addGlobalEnum(enumClass.getLuaName(), enumArray)
|
||||
}
|
||||
registeredStaticClasses.forEach { staticClass ->
|
||||
addGlobalStaticClass(getLuaName(staticClass), staticClass)
|
||||
addGlobalStaticClass(staticClass.getLuaName(), staticClass)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import com.esotericsoftware.reflectasm.ConstructorAccess
|
||||
import com.esotericsoftware.reflectasm.MethodAccess
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging.logger
|
||||
import org.anime_game_servers.core.base.annotations.lua.LuaNames
|
||||
import org.anime_game_servers.lua.utils.getLuaNames
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.ParameterizedType
|
||||
import java.lang.reflect.TypeVariable
|
||||
@ -47,7 +48,7 @@ abstract class BaseSerializer : Serializer {
|
||||
Arrays.stream(classtype.declaredFields)
|
||||
.forEach { field: Field ->
|
||||
val name = field.name
|
||||
val luaNames = getLuaNames(field)
|
||||
val luaNames = field.getLuaNames()
|
||||
val fieldMeta = if (methodNameSet.contains(getSetterName(name))) {
|
||||
val setter = getSetterName(name)
|
||||
val index = methodAccess.getIndex(setter)
|
||||
@ -68,7 +69,7 @@ abstract class BaseSerializer : Serializer {
|
||||
.filter { field: Field -> methodNameSet.contains(getSetterName(field.name)) }
|
||||
.forEach { field: Field ->
|
||||
val name = field.name
|
||||
val luaNames = getLuaNames(field)
|
||||
val luaNames = field.getLuaNames()
|
||||
val setter = getSetterName(name)
|
||||
val index = methodAccess.getIndex(setter)
|
||||
val fieldMeta = FieldMeta(name, luaNames, setter, index, field.type, field)
|
||||
@ -81,18 +82,6 @@ abstract class BaseSerializer : Serializer {
|
||||
return fieldMetaMap
|
||||
}
|
||||
|
||||
protected fun getLuaNames(field: Field): List<String> {
|
||||
val luaName: MutableList<String> = mutableListOf(field.name)
|
||||
field.annotations
|
||||
.filterIsInstance<LuaNames>()
|
||||
.forEach { luaNames: LuaNames ->
|
||||
if (luaNames.value.isNotEmpty()) {
|
||||
luaName.addAll(luaNames.value)
|
||||
}
|
||||
}
|
||||
return luaName
|
||||
}
|
||||
|
||||
protected fun set(`object`: Any?, @Nonnull fieldMeta: FieldMeta, methodAccess: MethodAccess?, value: Int) {
|
||||
try {
|
||||
if (methodAccess != null && fieldMeta.setter != null) {
|
||||
|
@ -3,8 +3,12 @@ package org.anime_game_servers.lua.utils
|
||||
import kotlinx.io.Source
|
||||
import kotlinx.io.asSource
|
||||
import kotlinx.io.buffered
|
||||
import org.anime_game_servers.core.base.annotations.lua.LuaNames
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.lang.reflect.AnnotatedElement
|
||||
import java.lang.reflect.Field
|
||||
import java.lang.reflect.Member
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
|
||||
@ -17,3 +21,21 @@ fun Path.asSource(): Source {
|
||||
val stream = Files.newInputStream(this)
|
||||
return stream.asSource().buffered()
|
||||
}
|
||||
|
||||
fun Member.getLuaNames(): List<String> {
|
||||
val luaName: MutableList<String> = mutableListOf(name)
|
||||
(this as? AnnotatedElement)?.let {
|
||||
annotations
|
||||
.filterIsInstance<LuaNames>()
|
||||
.forEach { luaNames: LuaNames ->
|
||||
if (luaNames.value.isNotEmpty()) {
|
||||
luaName.addAll(luaNames.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return luaName
|
||||
}
|
||||
|
||||
fun Class<*>.getLuaName()= getAnnotation(LuaNames::class.java)?.let {
|
||||
it.value.firstOrNull() ?: simpleName
|
||||
} ?: simpleName
|
@ -133,11 +133,12 @@ abstract class ParsingTest{
|
||||
val context = LuaContext(engine)
|
||||
|
||||
assert(script.hasMethod("expectIntArray"))
|
||||
val luaValue = script.callMethod("expectIntArray", context)
|
||||
luaValue?.isLong()
|
||||
val luaValue = script.callMethod("expectIntArray", context)!!
|
||||
assert(luaValue.isTable())
|
||||
|
||||
assert(script.hasMethod("expectObjectTable"))
|
||||
val objectTableResult = script.callMethod("expectObjectTable", context)
|
||||
objectTableResult?.isLong()
|
||||
val objectTableResult = script.callMethod("expectObjectTable", context)!!
|
||||
assert(objectTableResult.isTable())
|
||||
|
||||
// TODO test nested tables
|
||||
// TODO test mixed tables (int+string keys)
|
||||
|
@ -5,7 +5,7 @@ function expectIntArray(context)
|
||||
local array = KotlinFunctions.expectIntArray(context, param)
|
||||
for i,v in ipairs(array) do
|
||||
if v == Nil then
|
||||
return Nil
|
||||
return i
|
||||
end
|
||||
end
|
||||
return array
|
||||
@ -17,14 +17,14 @@ function expectObjectTable(context)
|
||||
local z = 3
|
||||
local result = KotlinFunctions.expectObjectTable(context, {x=x, y=y, z=z})
|
||||
|
||||
if(result.x ~= x) then
|
||||
return Nil
|
||||
if result.x ~= x then
|
||||
return "X"
|
||||
end
|
||||
if(result.y ~= y) then
|
||||
return Nil
|
||||
if result.y ~= y then
|
||||
return "Y"
|
||||
end
|
||||
if(result.z ~= z) then
|
||||
return Nil
|
||||
if result.z ~= z then
|
||||
return "Z"
|
||||
end
|
||||
|
||||
return result
|
||||
|
Loading…
Reference in New Issue
Block a user