├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── android-maven-gradle.gradle ├── android-test ├── CMakeLists.txt ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── hippo │ │ └── quickjs │ │ └── android │ │ └── test │ │ ├── App.kt │ │ ├── LogView.kt │ │ ├── MessagePrinter.kt │ │ ├── MessageQueue.kt │ │ ├── TestActivity.kt │ │ └── Tester.kt │ └── res │ └── xml │ └── file_paths.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── CMakeLists.txt ├── build.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── hippo │ │ └── quickjs │ │ └── android │ │ ├── ArrayTypeAdapterTest.java │ │ ├── InterfaceTypeAdapterTest.java │ │ ├── JSArrayBufferTest.java │ │ ├── JSContextTest.java │ │ ├── JSFunctionCallbackTest.java │ │ ├── JSObjectTest.java │ │ ├── JavaMethodTest.java │ │ ├── JavaTypeTests.java │ │ ├── JavaTypesTest.java │ │ ├── NativeCleanerTest.java │ │ ├── PromiseTest.java │ │ ├── QuickJSTest.java │ │ ├── ScriptTest.java │ │ ├── StandardTypeAdaptersTest.java │ │ ├── TestsWithContext.java │ │ ├── TypeAdapterTest.java │ │ └── Utils.java │ └── main │ ├── AndroidManifest.xml │ ├── c │ ├── java-helper.c │ ├── java-helper.h │ ├── java-method.c │ ├── java-method.h │ ├── java-object.c │ ├── java-object.h │ └── quickjs-jni.c │ └── java │ └── com │ └── hippo │ └── quickjs │ └── android │ ├── ArrayTypeAdapter.java │ ├── InterfaceTypeAdapter.java │ ├── JNIHelper.java │ ├── JSArray.java │ ├── JSArrayBuffer.java │ ├── JSBoolean.java │ ├── JSContext.java │ ├── JSDataException.java │ ├── JSEvaluationException.java │ ├── JSException.java │ ├── JSFloat64.java │ ├── JSFunction.java │ ├── JSFunctionCallback.java │ ├── JSInt.java │ ├── JSInternal.java │ ├── JSNull.java │ ├── JSNumber.java │ ├── JSObject.java │ ├── JSRuntime.java │ ├── JSString.java │ ├── JSSymbol.java │ ├── JSUndefined.java │ ├── JSValue.java │ ├── JSValueAdapter.java │ ├── JavaMethod.java │ ├── JavaType.java │ ├── JavaTypes.java │ ├── NativeCleaner.java │ ├── PromiseExecutor.java │ ├── QuickJS.java │ ├── StandardTypeAdapters.java │ └── TypeAdapter.java ├── quickjs ├── CMakeLists.txt ├── qjscalc.c └── repl.c └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .cxx 4 | /local.properties 5 | /.idea/ 6 | .DS_Store 7 | build 8 | /captures 9 | .externalNativeBuild 10 | testassets 11 | patched 12 | release.keystore 13 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "quickjs/quickjs"] 2 | path = quickjs/quickjs 3 | url = https://github.com/seven332/quickjs.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QuickJS Android 2 | 3 | [QuickJS](https://bellard.org/quickjs/) Android wrapper. 4 | 5 | ## Build 6 | 7 | ``` 8 | git clone --recurse-submodules https://github.com/seven332/quickjs-android.git 9 | ``` 10 | 11 | Open the folder `quickjs-android` in Android Studio. 12 | 13 | ## Download 14 | 15 | 1. Add the JitPack repository to your root build.gradle. 16 | 17 | ```gradle 18 | allprojects { 19 | repositories { 20 | ... 21 | maven { url 'https://jitpack.io' } 22 | } 23 | } 24 | ``` 25 | 26 | 2. Add quickjs-android dependency to your application build.gradle. 27 | 28 | ```gradle 29 | dependencies { 30 | implementation "com.github.seven332:quickjs-android:0.1.0" 31 | } 32 | ``` 33 | 34 | ## Usage 35 | 36 | ### Evaluate Javascript Scripts 37 | 38 | ```Java 39 | QuickJS quickJS = new QuickJS.Builder().build(); 40 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 41 | try (JSContext context = runtime.createJSContext()) { 42 | String script1 = "" + 43 | "function fibonacci(n) {" + 44 | " if (n == 0 || n == 1) return n;" + 45 | " return fibonacci(n - 1) + fibonacci(n - 2);" + 46 | "}"; 47 | // Evaluate a script without return value 48 | context.evaluate(script1, "fibonacci.js"); 49 | 50 | String script2 = "fibonacci(10);"; 51 | // Evaluate a script with return value 52 | int result = context.evaluate(script2, "fibonacci.js", int.class); 53 | assertEquals(55, result); 54 | } 55 | } 56 | ``` 57 | 58 | ### Call Java Methods in Javascript Scripts 59 | 60 | Non-static methods and static methods are supported. Wrap a Java method as a JSFunction, then add the JSFunction to the JSContext. Call it like a normal Javascript function. 61 | 62 | ```Java 63 | QuickJS quickJS = new QuickJS.Builder().build(); 64 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 65 | try (JSContext context = runtime.createJSContext()) { 66 | // Non-static method 67 | Integer integer = 0; 68 | JSFunction zeroCompareTo = context.createJSFunction(integer, Method.create(Integer.class, Integer.class.getMethod("compareTo", Integer.class))); 69 | // Add the function to the global object 70 | context.getGlobalObject().setProperty("zeroCompareTo", zeroCompareTo); 71 | assertEquals(-1, (int) context.evaluate("zeroCompareTo(1)", "test.js", int.class)); 72 | assertEquals(1, (int) context.evaluate("zeroCompareTo(-1)", "test.js", int.class)); 73 | 74 | // Static method 75 | JSFunction javaAbs = context.createJSFunctionS(Math.class, Method.create(Math.class, Math.class.getMethod("abs", int.class))); 76 | // Add the function to the global object 77 | context.getGlobalObject().setProperty("javaAbs", javaAbs); 78 | assertEquals(1, (int) context.evaluate("javaAbs(1)", "test.js", int.class)); 79 | assertEquals(1, (int) context.evaluate("javaAbs(-1)", "test.js", int.class)); 80 | } 81 | } 82 | ``` 83 | 84 | Or create a JSFunction with a callback. 85 | 86 | ```Java 87 | QuickJS quickJS = new QuickJS.Builder().build(); 88 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 89 | try (JSContext context = runtime.createJSContext()) { 90 | // Create a JSFunction with a callback 91 | JSValue plusFunction = context.createJSFunction((context, args) -> { 92 | int a = args[0].cast(JSNumber.class).getInt(); 93 | int b = args[1].cast(JSNumber.class).getInt(); 94 | int sum = a + b; 95 | return context.createJSNumber(sum); 96 | }); 97 | 98 | context.getGlobalObject().setProperty("plus", plusFunction); 99 | int result = context.evaluate("plus(1, 2)", "test.js", Integer.class); 100 | assertThat(result).isEqualTo(3); 101 | } 102 | } 103 | ``` 104 | 105 | ### Call Javascript Methods in Java codes 106 | 107 | Just **evaluate** it. Or call `JSFunction.invoke()`. 108 | 109 | ### Promise 110 | 111 | Use `JSContext.executePendingJob()` to execute pending job of promises. You may call `JSContext.executePendingJob()` several times until it returns `false`. 112 | 113 | ```Java 114 | QuickJS quickJS = new QuickJS.Builder().build(); 115 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 116 | try (JSContext context = runtime.createJSContext()) { 117 | context.evaluate("a = 1;Promise.resolve().then(() => { a = 2 })", "test.js"); 118 | assertEquals(1, context.getGlobalObject().getProperty("a").cast(JSNumber.class).getInt()); 119 | // Execute the pending job 120 | assertTrue(context.executePendingJob()); 121 | assertEquals(2, context.getGlobalObject().getProperty("a").cast(JSNumber.class).getInt()); 122 | // No pending job 123 | assertFalse(context.executePendingJob()); 124 | } 125 | } 126 | ``` 127 | 128 | ### Conversion between Java Values and Javascript Values 129 | 130 | Java values are converted to Javascript values when calling Java methods in Javascript scripts. Javascript values are converted to a Java values when receiving return values from evaluated Javascript scripts. QuickJS Android supports primitive types, string, array. 131 | 132 | ```Java 133 | QuickJS quickJS = new QuickJS.Builder().build(); 134 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 135 | try (JSContext context = runtime.createJSContext()) { 136 | String[] result = context.evaluate("['hello', 'world']", "test.js", String[].class); 137 | assertArrayEquals(new String[] { "hello", "world" }, result); 138 | } 139 | } 140 | ``` 141 | 142 | Java Interfaces are also supported. 143 | 144 | ```Java 145 | interface Calculator { 146 | double plus(double a, double b); 147 | double minus(double a, double b); 148 | double multiplies(double a, double b); 149 | double divides(double a, double b); 150 | void noop(); 151 | } 152 | 153 | QuickJS quickJS = new QuickJS.Builder().build(); 154 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 155 | try (JSContext context = runtime.createJSContext()) { 156 | Calculator calculator = context.evaluate("" + 157 | "a = {\n" + 158 | " plus: function(a, b) { return a + b },\n" + 159 | " minus: function(a, b) { return a - b },\n" + 160 | " multiplies: function(a, b) { return a * b },\n" + 161 | " divides: function(a, b) { return a / b },\n" + 162 | " noop: function() { }\n" + 163 | "}", "test.js", Calculator.class); 164 | } 165 | } 166 | ``` 167 | 168 | Use `TypeAdapter` to support any type you like. 169 | 170 | ```Java 171 | private static class AtomicIntegerTypeAdapter extends TypeAdapter { 172 | @Override 173 | public JSValue toJSValue(Depot depot, Context context, AtomicInteger value) { 174 | return context.createJSNumber(value.get()); 175 | } 176 | 177 | @Override 178 | public AtomicInteger fromJSValue(Depot depot, Context context, JSValue value) { 179 | return new AtomicInteger(value.cast(JSNumber.class).getInt()); 180 | } 181 | } 182 | 183 | QuickJS quickJS = new QuickJS.Builder().registerTypeAdapter(AtomicInteger.class, new AtomicIntegerTypeAdapter()).build(); 184 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 185 | try (JSContext context = runtime.createJSContext()) { 186 | AtomicInteger atomicInteger = context.evaluate("1", "test.js", AtomicInteger.class); 187 | assertEquals(1, atomicInteger.get()); 188 | } 189 | } 190 | ``` 191 | 192 | ## Concept 193 | 194 | QuickJS Android uses the similar APIs to QuickJS. 195 | 196 | ### JSRuntime 197 | 198 | > JSRuntime represents a Javascript runtime corresponding to an object heap. Several runtimes can exist at the same time but they cannot exchange objects. Inside a given runtime, no multi-threading is supported. 199 | > 200 | > -- QuickJS Document 201 | 202 | ### JSContext 203 | 204 | > JSContext represents a Javascript context (or Realm). Each JSContext has its own global objects and system objects. There can be several JSContexts per JSRuntime and they can share objects, similar to frames of the same origin sharing Javascript objects in a web browser. 205 | > 206 | > -- QuickJS Document 207 | 208 | ### JSValue 209 | 210 | > JSValue represents a Javascript value which can be a primitive type or an object. 211 | > 212 | > -- QuickJS Document 213 | 214 | Available subclasses of `JSValue` are `JSNull`, `JSUndefined`, `JSBoolean`, `JSNumber`, `JSString`, `JSObject`, `JSArray`, `JSFunction`, `JSSymbol`. 215 | 216 | ## Test 217 | 218 | The original tests and benchmarks of QuickJS are in [android-test](android-test). It's a console-like app running all tests and benchmarks at startup, like `make test`. 219 | -------------------------------------------------------------------------------- /android-maven-gradle.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.github.dcendents.android-maven' 18 | 19 | tasks.withType(JavaCompile) { 20 | options.encoding = 'UTF-8' 21 | } 22 | 23 | tasks.withType(Javadoc) { 24 | options.encoding = 'UTF-8' 25 | failOnError false 26 | } 27 | 28 | // build a jar with source files 29 | task sourcesJar(type: Jar) { 30 | from android.sourceSets.main.java.srcDirs 31 | classifier = 'sources' 32 | } 33 | 34 | task javadoc(type: Javadoc) { 35 | source = android.sourceSets.main.java.sourceFiles 36 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 37 | classpath += configurations.compile 38 | } 39 | 40 | // build a jar with javadoc 41 | task javadocJar(type: Jar, dependsOn: javadoc) { 42 | classifier = 'javadoc' 43 | from javadoc.destinationDir 44 | } 45 | 46 | artifacts { 47 | archives sourcesJar 48 | archives javadocJar 49 | } 50 | -------------------------------------------------------------------------------- /android-test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(quickjs-android-test) 4 | 5 | add_subdirectory(../quickjs ${CMAKE_CURRENT_BINARY_DIR}/quickjs) 6 | -------------------------------------------------------------------------------- /android-test/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import java.util.zip.CRC32 18 | 19 | apply plugin: 'com.android.application' 20 | apply plugin: 'kotlin-android' 21 | 22 | android { 23 | compileSdkVersion 31 24 | 25 | defaultConfig { 26 | applicationId 'com.hippo.quickjs.test.android' 27 | minSdkVersion 18 28 | targetSdkVersion 31 29 | versionCode 1 30 | versionName '1.0' 31 | externalNativeBuild { 32 | cmake { 33 | arguments '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON' 34 | } 35 | } 36 | } 37 | 38 | sourceSets { 39 | main.assets.srcDirs += 'testassets' 40 | } 41 | 42 | def hasReleaseKey = rootProject.file('release.keystore').exists() 43 | 44 | if (hasReleaseKey) { 45 | signingConfigs { 46 | release { 47 | def properties = new Properties() 48 | properties.load(rootProject.file('local.properties').newDataInputStream()) 49 | storeFile file(properties.getProperty('RELEASE_STORE_PATH')) 50 | storePassword properties.getProperty('RELEASE_STORE_PASSWORD') 51 | keyAlias properties.getProperty('RELEASE_KEY_ALIAS') 52 | keyPassword properties.getProperty('RELEASE_KEY_PASSWORD') 53 | } 54 | } 55 | } 56 | 57 | buildTypes { 58 | release { 59 | debuggable false 60 | jniDebuggable false 61 | minifyEnabled false 62 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 63 | if (hasReleaseKey) { 64 | signingConfig signingConfigs.release 65 | } 66 | } 67 | } 68 | 69 | externalNativeBuild { 70 | cmake { 71 | path 'CMakeLists.txt' 72 | } 73 | } 74 | } 75 | 76 | task bundleTestAssets(type: Zip) { 77 | doFirst { 78 | delete "${projectDir}/testassets" 79 | file("${projectDir}/testassets").mkdirs() 80 | } 81 | 82 | from "${rootProject.projectDir}/quickjs/quickjs" 83 | exclude '**/*.c', '**/*.h' 84 | archiveName "testassets.zip" 85 | destinationDir file("${projectDir}/testassets") 86 | 87 | doLast { 88 | File testassets = file("${projectDir}/testassets/testassets.zip") 89 | CRC32 crc32 = new CRC32() 90 | testassets.eachByte 4096, { bytes, size -> 91 | crc32.update(bytes, 0, size) 92 | } 93 | new File("${projectDir}/testassets/testassets-${crc32.getValue()}.crc32").createNewFile() 94 | } 95 | } 96 | 97 | project.afterEvaluate { 98 | generateDebugAssets.dependsOn 'bundleTestAssets' 99 | generateReleaseAssets.dependsOn 'bundleTestAssets' 100 | } 101 | 102 | dependencies { 103 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 104 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0' 105 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0' 106 | implementation 'androidx.core:core-ktx:1.7.0' 107 | implementation 'net.lingala.zip4j:zip4j:1.3.2' 108 | implementation 'com.getkeepsafe.relinker:relinker:1.3.1' 109 | } 110 | -------------------------------------------------------------------------------- /android-test/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 22 | 23 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 43 | 44 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/App.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | import android.app.Application 20 | 21 | class App : Application() { 22 | 23 | // Let Application holds the Tester instance. 24 | // The tester is lazy. 25 | // It is expected to start when Activity attach the LogView to it. 26 | val tester by lazy { Tester(this).also { it.start() } } 27 | } 28 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/LogView.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | import android.content.Context 20 | import android.graphics.Color 21 | import android.graphics.Typeface 22 | import android.graphics.drawable.ColorDrawable 23 | import android.view.View 24 | import android.view.ViewGroup 25 | import android.widget.BaseAdapter 26 | import android.widget.ListView 27 | import android.widget.TextView 28 | 29 | /** 30 | * LogView display lots of messages. 31 | */ 32 | class LogView(context: Context) : ListView(context), MessageQueuePrinter { 33 | 34 | private val messages = MessageQueue() 35 | 36 | @Volatile 37 | private var isClosed = false 38 | 39 | private var pendingLastVisiblePosition = 0 40 | private var lastVisibleBottomShows = true 41 | 42 | val Int.dp: Int 43 | get() { 44 | val f = context.resources.displayMetrics.density * this 45 | return (if (f >= 0) (f + 0.5f) else (f - 0.5f)).toInt() 46 | } 47 | 48 | val Int.sp: Float 49 | get() = context.resources.displayMetrics.scaledDensity * this 50 | 51 | init { 52 | adapter = Adapter(context) 53 | divider = null 54 | selector = ColorDrawable(Color.TRANSPARENT) 55 | clipToPadding = false 56 | setPadding(4.dp, 4.dp, 4.dp, 4.dp) 57 | } 58 | 59 | fun close() { 60 | isClosed = true 61 | } 62 | 63 | private fun postIfNotClosed(block: () -> Unit) { 64 | if (!isClosed) { 65 | post { 66 | if (!isClosed) { 67 | block() 68 | } 69 | } 70 | } 71 | } 72 | 73 | private inline fun catchNewMessages(block: () -> Unit) { 74 | val scrollToBottom = pendingLastVisiblePosition == adapter.count - 1 && lastVisibleBottomShows 75 | block() 76 | (adapter as BaseAdapter).notifyDataSetChanged() 77 | if (scrollToBottom) { 78 | setSelection(adapter.count - 1) 79 | pendingLastVisiblePosition = adapter.count - 1 80 | lastVisibleBottomShows = true 81 | } 82 | } 83 | 84 | override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { 85 | super.onScrollChanged(l, t, oldl, oldt) 86 | pendingLastVisiblePosition = lastVisiblePosition 87 | lastVisibleBottomShows = if (childCount > 0) getChildAt(childCount - 1).bottom <= height - paddingBottom else true 88 | } 89 | 90 | override fun print(message: String) { 91 | postIfNotClosed { 92 | catchNewMessages { 93 | messages.add(message) 94 | } 95 | } 96 | } 97 | 98 | override fun print(messages: MessageQueue) { 99 | postIfNotClosed { 100 | catchNewMessages { 101 | this.messages.addAll(messages) 102 | } 103 | } 104 | } 105 | 106 | inner class Adapter(private val context: Context): BaseAdapter() { 107 | 108 | private fun fixMessage(message: String) = 109 | if (message.endsWith("\u001B[K")) message.substring(0, message.length - 3) else message 110 | 111 | override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { 112 | var view = convertView as? TextView 113 | if (view == null) { 114 | view = TextView(context).apply { 115 | typeface = Typeface.MONOSPACE 116 | textSize = 14.0f 117 | setLineSpacing(3.sp, 1.0f) 118 | layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) 119 | } 120 | } 121 | view.text = fixMessage(messages[position]) 122 | return view 123 | } 124 | 125 | override fun getItem(position: Int): Any = messages[position] 126 | 127 | override fun getItemId(position: Int): Long = position.toLong() 128 | 129 | override fun getCount(): Int = messages.size 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/MessagePrinter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | interface MessagePrinter { 20 | fun print(message: String) 21 | } 22 | 23 | interface MessageQueuePrinter : MessagePrinter { 24 | fun print(messages: MessageQueue) 25 | } 26 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/MessageQueue.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | import java.util.* 20 | 21 | class MessageQueue( 22 | private val bufferSize: Int = 8192 23 | ) { 24 | 25 | constructor(other: MessageQueue): this(other.bufferSize) { 26 | this.messages.addAll(other.messages) 27 | } 28 | 29 | private val messages = LinkedList() 30 | 31 | val size: Int 32 | get() = messages.size 33 | 34 | operator fun get(index: Int): String = messages[index] 35 | 36 | fun add(message: String) { 37 | // Remove the last one if it ends with \u001B[K 38 | if (messages.isNotEmpty()) { 39 | if (messages.last.endsWith("\u001B[K")) { 40 | messages.removeLast() 41 | } 42 | } 43 | 44 | while (messages.size >= bufferSize) { 45 | messages.removeFirst() 46 | } 47 | messages.addLast(message) 48 | } 49 | 50 | fun addAll(messageQueue: MessageQueue) { 51 | messageQueue.messages.forEach { 52 | add(it) 53 | } 54 | } 55 | 56 | fun copy(): MessageQueue = MessageQueue(this) 57 | } 58 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/TestActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | import android.app.Activity 20 | import android.os.Bundle 21 | import android.view.Menu 22 | import android.widget.Toast 23 | 24 | class TestActivity : Activity() { 25 | 26 | private lateinit var logView: LogView 27 | 28 | override fun onCreate(savedInstanceState: Bundle?) { 29 | super.onCreate(savedInstanceState) 30 | 31 | logView = LogView(this) 32 | setContentView(logView) 33 | (application as App).tester.registerMessageQueuePrinter(logView) 34 | } 35 | 36 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 37 | menu?.add("Send Log File")?.apply { 38 | setOnMenuItemClickListener { 39 | val tester = (application as App).tester 40 | if (tester.isFinished) { 41 | tester.shareLogFile() 42 | } else { 43 | Toast.makeText(this@TestActivity, "The test is not finished", Toast.LENGTH_SHORT).show() 44 | } 45 | true 46 | } 47 | } 48 | return true 49 | } 50 | 51 | override fun onDestroy() { 52 | super.onDestroy() 53 | 54 | logView.close() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /android-test/src/main/java/com/hippo/quickjs/android/test/Tester.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android.test 18 | 19 | import android.content.Context 20 | import android.content.Intent 21 | import android.net.Uri 22 | import com.getkeepsafe.relinker.ReLinker 23 | import kotlinx.coroutines.* 24 | import kotlinx.coroutines.channels.Channel 25 | import net.lingala.zip4j.core.ZipFile 26 | import java.io.* 27 | import java.lang.ref.WeakReference 28 | 29 | class Tester( 30 | private val context: Context 31 | ) { 32 | 33 | private val logFile: File 34 | private val logFileUri: Uri 35 | private val printer: MessageHolder 36 | 37 | private val assetsNameFile = File(context.filesDir, "testassets.name") 38 | private val assetsDir = File(context.filesDir, "testassets") 39 | private val tempFile = File(context.cacheDir, "testassets.zip") 40 | 41 | @Volatile 42 | private var testNumber = 0 43 | 44 | var isFinished: Boolean = false 45 | private set 46 | 47 | init { 48 | val logDir = File(context.filesDir, "logs") 49 | logDir.mkdirs() 50 | 51 | logFile = File(logDir, "log.txt") 52 | logFileUri = Uri.Builder() 53 | .scheme("content") 54 | .authority("com.hippo.quickjs.android.test.fileprovider") 55 | .appendPath("logs") 56 | .appendPath("log.txt") 57 | .build() 58 | logFile.delete() 59 | 60 | printer = MessageHolder(logFile) 61 | } 62 | 63 | fun registerMessageQueuePrinter(messageQueuePrinter: MessageQueuePrinter) { 64 | printer.registerMessageQueuePrinter(messageQueuePrinter) 65 | } 66 | 67 | private fun ensureAssetFiles() { 68 | val exceptAssetsName = try { 69 | assetsNameFile.readText() 70 | } catch (e: IOException) { 71 | null 72 | } 73 | 74 | var actualAssetsName: String? = null 75 | for (asset in context.assets.list("") ?: emptyArray()) { 76 | if (asset.startsWith("testassets-") && asset.endsWith(".crc32")) { 77 | actualAssetsName = asset 78 | } 79 | } 80 | if (actualAssetsName == null) { 81 | error("Can't find test assets") 82 | } 83 | 84 | if (exceptAssetsName != actualAssetsName) { 85 | printer.print("Need exact assets") 86 | printer.print("except = $exceptAssetsName") 87 | printer.print("actual = $actualAssetsName") 88 | 89 | assetsDir.deleteRecursively() 90 | if (!assetsDir.mkdirs()) { 91 | error("Can't create test assets dir") 92 | } 93 | 94 | context.assets.open("testassets.zip").use { `in` -> 95 | tempFile.outputStream().use { out -> 96 | `in`.copyTo(out) 97 | } 98 | } 99 | 100 | val zipFile = ZipFile(tempFile) 101 | zipFile.extractAll(assetsDir.path) 102 | 103 | assetsNameFile.writeText(actualAssetsName) 104 | 105 | printer.print("All test assets are copied") 106 | } else { 107 | printer.print("All test assets are UP-TO-DATE") 108 | } 109 | } 110 | 111 | private fun ensureExecutable() { 112 | ReLinker.loadLibrary(context, "qjs") 113 | } 114 | 115 | private fun runTest(name: String, executable: String, parameter: String) { 116 | printer.print("********************************") 117 | printer.print("** ${++testNumber}. $name") 118 | printer.print("********************************") 119 | val code = run(executable, parameter) 120 | printer.print("EXIT CODE: $code") 121 | } 122 | 123 | private fun runTest(executable: String, parameter: String) { 124 | val name = "$executable $parameter" 125 | runTest(name, executable, parameter) 126 | } 127 | 128 | private fun js2c() { 129 | runTest("qjsc", "-c -o repl.c -m repl.js") 130 | runTest("qjsc", "-fbignum -c -o qjscalc.c qjscalc.js") 131 | } 132 | 133 | private fun test() { 134 | runTest("qjs", "tests/test_closure.js") 135 | runTest("qjs", "tests/test_language.js") 136 | runTest("qjs", "tests/test_builtin.js") 137 | runTest("qjs", "tests/test_loop.js") 138 | // tmpfile returns null 139 | runTest("qjs", "tests/test_std.js") 140 | runTest("qjs", "tests/test_worker.js") 141 | runTest("qjs", "--bignum tests/test_bjson.js") 142 | runTest("qjs", "examples/test_point.js") 143 | runTest("qjs", "--bignum tests/test_op_overloading.js") 144 | runTest("qjs", "--bignum tests/test_bignum.js") 145 | runTest("qjs", "--qjscalc tests/test_qjscalc.js") 146 | } 147 | 148 | private fun stats() { 149 | runTest("qjs", "-qd") 150 | } 151 | 152 | private fun microbench() { 153 | runTest("qjs", "tests/microbench.js") 154 | } 155 | 156 | private fun runTest262() { 157 | runTest("run-test262", "-m -c test262o.conf") 158 | runTest("run-test262", "-u -c test262o.conf") 159 | runTest("run-test262", "-m -c test262.conf") 160 | runTest("run-test262", "-m -c test262.conf -a") 161 | runTest("run-test262", "-u -c test262.conf -a") 162 | runTest("run-test262", "-m -c test262.conf -E -a") 163 | } 164 | 165 | private fun printThrowable(e: Throwable) { 166 | val baos = ByteArrayOutputStream() 167 | PrintWriter(baos).apply { 168 | e.printStackTrace(this) 169 | flush() 170 | } 171 | ByteArrayInputStream(baos.toByteArray()).reader().buffered().forEachLine { printer.print(it) } 172 | } 173 | 174 | fun start() { 175 | GlobalScope.launch { 176 | try { 177 | ensureAssetFiles() 178 | ensureExecutable() 179 | 180 | js2c() 181 | 182 | test() 183 | stats() 184 | microbench() 185 | runTest262() 186 | 187 | printer.print("********************************") 188 | printer.print("********************************") 189 | printer.print("********************************") 190 | printer.print("TEST COMPLETE") 191 | } catch (e: Throwable) { 192 | e.printStackTrace() 193 | printer.print("********************************") 194 | printer.print("********************************") 195 | printer.print("********************************") 196 | printer.print("TEST INTERRUPT") 197 | printThrowable(e) 198 | } 199 | printer.finish() 200 | 201 | (this + Dispatchers.Main).launch { 202 | isFinished = true 203 | shareLogFile() 204 | } 205 | } 206 | } 207 | 208 | fun shareLogFile() { 209 | val title = "Send Log File" 210 | val intent = Intent(Intent.ACTION_SEND) 211 | intent.type = "text/plain" 212 | intent.putExtra(Intent.EXTRA_STREAM, logFileUri) 213 | intent.putExtra(Intent.EXTRA_SUBJECT, title) 214 | intent.putExtra(Intent.EXTRA_TEXT, title) 215 | val chooser = Intent.createChooser(intent, title) 216 | chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 217 | context.startActivity(chooser) 218 | } 219 | 220 | private fun run(executable: String, parameter: String): Int = runBlocking { 221 | val nativeDir = context.applicationInfo.nativeLibraryDir 222 | val executableFile = File(nativeDir, "lib$executable.so") 223 | val command = "${executableFile.path} $parameter" 224 | 225 | val processChannel = Channel() 226 | 227 | val job1 = (GlobalScope + Dispatchers.IO).launch { 228 | val process = processChannel.receive() 229 | process.inputStream.reader().buffered().forEachLine { printer.print(it) } 230 | } 231 | 232 | val job2 = (GlobalScope + Dispatchers.IO).launch { 233 | val process = processChannel.receive() 234 | process.errorStream.reader().buffered().forEachLine { printer.print(it) } 235 | } 236 | 237 | val process = Runtime.getRuntime().exec(command, null, assetsDir) 238 | processChannel.send(process) 239 | processChannel.send(process) 240 | val code = process.waitFor() 241 | 242 | job1.join() 243 | job2.join() 244 | 245 | code 246 | } 247 | 248 | /** 249 | * MessageList cache messages and dispatch message to the last registered MultiPrinter. 250 | */ 251 | private class MessageHolder( 252 | logFile: File 253 | ) : MessagePrinter { 254 | 255 | private val messages = MessageQueue() 256 | private val writer = logFile.writer() 257 | 258 | @Volatile 259 | private var weakMessageQueuePrinter: WeakReference? = null 260 | 261 | @Synchronized 262 | fun registerMessageQueuePrinter(messageQueuePrinter: MessageQueuePrinter) { 263 | messageQueuePrinter.print(messages.copy()) 264 | weakMessageQueuePrinter = WeakReference(messageQueuePrinter) 265 | } 266 | 267 | @Synchronized 268 | override fun print(message: String) { 269 | messages.add(message) 270 | weakMessageQueuePrinter?.get()?.print(message) 271 | if (!message.endsWith("\u001B[K")) { 272 | writer.write(message) 273 | writer.write("\n") 274 | } 275 | } 276 | 277 | fun finish() { 278 | writer.flush() 279 | writer.close() 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /android-test/src/main/res/xml/file_paths.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | buildscript { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | //noinspection JcenterRepositoryObsolete 22 | jcenter() 23 | maven { url 'https://plugins.gradle.org/m2/' } 24 | } 25 | dependencies { 26 | classpath 'com.android.tools.build:gradle:7.0.3' 27 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 28 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 29 | } 30 | } 31 | 32 | allprojects { 33 | repositories { 34 | google() 35 | mavenCentral() 36 | //noinspection JcenterRepositoryObsolete 37 | jcenter() 38 | maven { url 'https://jitpack.io' } 39 | } 40 | } 41 | 42 | task clean(type: Delete) { 43 | delete rootProject.buildDir 44 | } 45 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2019 Hippo Seven 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Project-wide Gradle settings. 18 | # IDE (e.g. Android Studio) users: 19 | # Gradle settings configured through the IDE *will override* 20 | # any settings specified in this file. 21 | # For more details on how to configure your build environment visit 22 | # http://www.gradle.org/docs/current/userguide/build_environment.html 23 | # Specifies the JVM arguments used for the daemon process. 24 | # The setting is particularly useful for tweaking memory settings. 25 | org.gradle.jvmargs=-Xmx1536m 26 | # When configured, Gradle will run in incubating parallel mode. 27 | # This option should only be used with decoupled projects. More details, visit 28 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 29 | # org.gradle.parallel=true 30 | # AndroidX package structure to make it clearer which packages are bundled with the 31 | # Android operating system, and which are packaged with your app's APK 32 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 33 | android.useAndroidX=true 34 | # Automatically convert third-party libraries to use AndroidX 35 | android.enableJetifier=true 36 | # Kotlin code style for this project: "official" or "obsolete": 37 | kotlin.code.style=official 38 | 39 | kotlin_version=1.5.31 40 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seven332/quickjs-android/20a00380edb5acf0df90c26bbdaef6e7449a9ef4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Oct 17 02:15:12 CST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /library/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(quickjs-android) 4 | 5 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 6 | option(LEAK_TRIGGER "Add a leak trigger" ON) 7 | else () 8 | option(LEAK_TRIGGER "Add a leak trigger" OFF) 9 | endif (CMAKE_BUILD_TYPE STREQUAL "Debug") 10 | 11 | add_subdirectory(../quickjs ${CMAKE_CURRENT_BINARY_DIR}/quickjs) 12 | 13 | set(QUICKJS_ANDROID_SOURCES 14 | src/main/c/quickjs-jni.c 15 | src/main/c/java-method.c 16 | src/main/c/java-object.c 17 | src/main/c/java-helper.c 18 | ) 19 | 20 | if (LEAK_TRIGGER) 21 | set(COMMON_FLAGS -DLEAK_TRIGGER) 22 | else () 23 | set(COMMON_FLAGS) 24 | endif (LEAK_TRIGGER) 25 | 26 | add_library(quickjs-android SHARED ${QUICKJS_ANDROID_SOURCES}) 27 | target_compile_options(quickjs-android PRIVATE ${COMMON_FLAGS}) 28 | target_link_libraries(quickjs-android PRIVATE quickjs) 29 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | apply plugin: 'com.android.library' 18 | 19 | android { 20 | compileSdkVersion 31 21 | 22 | defaultConfig { 23 | minSdkVersion 18 24 | targetSdkVersion 31 25 | testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner' 26 | externalNativeBuild { 27 | cmake { 28 | targets 'quickjs-android' 29 | arguments '-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON' 30 | } 31 | } 32 | } 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_8 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | 39 | sourceSets { 40 | androidTest.java.srcDirs += 'src/androidTest/kotlin' 41 | } 42 | 43 | buildTypes { 44 | release { 45 | minifyEnabled false 46 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 47 | } 48 | } 49 | 50 | externalNativeBuild { 51 | cmake { 52 | path 'CMakeLists.txt' 53 | } 54 | } 55 | } 56 | 57 | dependencies { 58 | implementation 'androidx.annotation:annotation:1.3.0' 59 | androidTestImplementation 'androidx.test:core:1.4.0' 60 | androidTestImplementation 'androidx.test:runner:1.4.0' 61 | androidTestImplementation 'junit:junit:4.13.2' 62 | androidTestImplementation 'org.assertj:assertj-core:3.13.2' 63 | } 64 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/ArrayTypeAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Before; 20 | import org.junit.Test; 21 | 22 | import static org.assertj.core.api.Assertions.assertThat; 23 | 24 | public class ArrayTypeAdapterTest extends TestsWithContext { 25 | private ArrayPipe pipe; 26 | 27 | @Before 28 | @Override 29 | public void setup() { 30 | super.setup(); 31 | pipe = context.evaluate("" + 32 | "a = {\n" + 33 | " intArray: function(a) { return a },\n" + 34 | " integerArray: function(a) { return a },\n" + 35 | " intIntArray: function(a) { return a },\n" + 36 | " stringArray: function(a) { return a },\n" + 37 | "}", "test.js", ArrayPipe.class); 38 | } 39 | 40 | @Test 41 | public void intArray() { 42 | assertThat(pipe.intArray(new int[] {1, 2, 3})).containsExactly(1, 2, 3); 43 | } 44 | 45 | @Test 46 | public void intArray_empty() { 47 | assertThat(pipe.intArray(new int[] {})).isEmpty(); 48 | } 49 | 50 | @Test 51 | public void intArray_null() { 52 | assertThat(pipe.intArray(null)).isNull(); 53 | } 54 | 55 | @Test 56 | public void integerArray() { 57 | assertThat(pipe.integerArray(new Integer[] {1, 2, null})).containsExactly(1, 2, null); 58 | } 59 | 60 | @Test 61 | public void integerArray_empty() { 62 | assertThat(pipe.integerArray(new Integer[] {})).isEmpty(); 63 | } 64 | 65 | @Test 66 | public void integerArray_null() { 67 | assertThat(pipe.integerArray(null)).isNull(); 68 | } 69 | 70 | @Test 71 | public void intIntArray() { 72 | assertThat(pipe.intIntArray(new int[][] {new int[] {1, 2}, null, new int[] {}})) 73 | .containsExactly(new int[] {1, 2}, null, new int[] {}); 74 | } 75 | 76 | @Test 77 | public void intIntArray_empty() { 78 | assertThat(pipe.intIntArray(new int[][] {})).isEmpty(); 79 | } 80 | 81 | @Test 82 | public void intIntArray_null() { 83 | assertThat(pipe.intIntArray(null)).isNull(); 84 | } 85 | 86 | @Test 87 | public void stringArray() { 88 | assertThat(pipe.stringArray(new String[] {"str", "ing", null})) 89 | .containsExactly("str", "ing", null); 90 | } 91 | 92 | @Test 93 | public void stringArray_empty() { 94 | assertThat(pipe.stringArray(new String[] {})).isEmpty(); 95 | } 96 | 97 | @Test 98 | public void stringArray_null() { 99 | assertThat(pipe.stringArray(null)).isNull(); 100 | } 101 | 102 | private interface ArrayPipe { 103 | int[] intArray(int[] a); 104 | Integer[] integerArray(Integer[] a); 105 | int[][] intIntArray(int[][] a); 106 | String[] stringArray(String[] a); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/InterfaceTypeAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.assertj.core.data.MapEntry; 20 | import org.junit.Test; 21 | 22 | import java.lang.reflect.Type; 23 | import java.util.Map; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import static org.assertj.core.api.Assertions.assertThat; 27 | import static org.junit.Assert.*; 28 | 29 | public class InterfaceTypeAdapterTest extends TestsWithContext { 30 | @Test 31 | public void getInterfaceMethods() { 32 | Map methods = InterfaceTypeAdapter.getInterfaceMethods(InterfaceC.class); 33 | 34 | assertThat(methods).containsOnly(MapEntry.entry("getValue", 35 | new JavaMethod(NullPointerException.class, "getValue", new Type[]{})), 36 | MapEntry.entry("setValue", 37 | new JavaMethod(void.class, "setValue", new Type[]{ Throwable.class })), 38 | MapEntry.entry("setValueResolve", 39 | new JavaMethod(void.class, "setValueResolve", new Type[]{ Throwable.class })), 40 | MapEntry.entry("fun1", 41 | new JavaMethod(void.class, "fun1", new Type[]{})), 42 | MapEntry.entry("fun2", 43 | new JavaMethod(String.class, "fun2", new Type[]{ String[].class })) 44 | ); 45 | } 46 | 47 | @Test 48 | public void getInterfaceMethods_generic_null() { 49 | assertThat(InterfaceTypeAdapter.getInterfaceMethods(InterfaceD.class)).isNull(); 50 | } 51 | 52 | @Test 53 | public void getInterfaceMethods_overload_null() { 54 | assertThat(InterfaceTypeAdapter.getInterfaceMethods(InterfaceE.class)).isNull(); 55 | } 56 | 57 | private interface InterfaceA { 58 | T getValue(); 59 | void setValue(T value); 60 | void setValueResolve(T value); 61 | void fun1(); 62 | String fun2(String... args); 63 | } 64 | 65 | private interface InterfaceB { 66 | RuntimeException getValue(); 67 | } 68 | 69 | private interface InterfaceC extends InterfaceA, InterfaceB { 70 | @Override 71 | NullPointerException getValue(); 72 | @Override 73 | void setValue(Throwable value); 74 | } 75 | 76 | private interface InterfaceD { 77 | T fun(); 78 | } 79 | 80 | private interface InterfaceE { 81 | void fun(); 82 | void fun(int value); 83 | } 84 | 85 | private static class CalculatorImpl implements Calculator { 86 | @Override 87 | public double plus(double a, double b) { return a + b; } 88 | @Override 89 | public double minus(double a, double b) { return a - b; } 90 | @Override 91 | public double multiplies(double a, double b) { return a * b; } 92 | @Override 93 | public double divides(double a, double b) { return a / b; } 94 | @Override 95 | public void noop() { } 96 | } 97 | 98 | @Test 99 | public void toJSValue() { 100 | JSValue calculator = quickJS.getAdapter(Calculator.class) 101 | .toJSValue(context, new CalculatorImpl()); 102 | context.getGlobalObject().setProperty("calculator", calculator); 103 | 104 | double a = 3243.435; 105 | double b = -6541.34; 106 | 107 | assertEquals(a + b, context.evaluate("calculator.plus(" + a + ", " + b + ")", "test.js", double.class), 0.0); 108 | assertEquals(a - b, context.evaluate("calculator.minus(" + a + ", " + b + ")", "test.js", double.class), 0.0); 109 | assertEquals(a * b, context.evaluate("calculator.multiplies(" + a + ", " + b + ")", "test.js", double.class), 0.0); 110 | assertEquals(a / b, context.evaluate("calculator.divides(" + a + ", " + b + ")", "test.js", double.class), 0.0); 111 | } 112 | 113 | @Test 114 | public void fromJSValue() { 115 | Calculator calculator = context.evaluate("" + 116 | "a = {\n" + 117 | " plus: function(a, b) { return a + b },\n" + 118 | " minus: function(a, b) { return a - b },\n" + 119 | " multiplies: function(a, b) { return a * b },\n" + 120 | " divides: function(a, b) { return a / b },\n" + 121 | " noop: function() { }\n" + 122 | "}", "test.js", Calculator.class); 123 | 124 | double a = 3243.435; 125 | double b = -6541.34; 126 | 127 | assertEquals(a + b, calculator.plus(a, b), 0.0); 128 | assertEquals(a - b, calculator.minus(a, b), 0.0); 129 | assertEquals(a * b, calculator.multiplies(a, b), 0.0); 130 | assertEquals(a / b, calculator.divides(a, b), 0.0); 131 | calculator.noop(); 132 | } 133 | 134 | @Test 135 | public void fromJSValue_notJSObject_error() { 136 | Utils.assertException( 137 | JSDataException.class, 138 | "expected: JSObject, actual: JSInt", 139 | () -> context.evaluate("1", "test.js", Calculator.class) 140 | ); 141 | } 142 | 143 | @Test 144 | public void fromJSValue_notJSFunction_error() { 145 | Calculator calculator = context.evaluate("a = {}", "test.js", Calculator.class); 146 | Utils.assertException( 147 | JSDataException.class, 148 | "expected: JSFunction, actual: JSUndefined", 149 | () -> calculator.plus(0, 0) 150 | ); 151 | } 152 | 153 | @Test 154 | public void fromJSValue_unknownType_error() { 155 | AtomicIntegerHolder holder = context.evaluate("" + 156 | "a = {\n" + 157 | " get: function() { return 0 }\n" + 158 | "}", "test.js", AtomicIntegerHolder.class); 159 | Utils.assertException( 160 | IllegalArgumentException.class, 161 | "Can't find TypeAdapter for class java.util.concurrent.atomic.AtomicInteger", 162 | holder::get 163 | ); 164 | } 165 | 166 | interface Calculator { 167 | double plus(double a, double b); 168 | double minus(double a, double b); 169 | double multiplies(double a, double b); 170 | double divides(double a, double b); 171 | void noop(); 172 | } 173 | 174 | interface AtomicIntegerHolder { 175 | AtomicInteger get(); 176 | } 177 | 178 | @Test 179 | public void fromJSValueThenToJSValue_isSame() { 180 | JSValue jsValue1 = context.evaluate("" + 181 | "a = {\n" + 182 | " plus: function(a, b) { return a + b },\n" + 183 | " minus: function(a, b) { return a - b },\n" + 184 | " multiplies: function(a, b) { return a * b },\n" + 185 | " divides: function(a, b) { return a / b },\n" + 186 | " noop: function() { }\n" + 187 | "}", "test.js", JSValue.class); 188 | 189 | TypeAdapter adapter = quickJS.getAdapter(Calculator.class); 190 | Calculator calculator = adapter.fromJSValue(context, jsValue1); 191 | JSValue jsValue2 = adapter.toJSValue(context, calculator); 192 | assertSame(jsValue1, jsValue2); 193 | } 194 | 195 | @Test 196 | public void toJSValueThenFromJSValue_isSame() { 197 | Calculator calculator1 = new CalculatorImpl(); 198 | TypeAdapter adapter = quickJS.getAdapter(Calculator.class); 199 | JSValue jsValue = adapter.toJSValue(context, calculator1); 200 | Calculator calculator2 = adapter.fromJSValue(context, jsValue); 201 | assertSame(calculator1, calculator2); 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JSArrayBufferTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import static com.hippo.quickjs.android.Utils.assertException; 22 | import static org.junit.Assert.*; 23 | 24 | public class JSArrayBufferTest extends TestsWithContext { 25 | 26 | @Test 27 | public void toBooleanArray() { 28 | boolean[] booleans = new boolean[] {true, false, true, true, false}; 29 | JSArrayBuffer buffer = context.createJSArrayBuffer(booleans); 30 | assertArrayEquals(booleans, buffer.toBooleanArray()); 31 | } 32 | 33 | @Test 34 | public void toByteArray() { 35 | byte[] bytes = new byte[] {1, 2, 3, 4, 5}; 36 | JSArrayBuffer buffer = context.createJSArrayBuffer(bytes); 37 | assertArrayEquals(bytes, buffer.toByteArray()); 38 | } 39 | 40 | @Test 41 | public void toCharArray() { 42 | char[] chars = new char[] {'1', '2', '3', '4', '5'}; 43 | JSArrayBuffer buffer = context.createJSArrayBuffer(chars); 44 | assertArrayEquals(chars, buffer.toCharArray()); 45 | } 46 | 47 | @Test 48 | public void toShortArray() { 49 | short[] shorts = new short[] {10, 20, 30, 40, 50}; 50 | JSArrayBuffer buffer = context.createJSArrayBuffer(shorts); 51 | assertArrayEquals(shorts, buffer.toShortArray()); 52 | } 53 | 54 | @Test 55 | public void toIntArray() { 56 | int[] ints = new int[] {10, 20, 30, 40, 50}; 57 | JSArrayBuffer buffer = context.createJSArrayBuffer(ints); 58 | assertArrayEquals(ints, buffer.toIntArray()); 59 | } 60 | 61 | @Test 62 | public void toLongArray() { 63 | long[] longs = new long[] {100, 200, 300, 400, 500}; 64 | JSArrayBuffer buffer = context.createJSArrayBuffer(longs); 65 | assertArrayEquals(longs, buffer.toLongArray()); 66 | } 67 | 68 | @Test 69 | public void toFloatArray() { 70 | float[] floats = new float[] {1.1f, 2.2f, 3.3f, 4.4f, 5.5f}; 71 | JSArrayBuffer buffer = context.createJSArrayBuffer(floats); 72 | assertArrayEquals(floats, buffer.toFloatArray(), 0); 73 | } 74 | 75 | @Test 76 | public void toDoubleArray() { 77 | double[] doubles = new double[] {1.11, 2.22, 3.33, 4.44, 5.55}; 78 | JSArrayBuffer buffer = context.createJSArrayBuffer(doubles); 79 | assertArrayEquals(doubles, buffer.toDoubleArray(), 0); 80 | } 81 | 82 | @Test 83 | public void toLongArray_mismatchedSize_exception() { 84 | byte[] bytes = new byte[] {1, 2, 3, 4, 5}; 85 | JSArrayBuffer buffer = context.createJSArrayBuffer(bytes); 86 | assertException( 87 | IllegalStateException.class, 88 | "Size not matched", 89 | buffer::toLongArray 90 | ); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JSFunctionCallbackTest.java: -------------------------------------------------------------------------------- 1 | package com.hippo.quickjs.android; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.concurrent.atomic.AtomicReference; 6 | 7 | import static com.hippo.quickjs.android.Utils.assertException; 8 | import static org.assertj.core.api.Assertions.assertThat; 9 | 10 | public class JSFunctionCallbackTest extends TestsWithContext { 11 | 12 | @Test 13 | public void invoke() { 14 | JSValue plusFunction = context.createJSFunction((context, args) -> { 15 | int a = args[0].cast(JSNumber.class).getInt(); 16 | int b = args[1].cast(JSNumber.class).getInt(); 17 | int sum = a + b; 18 | return context.createJSNumber(sum); 19 | }); 20 | 21 | context.getGlobalObject().setProperty("plus", plusFunction); 22 | int result = context.evaluate("plus(1, 2)", "test.js", Integer.class); 23 | assertThat(result).isEqualTo(3); 24 | } 25 | 26 | @Test 27 | public void invoke_exception() { 28 | JSValue plusFunction = context.createJSFunction((context, args) -> { 29 | int x = args[0].cast(JSNumber.class).getInt(); 30 | return context.createJSNumber(x); 31 | }); 32 | 33 | context.getGlobalObject().setProperty("x", plusFunction); 34 | assertException( 35 | JSEvaluationException.class, 36 | "InternalError: Catch java exception\n at (test.js)\n", 37 | () -> context.evaluate("x()", "test.js", Integer.class) 38 | ); 39 | } 40 | 41 | @Test 42 | public void invoke_closure() { 43 | final AtomicReference funcHolder = new AtomicReference<>(); 44 | JSValue awaitFunction = context.createJSFunction((context, args) -> { 45 | funcHolder.set(args[0].cast(JSFunction.class)); 46 | return context.createJSUndefined(); 47 | }); 48 | context.getGlobalObject().setProperty("await", awaitFunction); 49 | context.evaluate("x = 1; await(() => { x = x + 1 })", "test.js", Integer.class); 50 | funcHolder.get().invoke(null, new JSValue[] {}); 51 | int result = context.getGlobalObject().getProperty("x").cast(JSNumber.class).getInt(); 52 | assertThat(result).isEqualTo(2); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JSObjectTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import static com.hippo.quickjs.android.Utils.assertException; 22 | import static org.junit.Assert.assertEquals; 23 | 24 | public class JSObjectTest extends TestsWithContext { 25 | @Test 26 | public void definePropertyIndex() { 27 | JSObject jo = context.createJSObject(); 28 | 29 | int index = 23; 30 | int value1 = 12123; 31 | 32 | jo.defineProperty(index, context.createJSNumber(value1), JSObject.PROP_FLAG_WRITABLE); 33 | assertEquals(value1, jo.getProperty(index).cast(JSNumber.class).getInt()); 34 | } 35 | 36 | @Test 37 | public void definePropertyName() { 38 | JSObject jo = context.createJSObject(); 39 | 40 | String name = "name"; 41 | int value1 = 12123; 42 | 43 | jo.defineProperty(name, context.createJSNumber(value1), JSObject.PROP_FLAG_WRITABLE); 44 | assertEquals(value1, jo.getProperty(name).cast(JSNumber.class).getInt()); 45 | } 46 | 47 | @Test 48 | public void setPropertyIndex_writableInt() { 49 | JSObject jo = context.createJSObject(); 50 | 51 | int index = 23; 52 | int value1 = 12123; 53 | int value2 = 32121; 54 | 55 | jo.defineProperty(index, context.createJSNumber(value1), JSObject.PROP_FLAG_WRITABLE); 56 | jo.setProperty(index, context.createJSNumber(value2)); 57 | assertEquals(value2, jo.getProperty(index).cast(JSNumber.class).getInt()); 58 | } 59 | 60 | @Test 61 | public void setPropertyIndex_WriteNotWritableInt_error() { 62 | JSObject jo = context.createJSObject(); 63 | 64 | int index = 23; 65 | int value1 = 12123; 66 | int value2 = 32121; 67 | 68 | jo.defineProperty(index, context.createJSNumber(value1), 0); 69 | assertException( 70 | JSEvaluationException.class, 71 | "TypeError: '23' is read-only\n", 72 | () -> jo.setProperty(index, context.createJSNumber(value2)) 73 | ); 74 | assertEquals(value1, jo.getProperty(index).cast(JSNumber.class).getInt()); 75 | } 76 | 77 | @Test 78 | public void setPropertyName_writableInt() { 79 | JSObject jo = context.createJSObject(); 80 | 81 | String name = "name"; 82 | int value1 = 12123; 83 | int value2 = 32121; 84 | 85 | jo.defineProperty(name, context.createJSNumber(value1), JSObject.PROP_FLAG_WRITABLE); 86 | jo.setProperty(name, context.createJSNumber(value2)); 87 | assertEquals(value2, jo.getProperty(name).cast(JSNumber.class).getInt()); 88 | } 89 | 90 | @Test 91 | public void setPropertyName_WriteNotWritableInt_error() { 92 | JSObject jo = context.createJSObject(); 93 | 94 | String name = "name"; 95 | int value1 = 12123; 96 | int value2 = 32121; 97 | 98 | jo.defineProperty(name, context.createJSNumber(value1), 0); 99 | assertException( 100 | JSEvaluationException.class, 101 | "TypeError: 'name' is read-only\n", 102 | () -> jo.setProperty(name, context.createJSNumber(value2)) 103 | ); 104 | assertEquals(value1, jo.getProperty(name).cast(JSNumber.class).getInt()); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JavaMethodTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import java.lang.reflect.Type; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | 25 | public class JavaMethodTest { 26 | 27 | @Test 28 | public void getSignature() { 29 | JavaMethod method = new JavaMethod(Boolean.class, "box", new Type[]{ boolean.class }); 30 | assertEquals("(Z)Ljava/lang/Boolean;", method.getSignature()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JavaTypeTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import static org.junit.Assert.assertArrayEquals; 20 | import static org.junit.Assert.assertEquals; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | import org.junit.Test; 24 | 25 | import java.lang.reflect.ParameterizedType; 26 | import java.lang.reflect.Type; 27 | import java.util.List; 28 | 29 | public class JavaTypeTests { 30 | @Test 31 | public void getType() { 32 | Type type = new JavaType>() {}.type; 33 | assertTrue(type instanceof ParameterizedType); 34 | 35 | ParameterizedType p = (ParameterizedType) type; 36 | assertEquals(p.getRawType(), List.class); 37 | assertArrayEquals(p.getActualTypeArguments(), new Type[] { Integer.class }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/JavaTypesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import java.lang.reflect.ParameterizedType; 22 | import java.lang.reflect.Type; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static com.hippo.quickjs.android.Utils.assertException; 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public final class JavaTypesTest { 31 | 32 | @Test 33 | public void newParameterizedType() { 34 | // List. List is a top-level class. 35 | Type type = JavaTypes.newParameterizedType(List.class, A.class); 36 | assertThat(getFirstTypeArgument(type)).isEqualTo(A.class); 37 | 38 | // A. A is a static inner class. 39 | type = JavaTypes.newParameterizedTypeWithOwner(JavaTypesTest.class, A.class, B.class); 40 | assertThat(getFirstTypeArgument(type)).isEqualTo(B.class); 41 | } 42 | 43 | @Test 44 | public void parameterizedTypeWithRequiredOwnerMissing() { 45 | assertException( 46 | IllegalArgumentException.class, 47 | "unexpected owner type for " + A.class + ": null", 48 | () -> JavaTypes.newParameterizedType(A.class, B.class) 49 | ); 50 | } 51 | 52 | @Test 53 | public void parameterizedTypeWithUnnecessaryOwnerProvided() { 54 | assertException( 55 | IllegalArgumentException.class, 56 | "unexpected owner type for " + List.class + ": " + A.class, 57 | () -> JavaTypes.newParameterizedTypeWithOwner(A.class, List.class, B.class) 58 | ); 59 | } 60 | 61 | @Test 62 | public void parameterizedTypeWithIncorrectOwnerProvided() { 63 | assertException( 64 | IllegalArgumentException.class, 65 | "unexpected owner type for " + D.class + ": " + A.class, 66 | () -> JavaTypes.newParameterizedTypeWithOwner(A.class, D.class, B.class) 67 | ); 68 | } 69 | 70 | @Test 71 | public void arrayOf() { 72 | assertThat(JavaTypes.getRawType(JavaTypes.arrayOf(int.class))).isEqualTo(int[].class); 73 | assertThat(JavaTypes.getRawType(JavaTypes.arrayOf(List.class))).isEqualTo(List[].class); 74 | assertThat(JavaTypes.getRawType(JavaTypes.arrayOf(String[].class))).isEqualTo(String[][].class); 75 | } 76 | 77 | List listSubtype; 78 | List listSupertype; 79 | 80 | @Test 81 | public void subtypeOf() throws Exception { 82 | Type listOfWildcardType = JavaTypesTest.class.getDeclaredField("listSubtype").getGenericType(); 83 | Type expected = ((ParameterizedType) JavaTypes.canonicalize(listOfWildcardType)).getActualTypeArguments()[0]; 84 | Type subtype = JavaTypes.subtypeOf(CharSequence.class); 85 | assertThat(subtype).isEqualTo(expected); 86 | assertThat(subtype.hashCode()).isEqualTo(expected.hashCode()); 87 | assertThat(subtype.toString()).isEqualTo(expected.toString()); 88 | } 89 | 90 | @Test 91 | public void supertypeOf() throws Exception { 92 | Type listOfWildcardType = JavaTypesTest.class.getDeclaredField("listSupertype").getGenericType(); 93 | Type expected = ((ParameterizedType) JavaTypes.canonicalize(listOfWildcardType)).getActualTypeArguments()[0]; 94 | Type supertype = JavaTypes.supertypeOf(String.class); 95 | assertThat(supertype).isEqualTo(expected); 96 | assertThat(supertype.hashCode()).isEqualTo(expected.hashCode()); 97 | assertThat(supertype.toString()).isEqualTo(expected.toString()); 98 | } 99 | 100 | @Test 101 | public void getFirstTypeArgument() { 102 | assertThat(getFirstTypeArgument(A.class)).isNull(); 103 | 104 | Type type = JavaTypes.newParameterizedTypeWithOwner(JavaTypesTest.class, A.class, B.class, C.class); 105 | assertThat(getFirstTypeArgument(type)).isEqualTo(B.class); 106 | } 107 | 108 | @Test 109 | public void newParameterizedTypeObjectMethods() throws Exception { 110 | Type mapOfStringIntegerType = JavaTypesTest.class.getDeclaredField( 111 | "mapOfStringInteger").getGenericType(); 112 | ParameterizedType newMapType = JavaTypes.newParameterizedType(Map.class, String.class, Integer.class); 113 | assertThat(newMapType).isEqualTo(mapOfStringIntegerType); 114 | assertThat(newMapType.hashCode()).isEqualTo(mapOfStringIntegerType.hashCode()); 115 | assertThat(newMapType.toString()).isEqualTo(mapOfStringIntegerType.toString()); 116 | 117 | Type arrayListOfMapOfStringIntegerType = JavaTypesTest.class.getDeclaredField( 118 | "arrayListOfMapOfStringInteger").getGenericType(); 119 | ParameterizedType newListType = JavaTypes.newParameterizedType(ArrayList.class, newMapType); 120 | assertThat(newListType).isEqualTo(arrayListOfMapOfStringIntegerType); 121 | assertThat(newListType.hashCode()).isEqualTo(arrayListOfMapOfStringIntegerType.hashCode()); 122 | assertThat(newListType.toString()).isEqualTo(arrayListOfMapOfStringIntegerType.toString()); 123 | } 124 | 125 | private static final class A { 126 | } 127 | 128 | private static final class B { 129 | } 130 | 131 | private static final class C { 132 | } 133 | 134 | private static final class D { 135 | } 136 | 137 | /** 138 | * Given a parameterized type {@code A}, returns B. If the specified type is not a generic 139 | * type, returns null. 140 | */ 141 | private static Type getFirstTypeArgument(Type type) { 142 | if (!(type instanceof ParameterizedType)) return null; 143 | ParameterizedType ptype = (ParameterizedType) type; 144 | Type[] actualTypeArguments = ptype.getActualTypeArguments(); 145 | if (actualTypeArguments.length == 0) return null; 146 | return JavaTypes.canonicalize(actualTypeArguments[0]); 147 | } 148 | 149 | Map mapOfStringInteger; 150 | Map[] arrayOfMapOfStringInteger; 151 | ArrayList> arrayListOfMapOfStringInteger; 152 | 153 | @Test 154 | public void arrayComponentType() throws Exception { 155 | assertThat(JavaTypes.arrayComponentType(String[][].class)).isEqualTo(String[].class); 156 | assertThat(JavaTypes.arrayComponentType(String[].class)).isEqualTo(String.class); 157 | 158 | Type arrayOfMapOfStringIntegerType = JavaTypesTest.class.getDeclaredField( 159 | "arrayOfMapOfStringInteger").getGenericType(); 160 | Type mapOfStringIntegerType = JavaTypesTest.class.getDeclaredField( 161 | "mapOfStringInteger").getGenericType(); 162 | assertThat(JavaTypes.arrayComponentType(arrayOfMapOfStringIntegerType)) 163 | .isEqualTo(mapOfStringIntegerType); 164 | } 165 | 166 | @Test 167 | public void arrayEqualsGenericTypeArray() { 168 | assertThat(JavaTypes.equals(int[].class, JavaTypes.arrayOf(int.class))).isTrue(); 169 | assertThat(JavaTypes.equals(JavaTypes.arrayOf(int.class), int[].class)).isTrue(); 170 | assertThat(JavaTypes.equals(String[].class, JavaTypes.arrayOf(String.class))).isTrue(); 171 | assertThat(JavaTypes.equals(JavaTypes.arrayOf(String.class), String[].class)).isTrue(); 172 | } 173 | 174 | @Test 175 | public void parameterizedAndWildcardTypesCannotHavePrimitiveArguments() { 176 | assertException( 177 | IllegalArgumentException.class, 178 | "Unexpected primitive int. Use the boxed type.", 179 | () -> JavaTypes.newParameterizedType(List.class, int.class) 180 | ); 181 | 182 | assertException( 183 | IllegalArgumentException.class, 184 | "Unexpected primitive byte. Use the boxed type.", 185 | () -> JavaTypes.subtypeOf(byte.class) 186 | ); 187 | 188 | assertException( 189 | IllegalArgumentException.class, 190 | "Unexpected primitive boolean. Use the boxed type.", 191 | () -> JavaTypes.subtypeOf(boolean.class) 192 | ); 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/NativeCleanerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Ignore; 20 | import org.junit.Test; 21 | 22 | import java.util.BitSet; 23 | 24 | import static org.junit.Assert.assertFalse; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | public class NativeCleanerTest { 28 | 29 | @Ignore("There is no guarantee that this test will pass") 30 | @Test 31 | public void test() { 32 | int objectCount = 1234; 33 | 34 | TestNativeCleaner cleaner = new TestNativeCleaner<>(objectCount); 35 | 36 | for (int i = 0; i < objectCount; i++) { 37 | cleaner.register(new Object(), i); 38 | } 39 | 40 | // Trigger GC 41 | Runtime.getRuntime().gc(); 42 | Runtime.getRuntime().gc(); 43 | 44 | cleaner.clean(); 45 | cleaner.assertAllCleaned(); 46 | } 47 | 48 | private class TestNativeCleaner extends NativeCleaner { 49 | 50 | private BitSet bitSet; 51 | 52 | public TestNativeCleaner(int count) { 53 | bitSet = new BitSet(count); 54 | bitSet.set(0, count); 55 | } 56 | 57 | @Override 58 | public void onRemove(long pointer) { 59 | assertTrue(bitSet.get((int) pointer)); 60 | bitSet.clear((int) pointer); 61 | } 62 | 63 | public void assertAllCleaned() { 64 | BitSet mask = new BitSet(bitSet.length()); 65 | // The last object may not be cleaned 66 | mask.set(0, bitSet.length() - 1); 67 | assertFalse(bitSet.intersects(mask)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/PromiseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import static org.assertj.core.api.Assertions.assertThat; 22 | 23 | public class PromiseTest extends TestsWithContext { 24 | private static String sLog; 25 | 26 | public static void log(String log) { 27 | sLog = log; 28 | } 29 | 30 | @Test 31 | public void testPromise() throws NoSuchMethodException { 32 | JavaMethod method = JavaMethod.create(Void.class, PromiseTest.class.getMethod("log", String.class)); 33 | context.getGlobalObject().setProperty("log", context.createJSFunctionS(PromiseTest.class, method)); 34 | 35 | context.evaluate("log('before')\n" + 36 | "Promise.resolve()\n" + 37 | " .then(() => { log('in promise 1') })\n" + 38 | " .then(() => { log('in promise 2') })", "test.js"); 39 | 40 | assertThat(sLog).isEqualTo("before"); 41 | 42 | assertThat(context.executePendingJob()).isEqualTo(true); 43 | assertThat(sLog).isEqualTo("in promise 1"); 44 | 45 | assertThat(context.executePendingJob()).isEqualTo(true); 46 | assertThat(sLog).isEqualTo("in promise 2"); 47 | 48 | assertThat(context.executePendingJob()).isEqualTo(false); 49 | } 50 | 51 | @Test 52 | public void testDumbPromise() { 53 | context.evaluate("new Promise((resolve, reject) => {}).then(() => {})\n", "test.js"); 54 | assertThat(context.executePendingJob()).isEqualTo(false); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/ScriptTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | 23 | public class ScriptTest extends TestsWithContext { 24 | @Test 25 | public void fibonacci() { 26 | String script = "" + 27 | "function fibonacci(n) {" + 28 | " if (n == 0 || n == 1) return n;" + 29 | " return fibonacci(n - 1) + fibonacci(n - 2);" + 30 | "}" + 31 | "fibonacci(10);"; 32 | int result = context.evaluate(script, "fibonacci.js", int.class); 33 | assertEquals(55, result); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/StandardTypeAdaptersTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import org.junit.Test; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | import static org.junit.Assert.fail; 23 | 24 | public class StandardTypeAdaptersTest { 25 | 26 | private void assertEquivalent(String script, T except, Class clazz) { 27 | QuickJS quickJS = new QuickJS.Builder().build(); 28 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 29 | try (JSContext context = runtime.createJSContext()) { 30 | assertEquals(except, context.evaluate(script, "test.js", StandardTypeAdapters.FACTORY.create(quickJS, clazz))); 31 | } 32 | } 33 | } 34 | 35 | private void assertException(String script, String message, Class clazz) { 36 | QuickJS quickJS = new QuickJS.Builder().build(); 37 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 38 | try (JSContext context = runtime.createJSContext()) { 39 | try { 40 | context.evaluate(script, "test.js", StandardTypeAdapters.FACTORY.create(quickJS, clazz)); 41 | fail(); 42 | } catch (JSDataException e) { 43 | assertEquals(message, e.getMessage()); 44 | } 45 | } 46 | } 47 | } 48 | 49 | @Test 50 | public void testBoolean() { 51 | assertEquivalent("false", false, boolean.class); 52 | assertEquivalent("true", true, boolean.class); 53 | assertException("null", "expected: JSBoolean, actual: JSNull", boolean.class); 54 | assertException("undefined", "expected: JSBoolean, actual: JSUndefined", boolean.class); 55 | assertException("1", "expected: JSBoolean, actual: JSInt", boolean.class); 56 | 57 | assertEquivalent("false", false, Boolean.class); 58 | assertEquivalent("true", true, Boolean.class); 59 | assertEquivalent("null", null, Boolean.class); 60 | assertEquivalent("undefined", null, Boolean.class); 61 | assertException("1", "expected: JSBoolean, actual: JSInt", Boolean.class); 62 | } 63 | 64 | @Test 65 | public void testByte() { 66 | assertEquivalent("0", (byte) 0, byte.class); 67 | assertEquivalent("1", (byte) 1, byte.class); 68 | assertEquivalent("-1", (byte) -1, byte.class); 69 | assertEquivalent("1.0", (byte) 1, byte.class); 70 | assertEquivalent("127", Byte.MAX_VALUE, byte.class); 71 | assertEquivalent("-128", Byte.MIN_VALUE, byte.class); 72 | assertException("128", "Can't treat 128 as byte", byte.class); 73 | assertException("-129", "Can't treat -129 as byte", byte.class); 74 | assertException("1.1", "Can't treat 1.1 as byte", byte.class); 75 | assertException("null", "expected: JSNumber, actual: JSNull", byte.class); 76 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", byte.class); 77 | assertException("false", "expected: JSNumber, actual: JSBoolean", byte.class); 78 | 79 | assertEquivalent("0", (byte) 0, Byte.class); 80 | assertEquivalent("1", (byte) 1, Byte.class); 81 | assertEquivalent("-1", (byte) -1, Byte.class); 82 | assertEquivalent("1.0", (byte) 1, Byte.class); 83 | assertEquivalent("127", Byte.MAX_VALUE, Byte.class); 84 | assertEquivalent("-128", Byte.MIN_VALUE, Byte.class); 85 | assertException("128", "Can't treat 128 as byte", Byte.class); 86 | assertException("-129", "Can't treat -129 as byte", Byte.class); 87 | assertException("1.1", "Can't treat 1.1 as byte", Byte.class); 88 | assertEquivalent("null", null, Byte.class); 89 | assertEquivalent("undefined", null, Byte.class); 90 | assertException("false", "expected: JSNumber, actual: JSBoolean", Byte.class); 91 | } 92 | 93 | @Test 94 | public void testChar() { 95 | assertEquivalent("'a'", 'a', char.class); 96 | assertException("'abc'", "Can't treat \"abc\" as char", char.class); 97 | assertException("''", "Can't treat \"\" as char", char.class); 98 | assertException("null", "expected: JSString, actual: JSNull", char.class); 99 | assertException("undefined", "expected: JSString, actual: JSUndefined", char.class); 100 | assertException("false", "expected: JSString, actual: JSBoolean", char.class); 101 | 102 | assertEquivalent("'a'", 'a', Character.class); 103 | assertException("'abc'", "Can't treat \"abc\" as char", Character.class); 104 | assertException("''", "Can't treat \"\" as char", Character.class); 105 | assertEquivalent("null", null, Character.class); 106 | assertEquivalent("undefined", null, Character.class); 107 | assertException("false", "expected: JSString, actual: JSBoolean", Character.class); 108 | } 109 | 110 | @Test 111 | public void testShort() { 112 | assertEquivalent("0", (short) 0, short.class); 113 | assertEquivalent("1", (short) 1, short.class); 114 | assertEquivalent("-1", (short) -1, short.class); 115 | assertEquivalent("1.0", (short) 1, short.class); 116 | assertEquivalent("32767", Short.MAX_VALUE, short.class); 117 | assertEquivalent("-32768", Short.MIN_VALUE, short.class); 118 | assertException("32768", "Can't treat 32768 as short", short.class); 119 | assertException("-32769", "Can't treat -32769 as short", short.class); 120 | assertException("1.1", "Can't treat 1.1 as short", short.class); 121 | assertException("null", "expected: JSNumber, actual: JSNull", short.class); 122 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", short.class); 123 | assertException("false", "expected: JSNumber, actual: JSBoolean", short.class); 124 | 125 | assertEquivalent("0", (short) 0, Short.class); 126 | assertEquivalent("1", (short) 1, Short.class); 127 | assertEquivalent("-1", (short) -1, Short.class); 128 | assertEquivalent("1.0", (short) 1, Short.class); 129 | assertEquivalent("32767", Short.MAX_VALUE, Short.class); 130 | assertEquivalent("-32768", Short.MIN_VALUE, Short.class); 131 | assertException("32768", "Can't treat 32768 as short", Short.class); 132 | assertException("-32769", "Can't treat -32769 as short", Short.class); 133 | assertException("1.1", "Can't treat 1.1 as short", Short.class); 134 | assertEquivalent("null", null, Short.class); 135 | assertEquivalent("undefined", null, Short.class); 136 | assertException("false", "expected: JSNumber, actual: JSBoolean", Short.class); 137 | } 138 | 139 | @Test 140 | public void testInt() { 141 | assertEquivalent("0", 0, int.class); 142 | assertEquivalent("0.0", 0, int.class); 143 | assertEquivalent("1", 1, int.class); 144 | assertEquivalent("1.0", 1, int.class); 145 | assertEquivalent("2147483647", Integer.MAX_VALUE, int.class); 146 | assertEquivalent("-2147483648", Integer.MIN_VALUE, int.class); 147 | assertException("2147483648", "Can't treat 2.147483648E9 as int", int.class); 148 | assertException("-2147483649", "Can't treat -2.147483649E9 as int", int.class); 149 | assertException("null", "expected: JSNumber, actual: JSNull", int.class); 150 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", int.class); 151 | assertException("false", "expected: JSNumber, actual: JSBoolean", int.class); 152 | 153 | assertEquivalent("0", 0, Integer.class); 154 | assertEquivalent("0.0", 0, Integer.class); 155 | assertEquivalent("1", 1, Integer.class); 156 | assertEquivalent("1.0", 1, Integer.class); 157 | assertEquivalent("2147483647", 2147483647, Integer.class); 158 | assertEquivalent("-2147483648", -2147483648, Integer.class); 159 | assertException("2147483648", "Can't treat 2.147483648E9 as int", Integer.class); 160 | assertException("-2147483649", "Can't treat -2.147483649E9 as int", Integer.class); 161 | assertEquivalent("null", null, Integer.class); 162 | assertEquivalent("undefined", null, Integer.class); 163 | assertException("false", "expected: JSNumber, actual: JSBoolean", Integer.class); 164 | } 165 | 166 | @Test 167 | public void testLong() { 168 | assertEquivalent("0", 0L, long.class); 169 | assertEquivalent("0.0", 0L, long.class); 170 | assertEquivalent("1", 1L, long.class); 171 | assertEquivalent("1.0", 1L, long.class); 172 | assertEquivalent("9007199254740991", 9007199254740991L, long.class); 173 | assertEquivalent("9007199254740992", 9007199254740992L, long.class); 174 | assertEquivalent("9007199254740993", 9007199254740992L, long.class); 175 | assertEquivalent("-9007199254740991", -9007199254740991L, long.class); 176 | assertEquivalent("-9007199254740992", -9007199254740992L, long.class); 177 | assertEquivalent("-9007199254740993", -9007199254740992L, long.class); 178 | assertEquivalent("9223372036854775808", 9223372036854775807L, long.class); 179 | assertEquivalent("9223372036854775807", 9223372036854775807L, long.class); 180 | assertEquivalent("9223372036854775806", 9223372036854775807L, long.class); 181 | assertEquivalent("-9223372036854775807", -9223372036854775808L, long.class); 182 | assertEquivalent("-9223372036854775808", -9223372036854775808L, long.class); 183 | assertEquivalent("-9223372036854775809", -9223372036854775808L, long.class); 184 | assertException("0.000001", "Can't treat 1.0E-6 as long", long.class); 185 | assertException("9923372036854775809", "Can't treat 9.923372036854776E18 as long", long.class); 186 | assertException("null", "expected: JSNumber, actual: JSNull", long.class); 187 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", long.class); 188 | assertException("false", "expected: JSNumber, actual: JSBoolean", long.class); 189 | 190 | assertEquivalent("0", 0L, Long.class); 191 | assertEquivalent("0.0", 0L, Long.class); 192 | assertEquivalent("1", 1L, Long.class); 193 | assertEquivalent("1.0", 1L, Long.class); 194 | assertEquivalent("9007199254740991", 9007199254740991L, Long.class); 195 | assertEquivalent("9007199254740992", 9007199254740992L, Long.class); 196 | assertEquivalent("9007199254740993", 9007199254740992L, Long.class); 197 | assertEquivalent("-9007199254740991", -9007199254740991L, Long.class); 198 | assertEquivalent("-9007199254740992", -9007199254740992L, Long.class); 199 | assertEquivalent("-9007199254740993", -9007199254740992L, Long.class); 200 | assertEquivalent("9223372036854775808", 9223372036854775807L, Long.class); 201 | assertEquivalent("9223372036854775807", 9223372036854775807L, Long.class); 202 | assertEquivalent("9223372036854775806", 9223372036854775807L, Long.class); 203 | assertEquivalent("-9223372036854775807", -9223372036854775808L, Long.class); 204 | assertEquivalent("-9223372036854775808", -9223372036854775808L, Long.class); 205 | assertEquivalent("-9223372036854775809", -9223372036854775808L, Long.class); 206 | assertException("0.000001", "Can't treat 1.0E-6 as long", Long.class); 207 | assertException("9923372036854775809", "Can't treat 9.923372036854776E18 as long", Long.class); 208 | assertEquivalent("null", null, Long.class); 209 | assertEquivalent("undefined", null, Long.class); 210 | assertException("false", "expected: JSNumber, actual: JSBoolean", Long.class); 211 | } 212 | 213 | @Test 214 | public void testFloat() { 215 | assertEquivalent("0", 0.0f, float.class); 216 | assertEquivalent("0.0", 0.0f, float.class); 217 | assertEquivalent("1", 1.0f, float.class); 218 | assertEquivalent("1.0", 1.0f, float.class); 219 | assertEquivalent("1.1", 1.1f, float.class); 220 | assertEquivalent("Number.MAX_VALUE", Float.POSITIVE_INFINITY, float.class); 221 | assertEquivalent("Number.MIN_VALUE", 0.0f, float.class); 222 | assertEquivalent("Number.NaN", Float.NaN, float.class); 223 | assertException("null", "expected: JSNumber, actual: JSNull", float.class); 224 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", float.class); 225 | assertException("false", "expected: JSNumber, actual: JSBoolean", float.class); 226 | 227 | assertEquivalent("0", 0.0f, Float.class); 228 | assertEquivalent("0.0", 0.0f, Float.class); 229 | assertEquivalent("1", 1.0f, Float.class); 230 | assertEquivalent("1.0", 1.0f, Float.class); 231 | assertEquivalent("1.1", 1.1f, Float.class); 232 | assertEquivalent("Number.MAX_VALUE", Float.POSITIVE_INFINITY, Float.class); 233 | assertEquivalent("Number.MIN_VALUE", 0.0f, Float.class); 234 | assertEquivalent("Number.NaN", Float.NaN, Float.class); 235 | assertEquivalent("null", null, Float.class); 236 | assertEquivalent("undefined", null, Float.class); 237 | assertException("false", "expected: JSNumber, actual: JSBoolean", Float.class); 238 | } 239 | 240 | @Test 241 | public void testDouble() { 242 | assertEquivalent("0", 0.0, double.class); 243 | assertEquivalent("0.0", 0.0, double.class); 244 | assertEquivalent("1", 1.0, double.class); 245 | assertEquivalent("1.0", 1.0, double.class); 246 | assertEquivalent("1.1", 1.1, double.class); 247 | assertEquivalent("Number.MAX_VALUE", Double.MAX_VALUE, double.class); 248 | assertEquivalent("Number.MIN_VALUE", Double.MIN_VALUE, double.class); 249 | assertEquivalent("Number.POSITIVE_INFINITY", Double.POSITIVE_INFINITY, double.class); 250 | assertEquivalent("Number.NEGATIVE_INFINITY", Double.NEGATIVE_INFINITY, double.class); 251 | assertEquivalent("Number.NaN", Double.NaN, double.class); 252 | assertException("null", "expected: JSNumber, actual: JSNull", double.class); 253 | assertException("undefined", "expected: JSNumber, actual: JSUndefined", double.class); 254 | assertException("false", "expected: JSNumber, actual: JSBoolean", double.class); 255 | 256 | assertEquivalent("0", 0.0, Double.class); 257 | assertEquivalent("0.0", 0.0, Double.class); 258 | assertEquivalent("1", 1.0, Double.class); 259 | assertEquivalent("1.0", 1.0, Double.class); 260 | assertEquivalent("1.1", 1.1, Double.class); 261 | assertEquivalent("Number.MAX_VALUE", Double.MAX_VALUE, Double.class); 262 | assertEquivalent("Number.MIN_VALUE", Double.MIN_VALUE, Double.class); 263 | assertEquivalent("Number.POSITIVE_INFINITY", Double.POSITIVE_INFINITY, Double.class); 264 | assertEquivalent("Number.NEGATIVE_INFINITY", Double.NEGATIVE_INFINITY, Double.class); 265 | assertEquivalent("Number.NaN", Double.NaN, Double.class); 266 | assertEquivalent("null", null, Double.class); 267 | assertEquivalent("undefined", null, Double.class); 268 | assertException("false", "expected: JSNumber, actual: JSBoolean", Double.class); 269 | } 270 | 271 | @Test 272 | public void testString() { 273 | assertEquivalent("''", "", String.class); 274 | assertEquivalent("'str'", "str", String.class); 275 | assertEquivalent("null", null, String.class); 276 | assertEquivalent("undefined", null, String.class); 277 | assertException("false", "expected: JSString, actual: JSBoolean", String.class); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/TestsWithContext.java: -------------------------------------------------------------------------------- 1 | package com.hippo.quickjs.android; 2 | 3 | import org.junit.After; 4 | import org.junit.Before; 5 | 6 | public abstract class TestsWithContext { 7 | protected QuickJS quickJS; 8 | protected JSRuntime runtime; 9 | protected JSContext context; 10 | 11 | @Before 12 | public void setup() { 13 | quickJS = new QuickJS.Builder().build(); 14 | runtime = quickJS.createJSRuntime(); 15 | context = runtime.createJSContext(); 16 | } 17 | 18 | @After 19 | public void cleanup() { 20 | if (context != null) { 21 | context.close(); 22 | context = null; 23 | } 24 | if (runtime != null) { 25 | runtime.close(); 26 | runtime = null; 27 | } 28 | quickJS = null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/TypeAdapterTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | import org.junit.Test; 21 | 22 | import java.lang.reflect.Type; 23 | import java.util.Map; 24 | import java.util.concurrent.atomic.AtomicInteger; 25 | 26 | import static org.junit.Assert.assertEquals; 27 | 28 | import android.util.ArrayMap; 29 | 30 | public class TypeAdapterTest { 31 | 32 | @Test 33 | public void registerTypeAdapter() { 34 | QuickJS quickJS = new QuickJS.Builder().registerTypeAdapter(AtomicInteger.class, new AtomicIntegerTypeAdapter()).build(); 35 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 36 | try (JSContext context = runtime.createJSContext()) { 37 | AtomicInteger atomicInteger = context.evaluate("1", "test.js", AtomicInteger.class); 38 | assertEquals(1, atomicInteger.get()); 39 | } 40 | } 41 | } 42 | 43 | @Test 44 | public void registerTypeAdapterFactory() { 45 | QuickJS quickJS = new QuickJS.Builder().registerTypeAdapterFactory(new AtomicIntegerTypeAdapterFactory()).build(); 46 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 47 | try (JSContext context = runtime.createJSContext()) { 48 | AtomicInteger atomicInteger = context.evaluate("1", "test.js", AtomicInteger.class); 49 | assertEquals(1, atomicInteger.get()); 50 | } 51 | } 52 | } 53 | 54 | private static class AtomicIntegerTypeAdapter extends TypeAdapter { 55 | @Override 56 | public JSValue toJSValue(JSContext context, AtomicInteger value) { 57 | return context.createJSNumber(value.get()); 58 | } 59 | 60 | @Override 61 | public AtomicInteger fromJSValue(JSContext context, JSValue value) { 62 | return new AtomicInteger(value.cast(JSNumber.class).getInt()); 63 | } 64 | } 65 | 66 | private static class AtomicIntegerTypeAdapterFactory implements TypeAdapter.Factory { 67 | @Nullable 68 | @Override 69 | public TypeAdapter create(QuickJS quickJS, Type type) { 70 | if (type == AtomicInteger.class) return new AtomicIntegerTypeAdapter(); 71 | return null; 72 | } 73 | } 74 | 75 | @Test 76 | public void registerMapAdapter() { 77 | Type mapType = new JavaType>() {}.type; 78 | 79 | QuickJS quickJS = new QuickJS.Builder() 80 | .registerTypeAdapter(mapType, new MapTypeAdapter().nullable()) 81 | .build(); 82 | try (JSRuntime runtime = quickJS.createJSRuntime()) { 83 | try (JSContext context = runtime.createJSContext()) { 84 | Map actual = context.evaluate( 85 | "a = { key1: \"value1\", key2: \"value2\" }", "test.js", mapType); 86 | Map expected = new ArrayMap<>(); 87 | expected.put("key1", "value1"); 88 | expected.put("key2", "value2"); 89 | assertEquals(expected, actual); 90 | } 91 | } 92 | } 93 | 94 | private static class MapTypeAdapter extends TypeAdapter> { 95 | @Override 96 | public JSValue toJSValue(JSContext context, Map value) { 97 | JSObject jo = context.createJSObject(); 98 | value.forEach((k, v) -> { 99 | if (k == null) return; 100 | jo.setProperty(k, context.createJSString(v)); 101 | }); 102 | return jo; 103 | } 104 | 105 | @Override 106 | public Map fromJSValue(JSContext context, JSValue value) { 107 | JSObject jo = value.cast(JSObject.class); 108 | JSFunction keysFunction = context.getGlobalObject() 109 | .getProperty("Object").cast(JSObject.class) 110 | .getProperty("keys").cast(JSFunction.class); 111 | 112 | TypeAdapter adapter = context.quickJS.getAdapter(String[].class); 113 | JSValue keysResult = keysFunction.invoke(null, new JSValue[] { jo }); 114 | String[] keys = adapter.fromJSValue(context, keysResult); 115 | 116 | Map map = new ArrayMap<>(keys.length); 117 | for (String key: keys) { 118 | String val = jo.getProperty(key).cast(JSString.class).getString(); 119 | map.put(key, val); 120 | } 121 | 122 | return map; 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /library/src/androidTest/java/com/hippo/quickjs/android/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import static org.junit.Assert.*; 20 | 21 | public class Utils { 22 | 23 | public static void assertException(Class type, String message, Block block) { 24 | try { 25 | block.run(); 26 | fail(); 27 | } catch (Throwable e) { 28 | assertTrue("excepted: " + type.getName() + ", actual: " + e.getClass().getName(), type.isInstance(e)); 29 | assertEquals(message, e.getMessage()); 30 | } 31 | } 32 | 33 | public interface Block { 34 | void run(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /library/src/main/c/java-helper.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "java-helper.h" 4 | 5 | #define MAX_MSG_SIZE 1024 6 | 7 | jint throw_exception(JNIEnv *env, const char *exception_name, const char *message, ...) { 8 | char formatted_message[MAX_MSG_SIZE]; 9 | va_list va_args; 10 | va_start(va_args, message); 11 | vsnprintf(formatted_message, MAX_MSG_SIZE, message, va_args); 12 | va_end(va_args); 13 | 14 | jclass exception_class = (*env)->FindClass(env, exception_name); 15 | if (exception_class == NULL) { 16 | return -1; 17 | } 18 | 19 | return (*env)->ThrowNew(env, exception_class, formatted_message); 20 | } 21 | -------------------------------------------------------------------------------- /library/src/main/c/java-helper.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICKJS_ANDROID_JAVA_HELPER_H 2 | #define QUICKJS_ANDROID_JAVA_HELPER_H 3 | 4 | #include 5 | 6 | #define CLASS_NAME_ILLEGAL_STATE_EXCEPTION "java/lang/IllegalStateException" 7 | #define CLASS_NAME_JS_DATA_EXCEPTION "com/hippo/quickjs/android/JSDataException" 8 | 9 | #define THROW_EXCEPTION(ENV, EXCEPTION_NAME, ...) \ 10 | do { \ 11 | throw_exception((ENV), (EXCEPTION_NAME), __VA_ARGS__); \ 12 | return; \ 13 | } while (0) 14 | 15 | #define THROW_EXCEPTION_RET(ENV, EXCEPTION_NAME, ...) \ 16 | do { \ 17 | throw_exception((ENV), (EXCEPTION_NAME), __VA_ARGS__); \ 18 | return 0; \ 19 | } while (0) 20 | 21 | #define THROW_ILLEGAL_STATE_EXCEPTION(ENV, ...) \ 22 | THROW_EXCEPTION(ENV, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, __VA_ARGS__) 23 | 24 | #define THROW_ILLEGAL_STATE_EXCEPTION_RET(ENV, ...) \ 25 | THROW_EXCEPTION_RET(ENV, CLASS_NAME_ILLEGAL_STATE_EXCEPTION, __VA_ARGS__) 26 | 27 | #define THROW_JS_DATA_EXCEPTION(ENV, ...) \ 28 | THROW_EXCEPTION(ENV, CLASS_NAME_JS_DATA_EXCEPTION, __VA_ARGS__) 29 | 30 | #define THROW_JS_DATA_EXCEPTION_RET(ENV, ...) \ 31 | THROW_EXCEPTION_RET(ENV, CLASS_NAME_JS_DATA_EXCEPTION, __VA_ARGS__) 32 | 33 | #define CHECK_NULL(ENV, POINTER, MESSAGE) \ 34 | do { \ 35 | if ((POINTER) == NULL) { \ 36 | THROW_ILLEGAL_STATE_EXCEPTION((ENV), (MESSAGE)); \ 37 | } \ 38 | } while (0) 39 | 40 | #define CHECK_NULL_RET(ENV, POINTER, MESSAGE) \ 41 | do { \ 42 | if ((POINTER) == NULL) { \ 43 | THROW_ILLEGAL_STATE_EXCEPTION_RET((ENV), (MESSAGE)); \ 44 | } \ 45 | } while (0) 46 | 47 | #define CHECK_FALSE_RET(ENV, STATEMENT, MESSAGE) \ 48 | do { \ 49 | if (!(STATEMENT)) { \ 50 | THROW_ILLEGAL_STATE_EXCEPTION_RET((ENV), (MESSAGE)); \ 51 | } \ 52 | } while (0) 53 | 54 | jint throw_exception(JNIEnv *env, const char *exception_name, const char *message, ...); 55 | 56 | #define OBTAIN_ENV(VM) \ 57 | JNIEnv *env = NULL; \ 58 | int __require_detach__ = 0; \ 59 | do { \ 60 | (*(VM))->GetEnv((VM), (void **) &env, JNI_VERSION_1_6); \ 61 | if (env == NULL) __require_detach__ = (*(VM))->AttachCurrentThread((VM), &env, NULL) == JNI_OK; \ 62 | } while (0) 63 | 64 | #define RELEASE_ENV(VM) \ 65 | do { \ 66 | if (__require_detach__) (*(VM))->DetachCurrentThread((VM)); \ 67 | } while (0) 68 | 69 | #endif //QUICKJS_ANDROID_JAVA_HELPER_H 70 | -------------------------------------------------------------------------------- /library/src/main/c/java-method.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICKJS_ANDROID_JAVA_METHOD_H 2 | #define QUICKJS_ANDROID_JAVA_METHOD_H 3 | 4 | #include 5 | #include 6 | 7 | int java_method_init(JNIEnv *env); 8 | 9 | int java_method_init_context(JSContext *ctx); 10 | 11 | JSValue QJ_NewJavaMethod(JSContext *ctx, JNIEnv *env, jobject js_context, jboolean is_static, jobject callee, jmethodID method, jobject return_type, int arg_count, jobject *arg_types, jboolean is_callback_method); 12 | 13 | #endif //QUICKJS_ANDROID_JAVA_METHOD_H 14 | -------------------------------------------------------------------------------- /library/src/main/c/java-object.c: -------------------------------------------------------------------------------- 1 | #include "java-object.h" 2 | #include "java-helper.h" 3 | 4 | static JSClassID java_object_class_id; 5 | 6 | typedef struct { 7 | JavaVM *vm; 8 | jobject object; 9 | } JavaObjectData; 10 | 11 | static void java_object_finalizer(JSRuntime *rt, JSValue val) { 12 | JavaObjectData *data = JS_GetOpaque(val, java_object_class_id); 13 | 14 | OBTAIN_ENV(data->vm); 15 | 16 | if (env != NULL) { 17 | (*env)->DeleteGlobalRef(env, data->object); 18 | } 19 | 20 | RELEASE_ENV(data->vm); 21 | 22 | js_free_rt(rt, data); 23 | } 24 | 25 | static JSClassDef java_object_class = { 26 | "JavaObject", 27 | .finalizer = java_object_finalizer 28 | }; 29 | 30 | int java_object_init_context(JSContext *ctx) { 31 | JS_NewClassID(&java_object_class_id); 32 | if (JS_NewClass(JS_GetRuntime(ctx), java_object_class_id, &java_object_class)) return -1; 33 | return 0; 34 | } 35 | 36 | JSValue QJ_NewJavaObject(JSContext *ctx, JNIEnv *env, jobject object) { 37 | JSRuntime *rt = JS_GetRuntime(ctx); 38 | 39 | JavaObjectData *data = js_malloc_rt(rt, sizeof(JavaObjectData)); 40 | if (data == NULL) return JS_ThrowOutOfMemory(ctx); 41 | 42 | JSValue value = JS_NewObjectClass(ctx, java_object_class_id); 43 | if (JS_IsException(value)) { 44 | js_free_rt(rt, data); 45 | return value; 46 | } 47 | 48 | (*env)->GetJavaVM(env, &data->vm); 49 | data->object = (*env)->NewGlobalRef(env, object); 50 | 51 | JS_SetOpaque(value, data); 52 | 53 | return value; 54 | } 55 | 56 | jobject QJ_GetJavaObject(JSContext __unused *ctx, JSValueConst val) { 57 | JavaObjectData *data = JS_GetOpaque(val, java_object_class_id); 58 | return data != NULL ? data->object : NULL; 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/c/java-object.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICKJS_ANDROID_JAVA_OBJECT_H 2 | #define QUICKJS_ANDROID_JAVA_OBJECT_H 3 | 4 | #include 5 | #include 6 | 7 | int java_object_init_context(JSContext *ctx); 8 | 9 | JSValue QJ_NewJavaObject(JSContext *ctx, JNIEnv *env, jobject object); 10 | 11 | jobject QJ_GetJavaObject(JSContext *ctx, JSValueConst val); 12 | 13 | #endif //QUICKJS_ANDROID_JAVA_OBJECT_H 14 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/ArrayTypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import java.lang.reflect.Array; 20 | import java.lang.reflect.Type; 21 | 22 | class ArrayTypeAdapter extends TypeAdapter { 23 | 24 | public static final Factory FACTORY = (depot, type) -> { 25 | Type elementType = JavaTypes.arrayComponentType(type); 26 | if (elementType == null) return null; 27 | Class elementClass = JavaTypes.getRawType(elementType); 28 | TypeAdapter elementAdapter = depot.getAdapter(elementType); 29 | return new ArrayTypeAdapter(elementClass, elementAdapter).nullable(); 30 | }; 31 | 32 | private final Class elementClass; 33 | private final TypeAdapter elementAdapter; 34 | 35 | private ArrayTypeAdapter(Class elementClass, TypeAdapter elementAdapter) { 36 | this.elementClass = elementClass; 37 | this.elementAdapter = elementAdapter; 38 | } 39 | 40 | @Override 41 | public JSValue toJSValue(JSContext context, Object value) { 42 | JSArray result = context.createJSArray(); 43 | for (int i = 0, length = Array.getLength(value); i < length; i++) { 44 | result.setProperty(i, elementAdapter.toJSValue(context, Array.get(value, i))); 45 | } 46 | return result; 47 | } 48 | 49 | @Override 50 | public Object fromJSValue(JSContext context, JSValue value) { 51 | JSArray array = value.cast(JSArray.class); 52 | int length = array.getLength(); 53 | Object result = Array.newInstance(elementClass, length); 54 | for (int i = 0; i < length; i++) { 55 | Array.set(result, i, elementAdapter.fromJSValue(context, array.getProperty(i))); 56 | } 57 | return result; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/InterfaceTypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.lang.reflect.Method; 22 | import java.lang.reflect.Proxy; 23 | import java.lang.reflect.Type; 24 | import java.lang.reflect.TypeVariable; 25 | import java.util.Arrays; 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | class InterfaceTypeAdapter extends TypeAdapter { 30 | 31 | /** 32 | * Returns all methods in the interface type. 33 | * Returns {@code null} if the type is not interface, 34 | * or any method is overloaded, or any type can't be resolved. 35 | */ 36 | @Nullable 37 | static Map getInterfaceMethods(Type type) { 38 | Class rawType = JavaTypes.getRawType(type); 39 | if (!rawType.isInterface()) return null; 40 | 41 | Map methods = new HashMap<>(); 42 | 43 | for (Method method : rawType.getMethods()) { 44 | Type returnType = JavaTypes.resolve(type, rawType, method.getGenericReturnType()); 45 | // It's not resolved 46 | if (returnType instanceof TypeVariable) return null; 47 | 48 | String name = method.getName(); 49 | 50 | Type[] originParameterTypes = method.getGenericParameterTypes(); 51 | Type[] parameterTypes = new Type[originParameterTypes.length]; 52 | for (int i = 0; i < parameterTypes.length; i++) { 53 | parameterTypes[i] = JavaTypes.resolve(type, rawType, originParameterTypes[i]); 54 | // It's not resolved 55 | if (parameterTypes[i] instanceof TypeVariable) return null; 56 | } 57 | 58 | JavaMethod oldMethod = methods.get(name); 59 | if (oldMethod != null) { 60 | if (!Arrays.equals(oldMethod.parameterTypes, parameterTypes)) { 61 | // overload is not supported 62 | return null; 63 | } 64 | if (returnType.equals(oldMethod.returnType) 65 | || JavaTypes.getRawType(returnType).isAssignableFrom(JavaTypes.getRawType(oldMethod.returnType))) { 66 | // The new method is overridden 67 | continue; 68 | } 69 | } 70 | 71 | methods.put(name, new JavaMethod(returnType, name, parameterTypes)); 72 | } 73 | 74 | return methods; 75 | } 76 | 77 | static final Factory FACTORY = (depot, type) -> { 78 | Map methods = getInterfaceMethods(type); 79 | if (methods == null) return null; 80 | return new InterfaceTypeAdapter(JavaTypes.getRawType(type), methods).nullable(); 81 | }; 82 | 83 | private final Class rawType; 84 | private final Map methods; 85 | 86 | private InterfaceTypeAdapter(Class rawType, Map methods) { 87 | this.rawType = rawType; 88 | this.methods = methods; 89 | } 90 | 91 | @Override 92 | public JSValue toJSValue(JSContext context, Object value) { 93 | if (value instanceof JSValueHolder) { 94 | return ((JSValueHolder) value).getJSValue(JS_VALUE_HOLDER_TAG); 95 | } 96 | 97 | JSObject jo = context.createJSObject(value); 98 | for (JavaMethod method : methods.values()) { 99 | jo.setProperty(method.name, context.createJSFunction(value, method)); 100 | } 101 | return jo; 102 | } 103 | 104 | @Override 105 | public Object fromJSValue(JSContext context, JSValue value) { 106 | JSObject jo = value.cast(JSObject.class); 107 | 108 | Object object = jo.getJavaObject(); 109 | // TODO Check generic 110 | if (rawType.isInstance(object)) return object; 111 | 112 | return Proxy.newProxyInstance(rawType.getClassLoader(), new Class[]{ rawType, JSValueHolder.class }, (proxy, method, args) -> { 113 | // If the method is a method from Object then defer to normal invocation. 114 | if (method.getDeclaringClass() == Object.class) { 115 | return method.invoke(this, args); 116 | } 117 | 118 | // Check JSValueHolder.getJSValue(JSValueHolderTag) 119 | if (args != null && args.length == 1 && args[0] == JS_VALUE_HOLDER_TAG) { 120 | return value; 121 | } 122 | 123 | String name = method.getName(); 124 | JavaMethod simpleMethod = methods.get(name); 125 | if (simpleMethod == null) throw new NoSuchMethodException("Can't find method: " + name); 126 | 127 | int parameterNumber = args != null ? args.length : 0; 128 | if (parameterNumber != simpleMethod.parameterTypes.length) throw new IllegalStateException("Parameter number doesn't match: " + name); 129 | JSValue[] parameters = new JSValue[parameterNumber]; 130 | for (int i = 0; i < parameterNumber; i++) { 131 | Type type = simpleMethod.parameterTypes[i]; 132 | TypeAdapter adapter = context.quickJS.getAdapter(type); 133 | parameters[i] = adapter.toJSValue(context, args[i]); 134 | } 135 | 136 | Type resultType = simpleMethod.returnType; 137 | TypeAdapter resultAdapter = context.quickJS.getAdapter(resultType); 138 | 139 | JSFunction function = jo.getProperty(name).cast(JSFunction.class); 140 | 141 | JSValue result = function.invoke(jo, parameters); 142 | 143 | return resultAdapter.fromJSValue(context, result); 144 | }); 145 | } 146 | 147 | private interface JSValueHolder { 148 | JSValue getJSValue(JSValueHolderTag tag); 149 | } 150 | private static class JSValueHolderTag { } 151 | private static final JSValueHolderTag JS_VALUE_HOLDER_TAG = new JSValueHolderTag(); 152 | } 153 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JNIHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import java.lang.reflect.Type; 20 | 21 | class JNIHelper { 22 | 23 | private static Type VOID_PRIMITIVE_TYPE = void.class; 24 | private static Type CHAR_PRIMITIVE_TYPE = char.class; 25 | private static Type BOOLEAN_PRIMITIVE_TYPE = boolean.class; 26 | private static Type BYTE_PRIMITIVE_TYPE = byte.class; 27 | private static Type SHORT_PRIMITIVE_TYPE = short.class; 28 | private static Type INT_PRIMITIVE_TYPE = int.class; 29 | private static Type LONG_PRIMITIVE_TYPE = long.class; 30 | private static Type FLOAT_PRIMITIVE_TYPE = float.class; 31 | private static Type DOUBLE_PRIMITIVE_TYPE = double.class; 32 | 33 | private static Object jsValueToJavaValue(JSContext jsContext, Type type, long value) { 34 | synchronized (jsContext.jsRuntime) { 35 | JSValue jsValue = null; 36 | try { 37 | jsContext.checkClosed(); 38 | TypeAdapter adapter = jsContext.quickJS.getAdapter(type); 39 | jsValue = jsContext.wrapAsJSValue(value); 40 | return adapter.fromJSValue(jsContext, jsValue); 41 | } finally { 42 | if (jsValue == null) { 43 | QuickJS.destroyValue(jsContext.pointer, value); 44 | } 45 | } 46 | } 47 | } 48 | 49 | private static long javaValueToJSValue(JSContext jsContext, Type type, boolean value) { return javaValueToJSValue(jsContext, type, (Boolean) value); } 50 | private static long javaValueToJSValue(JSContext jsContext, Type type, char value) { return javaValueToJSValue(jsContext, type, (Character) value); } 51 | private static long javaValueToJSValue(JSContext jsContext, Type type, byte value) { return javaValueToJSValue(jsContext, type, (Byte) value); } 52 | private static long javaValueToJSValue(JSContext jsContext, Type type, short value) { return javaValueToJSValue(jsContext, type, (Short) value); } 53 | private static long javaValueToJSValue(JSContext jsContext, Type type, int value) { return javaValueToJSValue(jsContext, type, (Integer) value); } 54 | private static long javaValueToJSValue(JSContext jsContext, Type type, long value) { return javaValueToJSValue(jsContext, type, (Long) value); } 55 | private static long javaValueToJSValue(JSContext jsContext, Type type, float value) { return javaValueToJSValue(jsContext, type, (Float) value); } 56 | private static long javaValueToJSValue(JSContext jsContext, Type type, double value) { return javaValueToJSValue(jsContext, type, (Double) value); } 57 | private static long javaValueToJSValue(JSContext jsContext, Type type, Object value) { 58 | synchronized (jsContext.jsRuntime) { 59 | jsContext.checkClosed(); 60 | TypeAdapter adapter = jsContext.quickJS.getAdapter(type); 61 | return adapter.toJSValue(jsContext, value).pointer; 62 | } 63 | } 64 | 65 | private static boolean isPrimitiveType(Type type) { 66 | return type instanceof Class && ((Class) type).isPrimitive(); 67 | } 68 | 69 | @SuppressWarnings("EqualsReplaceableByObjectsCall") 70 | private static boolean isSameType(Type t1, Type t2) { 71 | return (t1 == t2) || (t1 != null && t1.equals(t2)); 72 | } 73 | 74 | private static boolean unbox(Boolean value) { return value; } 75 | private static char unbox(Character value) { return value; } 76 | private static byte unbox(Byte value) { return value; } 77 | private static short unbox(Short value) { return value; } 78 | private static int unbox(Integer value) { return value; } 79 | private static long unbox(Long value) { return value; } 80 | private static float unbox(Float value) { return value; } 81 | private static double unbox(Double value) { return value; } 82 | } 83 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSArray.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript array. 21 | */ 22 | public final class JSArray extends JSObject { 23 | 24 | JSArray(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext, null); 26 | } 27 | 28 | /** 29 | * Returns the number of elements in an array. 30 | */ 31 | public int getLength() { 32 | return getProperty("length").cast(JSNumber.class).getInt(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSArrayBuffer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | public class JSArrayBuffer extends JSObject { 20 | 21 | JSArrayBuffer(long pointer, JSContext jsContext) { 22 | super(pointer, jsContext, null); 23 | } 24 | 25 | public int getByteLength() { 26 | return getProperty("byteLength").cast(JSNumber.class).getInt(); 27 | } 28 | 29 | public boolean[] toBooleanArray() { 30 | return QuickJS.toBooleanArray(jsContext.pointer, pointer); 31 | } 32 | 33 | public byte[] toByteArray() { 34 | return QuickJS.toByteArray(jsContext.pointer, pointer); 35 | } 36 | 37 | /** 38 | * @throws IllegalStateException if its byteLength isn't a multiple of 2 39 | */ 40 | public char[] toCharArray() { 41 | return QuickJS.toCharArray(jsContext.pointer, pointer); 42 | } 43 | 44 | /** 45 | * @throws IllegalStateException if its byteLength isn't a multiple of 2 46 | */ 47 | public short[] toShortArray() { 48 | return QuickJS.toShortArray(jsContext.pointer, pointer); 49 | } 50 | 51 | /** 52 | * @throws IllegalStateException if its byteLength isn't a multiple of 4 53 | */ 54 | public int[] toIntArray() { 55 | return QuickJS.toIntArray(jsContext.pointer, pointer); 56 | } 57 | 58 | /** 59 | * @throws IllegalStateException if its byteLength isn't a multiple of 8 60 | */ 61 | public long[] toLongArray() { 62 | return QuickJS.toLongArray(jsContext.pointer, pointer); 63 | } 64 | 65 | /** 66 | * @throws IllegalStateException if its byteLength isn't a multiple of 4 67 | */ 68 | public float[] toFloatArray() { 69 | return QuickJS.toFloatArray(jsContext.pointer, pointer); 70 | } 71 | 72 | /** 73 | * @throws IllegalStateException if its byteLength isn't a multiple of 8 74 | */ 75 | public double[] toDoubleArray() { 76 | return QuickJS.toDoubleArray(jsContext.pointer, pointer); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSBoolean.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript boolean. 21 | */ 22 | public class JSBoolean extends JSValue { 23 | 24 | private final boolean value; 25 | 26 | JSBoolean(long pointer, JSContext jsContext, boolean value) { 27 | super(pointer, jsContext); 28 | this.value = value; 29 | } 30 | 31 | /** 32 | * Returns boolean value. 33 | */ 34 | public boolean getBoolean() { 35 | return value; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSDataException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * This exception is raised if JSValue can't be convert to Java type. 21 | */ 22 | public class JSDataException extends RuntimeException { 23 | public JSDataException(String message) { 24 | super(message); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSEvaluationException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * This exception is raised if QuickJS raises a JavaScript exception. 21 | */ 22 | public class JSEvaluationException extends RuntimeException { 23 | 24 | private JSException jsException; 25 | 26 | JSEvaluationException(JSException jsException) { 27 | super(jsException.toString()); 28 | } 29 | 30 | public JSException getJSException() { 31 | return jsException; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.NonNull; 20 | import androidx.annotation.Nullable; 21 | 22 | /** 23 | * The information of JavaScript exception. 24 | */ 25 | public class JSException { 26 | 27 | private final boolean isError; 28 | private final String exception; 29 | private final String stack; 30 | 31 | private JSException(boolean isError, String exception, String stack) { 32 | this.isError = isError; 33 | this.exception = exception; 34 | this.stack = stack; 35 | } 36 | 37 | public boolean isError() { 38 | return isError; 39 | } 40 | 41 | /** 42 | * The exception message. 43 | */ 44 | @Nullable 45 | public String getException() { 46 | return exception; 47 | } 48 | 49 | /** 50 | * The stack trace. 51 | */ 52 | @Nullable 53 | public String getStack() { 54 | return stack; 55 | } 56 | 57 | @NonNull 58 | @Override 59 | public String toString() { 60 | StringBuilder sb = new StringBuilder(); 61 | if (!isError) { 62 | sb.append("Throw: "); 63 | } 64 | sb.append(exception).append("\n"); 65 | if (stack != null) { 66 | sb.append(stack); 67 | } 68 | return sb.toString(); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSFloat64.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | final class JSFloat64 extends JSNumber { 20 | 21 | private final double value; 22 | 23 | JSFloat64(long pointer, JSContext jsContext, double value) { 24 | super(pointer, jsContext); 25 | this.value = value; 26 | } 27 | 28 | private String wrongNumberMessage(String javaType, double value) { 29 | return "Can't treat " + value + " as " + javaType; 30 | } 31 | 32 | @Override 33 | public byte getByte() { 34 | double value = this.value; 35 | byte result = (byte) value; 36 | if (result != value) { 37 | throw new JSDataException(wrongNumberMessage("byte", value)); 38 | } 39 | return result; 40 | } 41 | 42 | @Override 43 | public short getShort() { 44 | double value = this.value; 45 | short result = (short) value; 46 | if (result != value) { 47 | throw new JSDataException(wrongNumberMessage("short", value)); 48 | } 49 | return result; 50 | } 51 | 52 | @Override 53 | public int getInt() { 54 | double value = this.value; 55 | int result = (int) value; 56 | if (result != value) { 57 | throw new JSDataException(wrongNumberMessage("int", value)); 58 | } 59 | return result; 60 | } 61 | 62 | @Override 63 | public long getLong() { 64 | double value = this.value; 65 | long result = (long) value; 66 | if (result != value) { 67 | throw new JSDataException(wrongNumberMessage("long", value)); 68 | } 69 | return result; 70 | } 71 | 72 | @Override 73 | public float getFloat() { 74 | return (float) value; 75 | } 76 | 77 | @Override 78 | public double getDouble() { 79 | return value; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSFunction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | /** 22 | * JavaScript function. 23 | */ 24 | public final class JSFunction extends JSObject { 25 | 26 | JSFunction(long pointer, JSContext jsContext) { 27 | super(pointer, jsContext, null); 28 | } 29 | 30 | /** 31 | * Calls the JavaScript function. 32 | */ 33 | public JSValue invoke(@Nullable JSValue thisObj, JSValue[] args) { 34 | // Check whether JSValues are from the same JSRuntime 35 | if (thisObj != null) checkSameJSContext(thisObj); 36 | for (JSValue arg : args) checkSameJSContext(arg); 37 | 38 | long[] valueArgs = new long[args.length]; 39 | for (int i = 0; i < args.length; i++) { 40 | valueArgs[i] = args[i].pointer; 41 | } 42 | 43 | synchronized (jsContext.jsRuntime) { 44 | long context = jsContext.checkClosed(); 45 | long ret = QuickJS.invokeValueFunction(context, pointer, thisObj != null ? thisObj.pointer : 0, valueArgs); 46 | return jsContext.wrapAsJSValue(ret); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSFunctionCallback.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | public interface JSFunctionCallback { 20 | JSValue invoke(JSContext context, JSValue[] args); 21 | } 22 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSInt.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | final class JSInt extends JSNumber { 20 | 21 | private final int value; 22 | 23 | JSInt(long pointer, JSContext jsContext, int value) { 24 | super(pointer, jsContext); 25 | this.value = value; 26 | } 27 | 28 | private int getIntInRange(String javaType, int min, int max) { 29 | int value = this.value; 30 | if (min <= value && value <= max) { 31 | return value; 32 | } else { 33 | throw new JSDataException("Can't treat " + value + " as " + javaType); 34 | } 35 | } 36 | 37 | @Override 38 | public byte getByte() { 39 | return (byte) getIntInRange("byte", Byte.MIN_VALUE, Byte.MAX_VALUE); 40 | } 41 | 42 | @Override 43 | public short getShort() { 44 | return (short) getIntInRange("short", Short.MIN_VALUE, Short.MAX_VALUE); 45 | } 46 | 47 | @Override 48 | public int getInt() { 49 | return value; 50 | } 51 | 52 | @Override 53 | public long getLong() { 54 | return value; 55 | } 56 | 57 | @Override 58 | public float getFloat() { 59 | return value; 60 | } 61 | 62 | @Override 63 | public double getDouble() { 64 | return value; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSInternal.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * Used internally in QuickJS. 21 | */ 22 | class JSInternal extends JSValue { 23 | 24 | JSInternal(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSNull.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript null. 21 | */ 22 | public final class JSNull extends JSValue { 23 | 24 | JSNull(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSNumber.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript number. 21 | */ 22 | public abstract class JSNumber extends JSValue { 23 | 24 | JSNumber(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext); 26 | } 27 | 28 | /** 29 | * Returns byte value. 30 | * 31 | * @throws JSDataException if it has decimal part, or bigger than {@link Byte#MAX_VALUE}, 32 | * or smaller than {@link Byte#MIN_VALUE} 33 | */ 34 | public abstract byte getByte(); 35 | 36 | /** 37 | * Returns short value. 38 | * 39 | * @throws JSDataException if it has decimal part, or bigger than {@link Short#MAX_VALUE}, 40 | * or smaller than {@link Short#MIN_VALUE} 41 | */ 42 | public abstract short getShort(); 43 | 44 | /** 45 | * Return int value. 46 | * 47 | * @throws JSDataException if it has decimal part, or bigger than {@link Integer#MAX_VALUE}, 48 | * or smaller than {@link Integer#MIN_VALUE} 49 | */ 50 | public abstract int getInt(); 51 | 52 | /** 53 | * Return long value. 54 | * 55 | * @throws JSDataException if it has decimal part, or bigger than {@link Long#MAX_VALUE}, 56 | * or smaller than {@link Long#MIN_VALUE} 57 | */ 58 | public abstract long getLong(); 59 | 60 | /** 61 | * Return float value. 62 | */ 63 | public abstract float getFloat(); 64 | 65 | /** 66 | * Return double value. 67 | */ 68 | public abstract double getDouble(); 69 | } 70 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSObject.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript object. 21 | */ 22 | public class JSObject extends JSValue { 23 | 24 | public static int PROP_FLAG_CONFIGURABLE = 0b001; 25 | public static int PROP_FLAG_WRITABLE = 0b010; 26 | public static int PROP_FLAG_ENUMERABLE = 0b100; 27 | 28 | private static final int PROP_FLAG_MASK = 0b111; 29 | 30 | private final Object javaObject; 31 | 32 | JSObject(long pointer, JSContext jsContext, Object javaObject) { 33 | super(pointer, jsContext); 34 | this.javaObject = javaObject; 35 | } 36 | 37 | public Object getJavaObject() { 38 | return javaObject; 39 | } 40 | 41 | /** 42 | * Returns the property as a JSValue. 43 | * 44 | * @throws JSEvaluationException if the cannot read property of this JSValue. 45 | */ 46 | public JSValue getProperty(int index) { 47 | synchronized (jsContext.jsRuntime) { 48 | long context = jsContext.checkClosed(); 49 | long property = QuickJS.getValueProperty(context, pointer, index); 50 | return jsContext.wrapAsJSValue(property); 51 | } 52 | } 53 | 54 | /** 55 | * Returns the property as a JSValue. 56 | * 57 | * @throws JSEvaluationException if the cannot read property of this JSValue. 58 | */ 59 | public JSValue getProperty(String name) { 60 | synchronized (jsContext.jsRuntime) { 61 | long context = jsContext.checkClosed(); 62 | long property = QuickJS.getValueProperty(context, pointer, name); 63 | return jsContext.wrapAsJSValue(property); 64 | } 65 | } 66 | 67 | /** 68 | * Sets JSValue as a property. 69 | */ 70 | public void setProperty(int index, JSValue jsValue) { 71 | checkSameJSContext(jsValue); 72 | synchronized (jsContext.jsRuntime) { 73 | jsContext.checkClosed(); 74 | if (!QuickJS.setValueProperty(jsContext.pointer, pointer, index, jsValue.pointer)) { 75 | throw new JSEvaluationException(QuickJS.getException(jsContext.pointer)); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Sets JSValue as a property. 82 | */ 83 | public void setProperty(String name, JSValue jsValue) { 84 | checkSameJSContext(jsValue); 85 | synchronized (jsContext.jsRuntime) { 86 | jsContext.checkClosed(); 87 | if (!QuickJS.setValueProperty(jsContext.pointer, pointer, name, jsValue.pointer)) { 88 | throw new JSEvaluationException(QuickJS.getException(jsContext.pointer)); 89 | } 90 | } 91 | } 92 | 93 | /** 94 | * Defines a new property directly on an object, or modifies an existing property on this object. 95 | */ 96 | public void defineProperty(int index, JSValue jsValue, int flags) { 97 | if ((flags & (~PROP_FLAG_MASK)) != 0) { 98 | throw new IllegalArgumentException("Invalid flags: " + flags); 99 | } 100 | synchronized (jsContext.jsRuntime) { 101 | jsContext.checkClosed(); 102 | if (!QuickJS.defineValueProperty(jsContext.pointer, pointer, index, jsValue.pointer, flags)) { 103 | throw new JSEvaluationException(QuickJS.getException(jsContext.pointer)); 104 | } 105 | } 106 | } 107 | 108 | /** 109 | * Defines a new property directly on an object, or modifies an existing property on this object. 110 | */ 111 | public void defineProperty(String name, JSValue jsValue, int flags) { 112 | if ((flags & (~PROP_FLAG_MASK)) != 0) { 113 | throw new IllegalArgumentException("Invalid flags: " + flags); 114 | } 115 | synchronized (jsContext.jsRuntime) { 116 | jsContext.checkClosed(); 117 | if (!QuickJS.defineValueProperty(jsContext.pointer, pointer, name, jsValue.pointer, flags)) { 118 | throw new JSEvaluationException(QuickJS.getException(jsContext.pointer)); 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSRuntime.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.io.Closeable; 22 | 23 | // TODO Check all JSContext closed when closing JSRuntime 24 | 25 | /** 26 | * JSRuntime is a JavaScript runtime with a memory heap. 27 | * It can't evaluate JavaScript script. 28 | * 29 | * @see JSContext 30 | */ 31 | public class JSRuntime implements Closeable { 32 | 33 | private long pointer; 34 | private final QuickJS quickJS; 35 | 36 | JSRuntime(long pointer, QuickJS quickJS) { 37 | this.pointer = pointer; 38 | this.quickJS = quickJS; 39 | } 40 | 41 | private void checkClosed() { 42 | if (pointer == 0) { 43 | throw new IllegalStateException("The JSRuntime is closed"); 44 | } 45 | } 46 | 47 | /** 48 | * Set the malloc limit for this JSRuntime. 49 | * Only positive number and {@code -1} are accepted. 50 | * {@code -1} for no limit. 51 | */ 52 | public synchronized void setMallocLimit(int mallocLimit) { 53 | checkClosed(); 54 | 55 | if (mallocLimit == 0 || mallocLimit < -1) { 56 | throw new IllegalArgumentException("Only positive number and -1 are accepted as malloc limit"); 57 | } 58 | 59 | QuickJS.setRuntimeMallocLimit(pointer, mallocLimit); 60 | } 61 | 62 | /** 63 | * Set the InterruptHandler for this JSRuntime. 64 | * {@link InterruptHandler#onInterrupt()} is called every 10000 js instructions. 65 | */ 66 | public synchronized void setInterruptHandler(@Nullable InterruptHandler interruptHandler) { 67 | checkClosed(); 68 | QuickJS.setRuntimeInterruptHandler(pointer, interruptHandler); 69 | } 70 | 71 | /** 72 | * Creates a JSContext with the memory heap of this JSRuntime. 73 | */ 74 | public synchronized JSContext createJSContext() { 75 | checkClosed(); 76 | long context = QuickJS.createContext(pointer); 77 | if (context == 0) { 78 | throw new IllegalStateException("Cannot create JSContext instance"); 79 | } 80 | return new JSContext(context, quickJS, this); 81 | } 82 | 83 | @Override 84 | public synchronized void close() { 85 | if (pointer != 0) { 86 | long runtimeToClose = pointer; 87 | pointer = 0; 88 | QuickJS.destroyRuntime(runtimeToClose); 89 | } 90 | } 91 | 92 | public interface InterruptHandler { 93 | /** 94 | * Returns {@code true} to interrupt. 95 | */ 96 | boolean onInterrupt(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSString.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript string. 21 | */ 22 | public final class JSString extends JSValue { 23 | 24 | private final String value; 25 | 26 | JSString(long pointer, JSContext jsContext, String value) { 27 | super(pointer, jsContext); 28 | this.value = value; 29 | } 30 | 31 | public String getString() { 32 | return value; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSSymbol.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript symbol. 21 | */ 22 | public final class JSSymbol extends JSValue { 23 | 24 | JSSymbol(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSUndefined.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | /** 20 | * JavaScript undefined. 21 | */ 22 | public final class JSUndefined extends JSValue { 23 | 24 | JSUndefined(long pointer, JSContext jsContext) { 25 | super(pointer, jsContext); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSValue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | // TODO Make JSValue closeable? 20 | 21 | /** 22 | * JSValue is a Javascript value. 23 | * It could be a number, a object, null, undefined or something else. 24 | */ 25 | public abstract class JSValue { 26 | 27 | final long pointer; 28 | final JSContext jsContext; 29 | 30 | JSValue(long pointer, JSContext jsContext) { 31 | this.pointer = pointer; 32 | this.jsContext = jsContext; 33 | } 34 | 35 | /** 36 | * Cast this JSValue to a special type. 37 | * 38 | * @throws JSDataException if it's not the type 39 | */ 40 | @SuppressWarnings("unchecked") 41 | public final T cast(Class clazz) { 42 | if (clazz.isInstance(this)) { 43 | return (T) this; 44 | } else { 45 | throw new JSDataException("expected: " + clazz.getSimpleName() + ", actual: " + getClass().getSimpleName()); 46 | } 47 | } 48 | 49 | /** 50 | * @throws IllegalStateException if two JSValues are not from the same JSContext 51 | */ 52 | final void checkSameJSContext(JSValue jsValue) { 53 | if (jsValue.jsContext != jsContext) { 54 | throw new IllegalStateException("Two JSValues are not from the same JSContext"); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JSValueAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.lang.reflect.Type; 22 | 23 | class JSValueAdapter { 24 | 25 | static final TypeAdapter.Factory FACTORY = new TypeAdapter.Factory() { 26 | 27 | @Nullable 28 | @Override 29 | public TypeAdapter create(QuickJS quickJS, Type type) { 30 | if (type == JSValue.class) return JS_VALUE_TYPE_ADAPTER; 31 | return null; 32 | } 33 | }; 34 | 35 | private static final TypeAdapter JS_VALUE_TYPE_ADAPTER = new TypeAdapter() { 36 | @Override 37 | public JSValue toJSValue(JSContext context, JSValue value) { 38 | if (value == null) throw new NullPointerException("value == null"); 39 | return value; 40 | } 41 | 42 | @Override 43 | public JSValue fromJSValue(JSContext context, JSValue value) { 44 | return value; 45 | } 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JavaMethod.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.lang.reflect.GenericArrayType; 22 | import java.lang.reflect.Method; 23 | import java.lang.reflect.Type; 24 | import java.lang.reflect.TypeVariable; 25 | import java.util.Arrays; 26 | 27 | /** 28 | * Represents a java method or a java static method. 29 | */ 30 | public final class JavaMethod { 31 | 32 | @Nullable 33 | public static JavaMethod create(Type type, Method rawMethod) { 34 | Class rawType = JavaTypes.getRawType(type); 35 | Type returnType = JavaTypes.resolve(type, rawType, rawMethod.getGenericReturnType()); 36 | // It's not resolved 37 | if (returnType instanceof TypeVariable) return null; 38 | 39 | String name = rawMethod.getName(); 40 | 41 | Type[] originParameterTypes = rawMethod.getGenericParameterTypes(); 42 | Type[] parameterTypes = new Type[originParameterTypes.length]; 43 | for (int i = 0; i < parameterTypes.length; i++) { 44 | parameterTypes[i] = JavaTypes.resolve(type, rawType, originParameterTypes[i]); 45 | // It's not resolved 46 | if (parameterTypes[i] instanceof TypeVariable) return null; 47 | } 48 | 49 | return new JavaMethod(returnType, name, parameterTypes); 50 | } 51 | 52 | final Type returnType; 53 | final String name; 54 | final Type[] parameterTypes; 55 | 56 | public JavaMethod(Type returnType, String name, Type[] parameterTypes) { 57 | this.returnType = canonicalize(returnType); 58 | this.name = name; 59 | this.parameterTypes = new Type[parameterTypes.length]; 60 | for (int i = 0; i < parameterTypes.length; i++) { 61 | this.parameterTypes[i] = canonicalize(parameterTypes[i]); 62 | } 63 | } 64 | 65 | private static Type canonicalize(Type type) { 66 | return JavaTypes.removeSubtypeWildcard(JavaTypes.canonicalize(type)); 67 | } 68 | 69 | private static String getTypeSignature(Type type) { 70 | // Array 71 | if (type instanceof GenericArrayType) { 72 | return "[" + getTypeSignature(((GenericArrayType) type).getGenericComponentType()); 73 | } 74 | 75 | // Primitive 76 | if (type instanceof Class && ((Class) type).isPrimitive()) { 77 | if (type == void.class) return "V"; 78 | if (type == boolean.class) return "Z"; 79 | if (type == byte.class) return "B"; 80 | if (type == char.class) return "C"; 81 | if (type == short.class) return "S"; 82 | if (type == int.class) return "I"; 83 | if (type == long.class) return "J"; 84 | if (type == float.class) return "F"; 85 | if (type == double.class) return "D"; 86 | } 87 | 88 | // Class 89 | Class clazz = JavaTypes.getRawType(type); 90 | String name = clazz.getName(); 91 | StringBuilder sb = new StringBuilder(name.length() + 2); 92 | sb.append("L"); 93 | for (int i = 0; i < name.length(); i++) { 94 | char c = name.charAt(i); 95 | sb.append(c == '.' ? '/' : c); 96 | } 97 | sb.append(";"); 98 | return sb.toString(); 99 | } 100 | 101 | // For jni 102 | String getSignature() { 103 | StringBuilder sb = new StringBuilder(); 104 | 105 | sb.append("("); 106 | for (Type parameterType : parameterTypes) { 107 | sb.append(getTypeSignature(parameterType)); 108 | } 109 | sb.append(")"); 110 | sb.append(getTypeSignature(returnType)); 111 | 112 | return sb.toString(); 113 | } 114 | 115 | @Override 116 | public String toString() { 117 | StringBuilder sb = new StringBuilder(); 118 | sb.append(returnType); 119 | sb.append(" "); 120 | sb.append(name); 121 | sb.append("("); 122 | for (int i = 0; i < parameterTypes.length; i++) { 123 | if (i != 0) sb.append(", "); 124 | sb.append(parameterTypes[i]); 125 | } 126 | sb.append(")"); 127 | return sb.toString(); 128 | } 129 | 130 | @Override 131 | public int hashCode() { 132 | int result = 0; 133 | result = 31 * result + returnType.hashCode(); 134 | result = 31 * result + name.hashCode(); 135 | result = 31 * result + Arrays.hashCode(parameterTypes); 136 | return result; 137 | } 138 | 139 | @Override 140 | public boolean equals(@Nullable Object obj) { 141 | if (!(obj instanceof JavaMethod)) return false; 142 | JavaMethod other = (JavaMethod) obj; 143 | return returnType.equals(other.returnType) 144 | && name.equals(other.name) 145 | && Arrays.equals(parameterTypes, other.parameterTypes); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/JavaType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.NonNull; 20 | 21 | import java.lang.reflect.ParameterizedType; 22 | import java.lang.reflect.Type; 23 | 24 | public abstract class JavaType { 25 | @NonNull 26 | public Type type; 27 | 28 | public JavaType() { 29 | Type supertype = JavaTypes.canonicalize(getClass().getGenericSuperclass()); 30 | if (!(supertype instanceof ParameterizedType)) invalidJavaType(); 31 | 32 | Type[] types = ((ParameterizedType) supertype).getActualTypeArguments(); 33 | if (types.length != 1) invalidJavaType(); 34 | 35 | type = types[0]; 36 | } 37 | 38 | private void invalidJavaType() { 39 | throw new IllegalStateException( 40 | "Invalid JavaType. JavaType must be inherited by a anonymous class"); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/NativeCleaner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import java.lang.ref.PhantomReference; 20 | import java.lang.ref.ReferenceQueue; 21 | import java.util.HashSet; 22 | import java.util.Set; 23 | 24 | /** 25 | * https://youtu.be/7_caITSjk1k 26 | */ 27 | abstract class NativeCleaner { 28 | 29 | private final Set> phantomReferences = new HashSet<>(); 30 | private final ReferenceQueue referenceQueue = new ReferenceQueue<>(); 31 | 32 | /** 33 | * Returns the size of not removed objects. 34 | */ 35 | public int size() { 36 | return phantomReferences.size(); 37 | } 38 | 39 | /** 40 | * Registers the object and the native pointer to this cleaner. 41 | * 42 | * @param referent the object 43 | * @param pointer the native pointer 44 | */ 45 | public void register(T referent, long pointer) { 46 | phantomReferences.add(new NativeReference<>(referent, pointer, referenceQueue)); 47 | } 48 | 49 | /** 50 | * Releases the native resources associated with the native pointer. 51 | * It's called in {@link #clean()} on objects recycled by GC, 52 | * or in {@link #forceClean()} on all objects. 53 | * It's only called once on each object. 54 | * 55 | * @param pointer the native pointer 56 | */ 57 | public abstract void onRemove(long pointer); 58 | 59 | /** 60 | * Calls {@link #onRemove(long)} on objects recycled by GC. 61 | */ 62 | @SuppressWarnings("unchecked") 63 | public void clean() { 64 | NativeReference ref; 65 | while ((ref = (NativeReference) referenceQueue.poll()) != null) { 66 | if (phantomReferences.contains(ref)) { 67 | onRemove(ref.pointer); 68 | phantomReferences.remove(ref); 69 | } 70 | } 71 | } 72 | 73 | /** 74 | * Calls {@link #onRemove(long)} on all objects. 75 | */ 76 | public void forceClean() { 77 | for (NativeReference ref : phantomReferences) { 78 | onRemove(ref.pointer); 79 | } 80 | phantomReferences.clear(); 81 | } 82 | 83 | private static class NativeReference extends PhantomReference { 84 | 85 | private final long pointer; 86 | 87 | private NativeReference(T referent, long pointer, ReferenceQueue q) { 88 | super(referent, q); 89 | this.pointer = pointer; 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/PromiseExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | public interface PromiseExecutor { 20 | void execute(JSFunction resolve, JSFunction reject); 21 | } 22 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/QuickJS.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import java.lang.reflect.Type; 20 | import java.util.ArrayList; 21 | import java.util.Collections; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.concurrent.ConcurrentHashMap; 25 | 26 | /** 27 | * QuickJS is a resources container to create {@link JSRuntime}s. 28 | */ 29 | public class QuickJS { 30 | 31 | private static final List BUILT_IN_FACTORIES = new ArrayList<>(4); 32 | 33 | static { 34 | BUILT_IN_FACTORIES.add(StandardTypeAdapters.FACTORY); 35 | BUILT_IN_FACTORIES.add(JSValueAdapter.FACTORY); 36 | BUILT_IN_FACTORIES.add(ArrayTypeAdapter.FACTORY); 37 | BUILT_IN_FACTORIES.add(InterfaceTypeAdapter.FACTORY); 38 | } 39 | 40 | private final List factories; 41 | private final Map> adapterCache; 42 | 43 | private QuickJS(QuickJS.Builder builder) { 44 | List factories = new ArrayList<>(builder.factories.size() + BUILT_IN_FACTORIES.size()); 45 | factories.addAll(builder.factories); 46 | factories.addAll(BUILT_IN_FACTORIES); 47 | this.factories = Collections.unmodifiableList(factories); 48 | this.adapterCache = new ConcurrentHashMap<>(); 49 | } 50 | 51 | @SuppressWarnings("unchecked") 52 | public TypeAdapter getAdapter(Type type) { 53 | // Canonicalize type 54 | Type newType = JavaTypes.removeSubtypeWildcard(JavaTypes.canonicalize(type)); 55 | 56 | TypeAdapter adapter = adapterCache.get(newType); 57 | if (adapter != null) { 58 | return (TypeAdapter) adapter; 59 | } 60 | 61 | for (int i = 0, size = factories.size(); i < size; i++) { 62 | adapter = factories.get(i).create(this, newType); 63 | if (adapter != null) { 64 | adapterCache.put(newType, adapter); 65 | return (TypeAdapter) adapter; 66 | } 67 | } 68 | 69 | throw new IllegalArgumentException("Can't find TypeAdapter for " + type); 70 | } 71 | 72 | /** 73 | * Creates a JSRuntime with resources in this QuickJS. 74 | */ 75 | public JSRuntime createJSRuntime() { 76 | long runtime = QuickJS.createRuntime(); 77 | if (runtime == 0) { 78 | throw new IllegalStateException("Cannot create JSRuntime instance"); 79 | } 80 | return new JSRuntime(runtime, this); 81 | } 82 | 83 | public static class Builder { 84 | 85 | private final List factories = new ArrayList<>(); 86 | 87 | public Builder registerTypeAdapter(final Type type, final TypeAdapter adapter) { 88 | return registerTypeAdapterFactory((depot, targetType) -> { 89 | if (JavaTypes.equals(type, targetType)) { 90 | return adapter; 91 | } 92 | return null; 93 | }); 94 | } 95 | 96 | public Builder registerTypeAdapterFactory(TypeAdapter.Factory factory) { 97 | factories.add(factory); 98 | return this; 99 | } 100 | 101 | public QuickJS build() { 102 | return new QuickJS(this); 103 | } 104 | } 105 | 106 | static { 107 | System.loadLibrary("quickjs-android"); 108 | } 109 | 110 | static native long createRuntime(); 111 | static native void setRuntimeMallocLimit(long runtime, int mallocLimit); 112 | static native void setRuntimeInterruptHandler(long runtime, JSRuntime.InterruptHandler interruptHandler); 113 | static native void destroyRuntime(long runtime); 114 | 115 | static native long createContext(long runtime); 116 | static native void destroyContext(long context); 117 | 118 | static native long createValueUndefined(long context); 119 | static native long createValueNull(long context); 120 | static native long createValueBoolean(long context, boolean value); 121 | static native long createValueInt(long context, int value); 122 | static native long createValueFloat64(long context, double value); 123 | static native long createValueString(long context, String value); 124 | static native long createValueObject(long context); 125 | static native long createValueArray(long context); 126 | static native long createValueArrayBufferZ(long context, boolean[] array, int start, int length); 127 | static native long createValueArrayBufferB(long context, byte[] array, int start, int length); 128 | static native long createValueArrayBufferC(long context, char[] array, int start, int length); 129 | static native long createValueArrayBufferS(long context, short[] array, int start, int length); 130 | static native long createValueArrayBufferI(long context, int[] array, int start, int length); 131 | static native long createValueArrayBufferJ(long context, long[] array, int start, int length); 132 | static native long createValueArrayBufferF(long context, float[] array, int start, int length); 133 | static native long createValueArrayBufferD(long context, double[] array, int start, int length); 134 | static native long createValueFunction(long context, JSContext jsContext, Object instance, String methodName, String methodSign, Type returnType, Type[] argTypes, boolean isCallbackMethod); 135 | static native long createValueFunctionS(long context, JSContext jsContext, String className, String methodName, String methodSign, Type returnType, Type[] argTypes); 136 | static native long createValueJavaObject(long context, Object object); 137 | static native long[] createValuePromise(long context); 138 | 139 | static native int getValueTag(long value); 140 | static native boolean isValueArray(long context, long value); 141 | static native boolean isValueArrayBuffer(long context, long value); 142 | static native boolean isValueFunction(long context, long value); 143 | static native long getValueProperty(long context, long value, int index); 144 | static native long getValueProperty(long context, long value, String name); 145 | static native boolean setValueProperty(long context, long value, int index, long property); 146 | static native boolean setValueProperty(long context, long value, String name, long property); 147 | static native boolean[] toBooleanArray(long context, long value); 148 | static native byte[] toByteArray(long context, long value); 149 | static native char[] toCharArray(long context, long value); 150 | static native short[] toShortArray(long context, long value); 151 | static native int[] toIntArray(long context, long value); 152 | static native long[] toLongArray(long context, long value); 153 | static native float[] toFloatArray(long context, long value); 154 | static native double[] toDoubleArray(long context, long value); 155 | static native boolean getValueBoolean(long value); 156 | static native int getValueInt(long value); 157 | static native double getValueFloat64(long value); 158 | static native String getValueString(long context, long value); 159 | static native Object getValueJavaObject(long context, long value); 160 | static native boolean defineValueProperty(long context, long value, int index, long property, int flags); 161 | static native boolean defineValueProperty(long context, long value, String name, long property, int flags); 162 | static native long invokeValueFunction(long context, long function, long thisObj, long[] args); 163 | static native void destroyValue(long context, long value); 164 | 165 | static native JSException getException(long context); 166 | static native long getGlobalObject(long context); 167 | 168 | static native long evaluate(long context, String sourceCode, String fileName, int flags); 169 | static native int executePendingJob(long context); 170 | } 171 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/StandardTypeAdapters.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.lang.reflect.Type; 22 | 23 | class StandardTypeAdapters { 24 | 25 | static final TypeAdapter.Factory FACTORY = new TypeAdapter.Factory() { 26 | 27 | @Nullable 28 | @Override 29 | public TypeAdapter create(QuickJS quickJS, Type type) { 30 | if (type == void.class) return VOID_TYPE_ADAPTER; 31 | if (type == boolean.class) return BOOLEAN_TYPE_ADAPTER; 32 | if (type == byte.class) return BYTE_TYPE_ADAPTER; 33 | if (type == char.class) return CHARACTER_TYPE_ADAPTER; 34 | if (type == short.class) return SHORT_TYPE_ADAPTER; 35 | if (type == int.class) return INTEGER_TYPE_ADAPTER; 36 | if (type == long.class) return LONG_TYPE_ADAPTER; 37 | if (type == float.class) return FLOAT_TYPE_ADAPTER; 38 | if (type == double.class) return DOUBLE_TYPE_ADAPTER; 39 | if (type == Void.class) return VOID_TYPE_ADAPTER; 40 | if (type == Boolean.class) return BOOLEAN_TYPE_ADAPTER.nullable(); 41 | if (type == Byte.class) return BYTE_TYPE_ADAPTER.nullable(); 42 | if (type == Character.class) return CHARACTER_TYPE_ADAPTER.nullable(); 43 | if (type == Short.class) return SHORT_TYPE_ADAPTER.nullable(); 44 | if (type == Integer.class) return INTEGER_TYPE_ADAPTER.nullable(); 45 | if (type == Long.class) return LONG_TYPE_ADAPTER.nullable(); 46 | if (type == Float.class) return FLOAT_TYPE_ADAPTER.nullable(); 47 | if (type == Double.class) return DOUBLE_TYPE_ADAPTER.nullable(); 48 | if (type == String.class) return STRING_TYPE_ADAPTER.nullable(); 49 | return null; 50 | } 51 | }; 52 | 53 | private static final TypeAdapter VOID_TYPE_ADAPTER = new TypeAdapter() { 54 | @Override 55 | public JSValue toJSValue(JSContext context, Void value) { 56 | return context.createJSNull(); 57 | } 58 | 59 | @Override 60 | public Void fromJSValue(JSContext context, JSValue value) { 61 | if (value instanceof JSNull || value instanceof JSUndefined) return null; 62 | throw new JSDataException("excepted: JSNull or JSUndefined, actual: " + value.getClass().getSimpleName()); 63 | } 64 | }; 65 | 66 | private static final TypeAdapter BOOLEAN_TYPE_ADAPTER = new TypeAdapter() { 67 | @Override 68 | public JSValue toJSValue(JSContext context, Boolean value) { 69 | return context.createJSBoolean(value); 70 | } 71 | 72 | @Override 73 | public Boolean fromJSValue(JSContext context, JSValue value) { 74 | return value.cast(JSBoolean.class).getBoolean(); 75 | } 76 | }; 77 | 78 | private static final TypeAdapter BYTE_TYPE_ADAPTER = new TypeAdapter() { 79 | @Override 80 | public JSValue toJSValue(JSContext context, Byte value) { 81 | return context.createJSNumber(value); 82 | } 83 | 84 | @Override 85 | public Byte fromJSValue(JSContext context, JSValue value) { 86 | return value.cast(JSNumber.class).getByte(); 87 | } 88 | }; 89 | 90 | private static final TypeAdapter CHARACTER_TYPE_ADAPTER = new TypeAdapter() { 91 | @Override 92 | public JSValue toJSValue(JSContext context, Character value) { 93 | return context.createJSString(value.toString()); 94 | } 95 | 96 | @Override 97 | public Character fromJSValue(JSContext context, JSValue value) { 98 | String str = value.cast(JSString.class).getString(); 99 | if (str.length() != 1) { 100 | throw new JSDataException("Can't treat \"" + str + "\" as char"); 101 | } 102 | return str.charAt(0); 103 | } 104 | }; 105 | 106 | private static final TypeAdapter SHORT_TYPE_ADAPTER = new TypeAdapter() { 107 | @Override 108 | public JSValue toJSValue(JSContext context, Short value) { 109 | return context.createJSNumber(value); 110 | } 111 | 112 | @Override 113 | public Short fromJSValue(JSContext context, JSValue value) { 114 | return value.cast(JSNumber.class).getShort(); 115 | } 116 | }; 117 | 118 | private static final TypeAdapter INTEGER_TYPE_ADAPTER = new TypeAdapter() { 119 | @Override 120 | public JSValue toJSValue(JSContext context, Integer value) { 121 | return context.createJSNumber(value); 122 | } 123 | 124 | @Override 125 | public Integer fromJSValue(JSContext context, JSValue value) { 126 | return value.cast(JSNumber.class).getInt(); 127 | } 128 | }; 129 | 130 | private static final TypeAdapter LONG_TYPE_ADAPTER = new TypeAdapter() { 131 | @Override 132 | public JSValue toJSValue(JSContext context, Long value) { 133 | return context.createJSNumber(value); 134 | } 135 | 136 | @Override 137 | public Long fromJSValue(JSContext context, JSValue value) { 138 | return value.cast(JSNumber.class).getLong(); 139 | } 140 | }; 141 | 142 | private static final TypeAdapter FLOAT_TYPE_ADAPTER = new TypeAdapter() { 143 | @Override 144 | public JSValue toJSValue(JSContext context, Float value) { 145 | return context.createJSNumber(value); 146 | } 147 | 148 | @Override 149 | public Float fromJSValue(JSContext context, JSValue value) { 150 | return value.cast(JSNumber.class).getFloat(); 151 | } 152 | }; 153 | 154 | private static final TypeAdapter DOUBLE_TYPE_ADAPTER = new TypeAdapter() { 155 | @Override 156 | public JSValue toJSValue(JSContext context, Double value) { 157 | return context.createJSNumber(value); 158 | } 159 | 160 | @Override 161 | public Double fromJSValue(JSContext context, JSValue value) { 162 | return value.cast(JSNumber.class).getDouble(); 163 | } 164 | }; 165 | 166 | private static final TypeAdapter STRING_TYPE_ADAPTER = new TypeAdapter() { 167 | @Override 168 | public JSValue toJSValue(JSContext context, String value) { 169 | return context.createJSString(value); 170 | } 171 | 172 | @Override 173 | public String fromJSValue(JSContext context, JSValue value) { 174 | return value.cast(JSString.class).getString(); 175 | } 176 | }; 177 | } 178 | -------------------------------------------------------------------------------- /library/src/main/java/com/hippo/quickjs/android/TypeAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.hippo.quickjs.android; 18 | 19 | import androidx.annotation.Nullable; 20 | 21 | import java.lang.reflect.Type; 22 | 23 | public abstract class TypeAdapter { 24 | /** 25 | * Converts the java value to {@code JSValue}. 26 | * Throws {@link JSDataException} if the value can't be handled. 27 | */ 28 | public abstract JSValue toJSValue(JSContext context, T value); 29 | 30 | /** 31 | * Converts the {@code JSValue} to java value. 32 | */ 33 | public abstract T fromJSValue(JSContext context, JSValue value); 34 | 35 | /** 36 | * Returns a TypeAdapter equal to this TypeAdapter, 37 | * but with support for null java object and null/undefined javascript value. 38 | */ 39 | public final TypeAdapter nullable() { 40 | return new NullableTypeAdapter<>(this); 41 | } 42 | 43 | private static class NullableTypeAdapter extends TypeAdapter { 44 | 45 | private final TypeAdapter delegate; 46 | 47 | NullableTypeAdapter(TypeAdapter delegate) { 48 | this.delegate = delegate; 49 | } 50 | 51 | @Override 52 | public JSValue toJSValue(JSContext context, T value) { 53 | if (value == null) return context.createJSNull(); 54 | return delegate.toJSValue(context, value); 55 | } 56 | 57 | @Override 58 | public T fromJSValue(JSContext context, JSValue value) { 59 | if (value instanceof JSNull || value instanceof JSUndefined) return null; 60 | return delegate.fromJSValue(context, value); 61 | } 62 | } 63 | 64 | public interface Factory { 65 | @Nullable 66 | TypeAdapter create(QuickJS quickJS, Type type); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /quickjs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | project(quickjs C) 4 | 5 | option(LEAK_TRIGGER "Add a leak trigger" FALSE) 6 | 7 | file(STRINGS "quickjs/VERSION" CONFIG_VERSION) 8 | 9 | include_directories(quickjs) 10 | 11 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 12 | set(CONFIG_CC clang) 13 | else() 14 | set(CONFIG_CC gcc) 15 | endif() 16 | 17 | set(COMMON_FLAGS -D_GNU_SOURCE -DCONFIG_VERSION=\"${CONFIG_VERSION}\" -DCONFIG_CC=\"${CONFIG_CC}\" -DCONFIG_PREFIX=\"/usr/local\" -DCONFIG_BIGNUM) 18 | 19 | set(QUICKJS_LIB_SOURCES 20 | quickjs/quickjs.c 21 | quickjs/libregexp.c 22 | quickjs/libunicode.c 23 | quickjs/libbf.c 24 | quickjs/cutils.c 25 | ) 26 | 27 | set(QJS_LIB_SOURCES 28 | quickjs/quickjs-libc.c 29 | ${QUICKJS_LIB_SOURCES} 30 | ) 31 | 32 | set(QJS_SOURCES 33 | quickjs/qjs.c 34 | repl.c 35 | qjscalc.c 36 | ${QJS_LIB_SOURCES} 37 | ) 38 | 39 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 40 | set(COMMON_FLAGS ${COMMON_FLAGS} -DDUMP_LEAKS) 41 | endif (CMAKE_BUILD_TYPE STREQUAL "Debug") 42 | 43 | if (LEAK_TRIGGER) 44 | # Use printf as leak_trigger 45 | set(COMMON_FLAGS ${COMMON_FLAGS} -Dprintf=leak_trigger) 46 | endif (LEAK_TRIGGER) 47 | 48 | # Camouflage executable files as shared libraries to make apk include them 49 | 50 | add_executable(qjs ${QJS_SOURCES}) 51 | target_compile_options(qjs PRIVATE ${COMMON_FLAGS}) 52 | set_target_properties(qjs PROPERTIES OUTPUT_NAME libqjs.so) 53 | 54 | add_executable(qjsc quickjs/qjsc.c ${QJS_LIB_SOURCES}) 55 | target_compile_options(qjsc PRIVATE ${COMMON_FLAGS}) 56 | set_target_properties(qjsc PROPERTIES OUTPUT_NAME libqjsc.so) 57 | 58 | add_executable(run-test262 quickjs/run-test262.c ${QJS_LIB_SOURCES}) 59 | target_compile_options(run-test262 PRIVATE ${COMMON_FLAGS}) 60 | set_target_properties(run-test262 PROPERTIES OUTPUT_NAME librun-test262.so) 61 | 62 | add_library(quickjs STATIC ${QUICKJS_LIB_SOURCES}) 63 | target_compile_options(quickjs PRIVATE ${COMMON_FLAGS}) 64 | target_include_directories(quickjs PUBLIC quickjs .) 65 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Hippo Seven 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | include ':android-test' 18 | include ':library' 19 | --------------------------------------------------------------------------------