├── .gitignore ├── LICENSE ├── README.md ├── app ├── build.gradle.kts ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── logback.xml │ └── lua │ │ ├── TestErrorMessageForJavaMethodCall.lua │ │ └── setup.lua │ ├── java │ └── com │ │ └── bennyhuo │ │ └── luajavax │ │ └── sample │ │ ├── MainActivity.kt │ │ └── cases │ │ └── LuaTests.kt │ └── res │ ├── layout │ ├── activity_jnitest.xml │ └── activity_main.xml │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── luajava ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── org │ │ │ └── keplerproject │ │ │ └── luajava │ │ │ ├── CPtr.java │ │ │ ├── Console.java │ │ │ ├── JavaFunction.java │ │ │ ├── LuaException.java │ │ │ ├── LuaInvocationHandler.java │ │ │ ├── LuaJavaAPI.java │ │ │ ├── LuaObject.java │ │ │ ├── LuaState.java │ │ │ └── LuaStateFactory.java │ └── jni │ │ ├── CMakeLists.txt │ │ ├── lua │ │ ├── Makefile │ │ ├── lapi.c │ │ ├── lapi.h │ │ ├── lauxlib.c │ │ ├── lauxlib.h │ │ ├── lbaselib.c │ │ ├── lbitlib.c │ │ ├── lcode.c │ │ ├── lcode.h │ │ ├── lcorolib.c │ │ ├── lctype.c │ │ ├── lctype.h │ │ ├── ldblib.c │ │ ├── ldebug.c │ │ ├── ldebug.h │ │ ├── ldo.c │ │ ├── ldo.h │ │ ├── ldump.c │ │ ├── lfunc.c │ │ ├── lfunc.h │ │ ├── lgc.c │ │ ├── lgc.h │ │ ├── linit.c │ │ ├── liolib.c │ │ ├── llex.c │ │ ├── llex.h │ │ ├── llimits.h │ │ ├── lmathlib.c │ │ ├── lmem.c │ │ ├── lmem.h │ │ ├── loadlib.c │ │ ├── lobject.c │ │ ├── lobject.h │ │ ├── lopcodes.c │ │ ├── lopcodes.h │ │ ├── loslib.c │ │ ├── lparser.c │ │ ├── lparser.h │ │ ├── lprefix.h │ │ ├── lstate.c │ │ ├── lstate.h │ │ ├── lstring.c │ │ ├── lstring.h │ │ ├── lstrlib.c │ │ ├── ltable.c │ │ ├── ltable.h │ │ ├── ltablib.c │ │ ├── ltm.c │ │ ├── ltm.h │ │ ├── lua.c │ │ ├── lua.h │ │ ├── lua.hpp │ │ ├── luac.c │ │ ├── luaconf.h │ │ ├── lualib.h │ │ ├── lundump.c │ │ ├── lundump.h │ │ ├── lutf8lib.c │ │ ├── lvm.c │ │ ├── lvm.h │ │ ├── lzio.c │ │ └── lzio.h │ │ └── luajava │ │ ├── log.h │ │ ├── luajava.c │ │ └── luajava.h │ └── test │ └── java │ └── org │ └── keplerproject │ └── luajava │ └── ExampleUnitTest.java ├── luajavax ├── build.gradle.kts ├── proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── bennyhuo │ │ │ └── luajavax │ │ │ ├── LuaFactory.kt │ │ │ ├── annotations │ │ │ └── LuaApi.java │ │ │ ├── core │ │ │ ├── DummyLua.kt │ │ │ ├── ILua.kt │ │ │ └── Lua.kt │ │ │ ├── log │ │ │ └── AndroidLog.kt │ │ │ └── utils │ │ │ └── IO.kt │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── com │ └── bennyhuo │ └── luajavax │ └── ExampleUnitTest.java └── settings.gradle.kts /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .gradle 3 | .idea 4 | local.properties 5 | captures 6 | .cxx 7 | .externalNativeBuild 8 | out 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bennyhuo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) 2 | 3 | 4 | # Android-Luajavax 5 | 6 | * Lua 5.3.3 7 | * LuaJava with some bug fixes. 8 | * LuaJavax with Kotlin APIs. 9 | 10 | # How to use 11 | 12 | ## Import 13 | 14 | I have deployed these modules to maven central, you may add this in your build.gradle: 15 | 16 | ``` 17 | implementation("com.bennyhuo:luajavax:1.0") 18 | ``` 19 | 20 | ### SNAPSHOT 21 | 22 | If you want to try the dev version, add this to your build.gradle: 23 | 24 | ``` 25 | repositories { 26 | maven { 27 | url "https://oss.sonatype.org/service/local/repositories/snapshots/" 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation("com.bennyhuo:luajavax:1.0-SNAPSHOT") 33 | } 34 | ``` 35 | 36 | ## API 37 | 38 | You can create a Lua state machine via `LuaFactory`: 39 | 40 | ```kotlin 41 | object LuaFactory { 42 | 43 | @JvmStatic 44 | fun createLua(context: Context): ILua = Lua(context) 45 | 46 | } 47 | ``` 48 | 49 | The `ILua` instance is what you need to execute Lua scripts. See its APIs here: [ILua](luajavax/src/main/java/com/bennyhuo/luajavax/core/ILua.kt). 50 | 51 | You can run Lua scripts from string/file/assets directly. It is easy to access Java class and objects. 52 | 53 | ```kotlin 54 | LuaFactory.createLua(this@MainActivity).use { lua -> 55 | lua["Hello"] = Hello::class.java // set global value 56 | // run script, access to Java class 57 | lua.runText("hello = luajava.new(Hello); hello:a()") 58 | lua.runFile(File(getExternalFilesDir("lua"), "hello_world.lua")) 59 | lua.runScriptInAssets("hello_world.lua") 60 | } 61 | ``` 62 | 63 | You can redirect stdout to any file you want: 64 | 65 | ```kotlin 66 | lua.setOutput(File(getExternalFilesDir("lua_output"), "hello_world.lua.output") 67 | ``` 68 | 69 | The default behavior of Lua function `print` is to write content to the stdout, which does not work in Logcat. So you can use `redirectStdioToLogcat` to redirect `print` to Logcat with a Tag 'luajavax': 70 | 71 | ```kotlin 72 | LuaFactory.createLua(this).use { lua -> 73 | // redirect 'print' and 'print_error' to logcat. 74 | // It take no effects on 'io.write' or 'io.stdout.write' or 'io.stderr.write'. 75 | lua.redirectStdioToLogcat() 76 | lua.runText(""" 77 | print("see this in logcat info") 78 | print_error("see this in logcat warn") 79 | io.write("this won't work") 80 | io.stderr:write("this won't work") 81 | """.trimIndent()) 82 | } 83 | ``` 84 | 85 | The Lua state machine will be closed after use block. If you want to hold a global instance, you can: 86 | 87 | ```kotlin 88 | object GlobalLua { 89 | 90 | val context: Context = ... 91 | 92 | val luaVm by { 93 | LuaFactory.createLua(context) 94 | } 95 | 96 | } 97 | 98 | ... 99 | 100 | GlobalLua.luaVm.runFile(...) 101 | ``` 102 | 103 | # Link 104 | 105 | - [AndroidLuaExample](https://github.com/haodynasty/AndroidLuaExample) 106 | - [luajava](https://github.com/LuaDist/luajava/) 107 | 108 | # Change Log 109 | 110 | Bug Fix: 111 | 112 | 1. [Bug] Wrong class argument passed into jni function when calling javaNew. This will lead to that the 'new' method always returns nil when called from Lua on Java class. 113 | 2. [Bug] Falsely removes the Lua state object when closing. 114 | 3. [Bug] Error occurred when pass an expression of Java method call as an argument into another Java method call from Lua. 115 | 4. [Bug] Use objectIndex instead of classIndex for Java Classes. 116 | 117 | Feature: 118 | 119 | 1. [Feature] The original error message was simply something like 'Not a valid Java Object.'. I have added the Lua script line info when error calling Java api in Lua. 120 | 2. [Feature] Add support to access fields and methods with the same name in Java object when calling Java api from Lua. Function in Lua is a first-class type so it is treated equally as other types like number or string, thus making keys in the Lua table must be different whatever types of the values are. However, This is not the case in Java since fields and methods are not the same thing so they can share an identical name. When accessing a key, we can figure out whether it is a function call or a value access from the calling stack to make this work. 121 | 3. [Feature] Support Java vararg method call from Lua. 122 | 4. [Feature] Support partially redirect stdio to Logcat. 123 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | kotlin("android") 4 | kotlin("android.extensions") 5 | } 6 | 7 | android { 8 | compileSdkVersion(30) 9 | 10 | defaultConfig { 11 | minSdkVersion(16) 12 | targetSdkVersion(30) 13 | versionCode = 1 14 | versionName = "1.0" 15 | } 16 | buildTypes { 17 | val release by getting { 18 | isMinifyEnabled = false 19 | signingConfig = signingConfigs.getByName("debug") 20 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 21 | } 22 | } 23 | 24 | lintOptions { 25 | isCheckReleaseBuilds = false 26 | // Or, if you prefer, you can continue to check for errors in release builds, 27 | // but continue the build even when errors are found: 28 | isAbortOnError = false 29 | } 30 | } 31 | 32 | tasks.withType { 33 | options.encoding = "UTF-8" 34 | } 35 | 36 | dependencies { 37 | implementation(project(":luajavax")) 38 | api("org.jetbrains.kotlin:kotlin-stdlib:1.4.30") 39 | 40 | api("org.slf4j:slf4j-api:1.7.21") 41 | api("com.github.tony19:logback-android-core:1.1.1-6") 42 | api("com.github.tony19:logback-android-classic:1.1.1-6") { 43 | // workaround issue #73 44 | exclude(group = "com.google.android", module = "android") 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\studio_sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # luajava 20 | 21 | -keep class kotlin.** { 22 | *; 23 | } 24 | 25 | -keep class kotlinx.** { 26 | *; 27 | } 28 | 29 | -keep class ch.qos.logback.** { 30 | *; 31 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/assets/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%thread](%file:%line\)%M: %msg%n 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/assets/lua/TestErrorMessageForJavaMethodCall.lua: -------------------------------------------------------------------------------- 1 | --comment 1 2 | --comment 2 3 | 4 | print("Hello World") 5 | 6 | logger.debug("This is an invalid Java method call.") -------------------------------------------------------------------------------- /app/src/main/assets/lua/setup.lua: -------------------------------------------------------------------------------- 1 | print = function(...) 2 | logger:debug(tostring(...)) 3 | end 4 | 5 | function redirect_output(path) 6 | io.output(path) 7 | end -------------------------------------------------------------------------------- /app/src/main/java/com/bennyhuo/luajavax/sample/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.luajavax.sample 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import com.bennyhuo.luajavax.sample.cases.* 6 | import kotlinx.android.synthetic.main.activity_main.* 7 | import org.slf4j.LoggerFactory 8 | 9 | val logger = LoggerFactory.getLogger("LuaJava") 10 | 11 | class MainActivity : Activity() { 12 | 13 | val sharedPreferences by lazy { 14 | getSharedPreferences("Android-LuaJava", 0) 15 | } 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_main) 20 | 21 | runLua.setOnClickListener { 22 | // basic() 23 | // testErrorMessageForJavaMethodCall() 24 | // testNameConflictForFieldAndMethod() 25 | // testNestedJavaMethodCall() 26 | // testBindClass() 27 | // testJavaNew() 28 | // testJavaMethodNotFound() 29 | // testLuaStdio() 30 | testValues() 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/com/bennyhuo/luajavax/sample/cases/LuaTests.kt: -------------------------------------------------------------------------------- 1 | package com.bennyhuo.luajavax.sample.cases 2 | 3 | import android.content.Context 4 | import com.bennyhuo.luajavax.LuaFactory 5 | import com.bennyhuo.luajavax.sample.BuildConfig 6 | import com.bennyhuo.luajavax.sample.logger 7 | import org.slf4j.Logger 8 | 9 | 10 | fun Context.basic() { 11 | LuaFactory.createLua(this).use { lua -> 12 | lua["logger"] = logger 13 | lua["BuildConfig"] = BuildConfig::class.java // set global value 14 | // run script, access to Java class 15 | lua.runText("print(BuildConfig.APPLICATION_ID); print(BuildConfig.VERSION_CODE)") 16 | lua.runScriptInAssets("lua/setup.lua") 17 | lua.runText("print(BuildConfig.APPLICATION_ID); print(BuildConfig.VERSION_CODE)") 18 | } 19 | } 20 | 21 | fun Context.testErrorMessageForJavaMethodCall() { 22 | LuaFactory.createLua(this).use { lua -> 23 | lua["logger"] = logger 24 | lua["BuildConfig"] = BuildConfig::class.java // set global value 25 | // 'logger.debug' is an invalid function call in lua. 26 | // You will get 'org.keplerproject.luajava.LuaException: Not a valid OO function call.' 27 | // without any useful line info. 28 | lua.runText("logger.debug(BuildConfig.APPLICATION_ID)") 29 | // Now, we can have this in the logcat: 30 | // 'org.keplerproject.luajava.LuaException: 31 | // [string "logger.debug(BuildConfig.APPLICATION_ID)"]:1: Not a valid OO function call.' 32 | 33 | lua.runScriptInAssets("lua/TestErrorMessageForJavaMethodCall.lua") 34 | 35 | } 36 | } 37 | 38 | fun Context.testNameConflictForFieldAndMethod() { 39 | class NameConflict { 40 | @JvmField 41 | var a = "NameConflict.a" 42 | 43 | fun a() { 44 | logger.debug("NameConflict.a() called.") 45 | } 46 | } 47 | 48 | LuaFactory.createLua(this).use { lua -> 49 | lua["logger"] = logger 50 | lua["nameConflict"] = NameConflict() // set global value 51 | // 'nameConflict.a' will be treated as a function call before. 52 | // This is fixed by checking the Lua instruction for OP_GET_TABLE. 53 | lua.runText(""" 54 | logger:debug(nameConflict.a) 55 | nameConflict:a() 56 | """.trimIndent()) 57 | } 58 | } 59 | 60 | fun Context.testNestedJavaMethodCall() { 61 | class NestedMethodCall { 62 | 63 | fun a() = "NestedMethodCall.a()" 64 | 65 | fun b(value: String) { 66 | logger.debug(value) 67 | } 68 | } 69 | 70 | LuaFactory.createLua(this).use { lua -> 71 | lua["logger"] = logger 72 | lua["nestedMethodCall"] = NestedMethodCall() // set global value 73 | // Use a table to hold the nested called method names instead of a string to fix this. 74 | // This would lead to a 'Invalid method call. No such method.' error before. 75 | lua.runText(""" 76 | nestedMethodCall:b(nestedMethodCall:a()) 77 | """.trimIndent()) 78 | } 79 | } 80 | 81 | fun Context.testBindClass() { 82 | LuaFactory.createLua(this).use { lua -> 83 | lua["logger"] = logger 84 | lua["StringClass"] = String::class.java 85 | // Use a table to hold the nested called method names instead of a string to fix this. 86 | // This would lead to a 'Invalid method call. No such method.' error before. 87 | lua.runText(""" 88 | logger:debug("{}", StringClass) 89 | StringClass1 = luajava.bindClass("java.lang.String") 90 | logger:debug("{}", StringClass1) 91 | 92 | logger:debug(StringClass:format("%d.%d.%d.%d",1, 2, 3, 4)) 93 | logger:debug(StringClass1:format("%d.%d.%d.%d",4, 3, 2, 1)) 94 | """.trimIndent()) 95 | } 96 | } 97 | 98 | fun Context.testJavaNew() { 99 | class TestJavaNew { 100 | fun sayHello() { 101 | logger.debug("Hello.") 102 | } 103 | } 104 | 105 | LuaFactory.createLua(this).use { lua -> 106 | lua["logger"] = logger 107 | lua["TestJavaNew"] = TestJavaNew::class.java 108 | // Use a table to hold the nested called method names instead of a string to fix this. 109 | // This would lead to a 'Invalid method call. No such method.' error before. 110 | lua.runText(""" 111 | testJavaNew = luajava.new(TestJavaNew) 112 | testJavaNew:sayHello() 113 | """.trimIndent()) 114 | } 115 | } 116 | 117 | fun Context.testJavaMethodNotFound() { 118 | class JavaMethodNotFound { 119 | fun sayHello() { 120 | logger.debug("Hello.") 121 | } 122 | } 123 | 124 | LuaFactory.createLua(this).use { lua -> 125 | lua["logger"] = logger 126 | lua["javaMethodNotFound"] = JavaMethodNotFound() 127 | // cannot find method 'sayHell' and threw exception from LuaJavaAPI, no line info before. 128 | lua.runText(""" 129 | javaMethodNotFound:sayHello() 130 | javaMethodNotFound:sayHell() 131 | """.trimIndent()) 132 | } 133 | } 134 | 135 | fun Context.testLuaStdio() { 136 | LuaFactory.createLua(this).use { lua -> 137 | // redirect 'print' and 'print_error' to logcat. 138 | // It take no effects on 'io.write' or 'io.stdout.write' or 'io.stderr.write'. 139 | lua.redirectStdioToLogcat() 140 | lua.runText(""" 141 | print("see this in logcat info") 142 | print_error("see this in logcat warn") 143 | io.write("this won't work") 144 | io.stderr:write("this won't work") 145 | """.trimIndent()) 146 | } 147 | } 148 | 149 | fun Context.testValues() { 150 | LuaFactory.createLua(this).use { lua -> 151 | lua["logger"] = logger 152 | lua["StringClass"] = String::class.java 153 | // redirect 'print' and 'print_error' to logcat. 154 | // It take no effects on 'io.write' or 'io.stdout.write' or 'io.stderr.write'. 155 | lua.redirectStdioToLogcat() 156 | lua.runText(""" 157 | n = 2.0 158 | i = 3 159 | s = "hello" 160 | x = nil 161 | """.trimIndent()) 162 | 163 | val loggerFromLua = lua.get("logger") 164 | loggerFromLua?.debug("I got a logger from lua!") 165 | val stringClass: Class? = lua["StringClass"] 166 | loggerFromLua?.warn("And a $stringClass") 167 | 168 | val n = lua.getDouble("n") 169 | val i = lua.getInt("i") 170 | val s = lua.getString("s") 171 | val x = lua.getString("x") 172 | 173 | loggerFromLua?.info("Others: n = $n, i = $i, s = $s, x = $x") 174 | } 175 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_jnitest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |