[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:
hartie95 2024-04-05 01:35:52 +02:00
parent 631cf4f080
commit 457378f83c
19 changed files with 790 additions and 50 deletions

View File

@ -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 {

View File

@ -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)
}
}

View 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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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());
}
}

View File

@ -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;
}
}
}

View File

@ -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) {
}
}
}
}

View File

@ -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);
}
}

View File

@ -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("")
}
}

View File

@ -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)
}
}

View File

@ -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) {

View File

@ -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
@ -16,4 +20,22 @@ fun InputStream.asSource(): Source {
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

View File

@ -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)

View File

@ -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