├── .gitignore ├── Gruntfile.js ├── README.md ├── package.json ├── src ├── classes │ └── com │ │ └── javapoly │ │ ├── Bridge.java │ │ ├── DoppioBridge.java │ │ ├── DoppioJSObject.java │ │ ├── DoppioJSPrimitive.java │ │ ├── DoppioJSValue.java │ │ ├── Eval.java │ │ ├── FlatThrowable.java │ │ ├── JavaPolyClassLoader.java │ │ ├── Main.java │ │ ├── SimpleHttpServer.java │ │ ├── SystemBridge.java │ │ ├── SystemJSObject.java │ │ ├── SystemJSPrimitive.java │ │ ├── SystemJSValue.java │ │ ├── XHRHttpURLConnection.java │ │ ├── XHRResponse.java │ │ ├── XHRUrlStreamHandlerFactory.java │ │ ├── dom │ │ ├── Math.java │ │ └── Window.java │ │ ├── invoke │ │ ├── ConstructorUtils.java │ │ ├── MethodUtils.java │ │ └── internal │ │ │ ├── ArrayUtils.java │ │ │ ├── ClassUtils.java │ │ │ └── MemberUtils.java │ │ └── reflect │ │ ├── JSObject.java │ │ ├── JSPrimitive.java │ │ └── JSValue.java ├── core │ ├── CommonUtils.js │ ├── JavaClassWrapper.js │ ├── JavaObjectWrapper.js │ ├── JavaPoly.js │ ├── JavaPolyBase.js │ ├── JavaPolyNodeDoppio.js │ ├── JavaPolyNodeSystem.js │ ├── NodeManager.js │ ├── ProxyWrapper.js │ ├── Wrapper.js │ └── WrapperUtil.js ├── dispatcher │ ├── BrowserDispatcher.js │ ├── CommonDispatcher.js │ ├── NodeDoppioDispatcher.js │ ├── NodeSystemDispatcher.js │ ├── WorkerCallBackDispatcher.js │ └── WorkerDispatcher.js ├── jars │ ├── java_websocket.jar │ └── javax.json-1.0.4.jar ├── jvmManager │ ├── DoppioManager.js │ ├── NodeDoppioManager.js │ └── NodeSystemManager.js ├── main.js ├── main.test.js ├── natives │ ├── DoppioBridge.js │ └── XHRConnection.js ├── node-doppio.js ├── node-system.js ├── tools │ ├── classfile.js │ └── fsext.js └── webworkers │ └── JavaPolyWorker.js ├── tasks ├── grunt-compare-version.js ├── grunt-listings.js └── package │ ├── README.md │ ├── index.js │ └── package.json └── test ├── DirectRun.js ├── TestJavaPolyNodeDoppio.js ├── TestJavaPolyNodeSystem.js ├── classes ├── Counter.java ├── EvalTest.java ├── Main.java ├── Main2.java ├── Main3.java ├── MainWithPackage.java ├── Overload.java ├── References.java ├── ShaTest.java ├── Threads.java ├── URLConnectionTest.java └── com │ └── javapoly │ └── test │ └── LongTest.java ├── classfile_test.js ├── expect ├── fsext_test.js ├── index.html ├── jars ├── commons-codec-1.10.jar └── javapoly-utils.jar ├── java_source_file.html ├── mocha ├── multiple_instances.html ├── natives ├── Utilities.js └── polylistings.json ├── server.js ├── simpleResponse.bin ├── style.css └── units ├── crypto.js ├── dynamicAdd.js ├── eval.js ├── exceptions.js ├── jars.js ├── objectWrappers.js ├── proxy.js ├── reference.js ├── reflect.js ├── stringIdAccess.js ├── urls.js └── util.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Project specific 2 | 3 | *.sublime-project 4 | *.sublime-workspace 5 | test/doppio 6 | test/browserfs 7 | build/javapoly.js 8 | test/build/javapoly.js 9 | /test/classes/**/*.class 10 | 11 | ### OSX ### 12 | .DS_Store 13 | .AppleDouble 14 | .LSOverride 15 | 16 | # Icon must end with two \r 17 | Icon 18 | 19 | 20 | # Thumbnails 21 | ._* 22 | 23 | # Files that might appear in the root of a volume 24 | .DocumentRevisions-V100 25 | .fseventsd 26 | .Spotlight-V100 27 | .TemporaryItems 28 | .Trashes 29 | .VolumeIcon.icns 30 | 31 | # Directories potentially created on remote AFP share 32 | .AppleDB 33 | .AppleDesktop 34 | Network Trash Folder 35 | Temporary Items 36 | .apdisk 37 | 38 | 39 | ### Node ### 40 | # Logs 41 | logs 42 | *.log 43 | npm-debug.log* 44 | 45 | # Runtime data 46 | pids 47 | *.pid 48 | *.seed 49 | 50 | # Directory for instrumented libs generated by jscoverage/JSCover 51 | lib-cov 52 | coverage 53 | 54 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 55 | .grunt 56 | 57 | # node-waf configuration 58 | .lock-wscript 59 | 60 | # Compiled binary addons (http://nodejs.org/api/addons.html) 61 | build/Release 62 | 63 | # Dependency directory 64 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 65 | node_modules 66 | 67 | 68 | ### Windows ### 69 | # Windows image file caches 70 | Thumbs.db 71 | ehthumbs.db 72 | 73 | # Folder config file 74 | Desktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Windows Installer files 80 | *.cab 81 | *.msi 82 | *.msm 83 | *.msp 84 | 85 | # Windows shortcuts 86 | *.lnk 87 | 88 | 89 | ### Linux ### 90 | *~ 91 | 92 | # KDE directory preferences 93 | .directory 94 | 95 | # Linux trash folder which might appear on any partition or disk 96 | .Trash-* 97 | test/build 98 | 99 | # Vim 100 | *.swp 101 | 102 | # build directory 103 | /build 104 | 105 | # package directory 106 | /package 107 | 108 | # Jetbrains .idea project directory 109 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JavaPoly 2 | 3 | ## Building 4 | 5 | There is a Grunt script for building JavaPoly. So for building you have to install `grunt-cli`. Just type command: 6 | ```sh 7 | $ npm install -g grunt-cli 8 | ``` 9 | 10 | After this install all needed packages and type: 11 | ```sh 12 | $ npm install 13 | ``` 14 | 15 | For building JavaPoly browser version only run command: 16 | ```sh 17 | $ grunt build:browser 18 | ``` 19 | 20 | This creates `build/javapoly.js` file. 21 | The javapoly.js will auto-load the js library needed(browserjs.js, doppio lib and so on) from external site(eg, www.javapoly.com) 22 | 23 | You can create complete package (including JavaPoly-Node-Doppio, JavaPoly-Node-SystemJVM) by running command: 24 | ```sh 25 | $ grunt build 26 | ``` 27 | 28 | For testing JavaPoly-Node-Doppio or JavaPoly-Node-SystemJVM you can run: 29 | ```sh 30 | $ ./node_modules/mocha/bin/mocha test/TestJavaPolyNodeDoppio.js 31 | or 32 | $ ./node_modules/mocha/bin/mocha test/TestJavaPolyNodeSystem.js 33 | ``` 34 | 35 | 36 | ### Development JavaPoly 37 | 38 | To develop JavaPoly you have to run command: 39 | ```sh 40 | $ grunt watch 41 | ``` 42 | 43 | This command runs watching process that updates build-file when you change any js-file in src-folder. 44 | 45 | ### Testing JavaPoly 46 | 47 | Tests need to be run in atleast the following two browsers: Firefox and Chromium. (different 48 | browser capabilities affect the number and kind of tests that are run). 49 | 50 | There is a folder `test` which should contain a simple build of Doppio and JavaPoly. 51 | 52 | Make a build of Doppio and copy / link it into the `test/doppio` folder. 53 | 54 | Note that **watch**ing-process updates JavaPoly build in `test` folder. 55 | 56 | You can also rebuild JavaPoly for testing without watching by command: 57 | ```sh 58 | $ grunt build:test 59 | ``` 60 | 61 | **note, the final javapoly.js file which build:test task generate is a little different from the file which build:browser task generate. 62 | the javapoly.js which build:test task generate will load the doppio and browserjs library in local folders. 63 | and the javapoly.js which build:browser(or build) task generate will load the doppio and browserjs library from external web site.** 64 | 65 | To test JavaPoly you should run HTTP server for folder `test` and open `index.html` in browser. 66 | 67 | We use a custom http server for this. To run it: 68 | ```sh 69 | $ cd test 70 | $ node server.js 71 | ``` 72 | 73 | After this, open browser and navigate to http://localhost:8080/index.html. The page contains tests written in Mocha environment. 74 | 75 | You can also test JavaPoly via Mocha in nodejs. Install it by typing: 76 | ```sh 77 | $ npm install -g mocha 78 | ``` 79 | 80 | And type `mocha` in projects directory. 81 | 82 | ### Using JavaPoly Core(include JVM) in WebWorker 83 | 84 | we could use webworkers if possible(the browser support) , to avoid blocking the main thread. 85 | 86 | simply add a worker options in javapoly. 87 | The options value is the url of the javapoly_worker.js.(default value is null for not using webworkers) 88 | 89 | eg. 90 | 91 | ```js 92 | new JavaPoly(worker : 'build/javapoly_worker.js'); 93 | ``` 94 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javapoly", 3 | "version": "0.1.0", 4 | "description": "JavaPoly.js is a library that polyfills native JVM support in the browser. It allows you to import your existing Java code, and invoke the code directly from Javascript.", 5 | "repository": { 6 | "type": "git", 7 | "url": "http://git.javadeploy.net/jimsproch/JavaPoly.git" 8 | }, 9 | "keywords": [ 10 | "java", 11 | "javascript", 12 | "doppio" 13 | ], 14 | "scripts": { 15 | "test": "node_modules/http-server/bin/http-server test/", 16 | "postinstall": "node node_modules/jvminstall" 17 | }, 18 | "author": "", 19 | "license": "ISC", 20 | "devDependencies": { 21 | "babel-plugin-transform-runtime": "^6.8.0", 22 | "babel-preset-es2015": "^6.6.0", 23 | "babel-runtime": "^6.6.1", 24 | "babelify": "^7.3.0", 25 | "expect": "^1.20.1", 26 | "grunt": "^0.4.5", 27 | "grunt-babel": "^6.0.0", 28 | "grunt-browserify": "^4.0.1", 29 | "grunt-contrib-clean": "^1.0.0", 30 | "grunt-contrib-copy": "^1.0.0", 31 | "grunt-contrib-symlink": "^1.0.0", 32 | "grunt-contrib-watch": "^0.6.1", 33 | "grunt-mkdir": "^1.0.0", 34 | "grunt-newer": "^1.1.2", 35 | "grunt-nodemon": "^0.4.1", 36 | "grunt-run-java": "^0.1.1", 37 | "http-server": "^0.8.5", 38 | "mocha": "^2.4.5", 39 | "semver": "^5.1.0", 40 | "underscore": "^1.8.3" 41 | }, 42 | "dependencies": { 43 | "@hrj/doppiojvm-snapshot": "^0.4.13", 44 | "browserfs": "^0.5.12", 45 | "jsjavaparser": "https://github.com/sreym/jsjavaparser.git", 46 | "csrf": "^3.0.1", 47 | "jvminstall": "^0.1.0", 48 | "ws": "^1.1.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/Bridge.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | interface Bridge { 6 | String getMessageId(); 7 | Object[] getData(String messageId); 8 | String getMessageType(String messageId); 9 | void dispatchMessage(String messageId); 10 | void returnResult(String messageId, Object returnValue); 11 | void returnErrorFlat(String messageId, FlatThrowable ft); 12 | void setJavaPolyInstanceId(String javapolyId); 13 | 14 | JSValue eval(String s); 15 | JSValue wrapValue(String descripton, Object obj); 16 | JSValue reflectJSValue(final Object[] obj); 17 | Object[] reflectParams(final Object[] params); 18 | } 19 | 20 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/DoppioBridge.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | class DoppioBridge implements Bridge { 6 | public native String getMessageId(); 7 | public native Object[] getData(String messageId); 8 | public native String getMessageType(String messageId); 9 | public native void dispatchMessage(String messageId); 10 | public native void returnResult(String messageId, Object returnValue); 11 | public native void returnErrorFlat(String messageId, FlatThrowable ft); 12 | public native void setJavaPolyInstanceId(String javapolyId); 13 | 14 | private static native boolean isJSNativeObj(Object obj); 15 | 16 | private static native Object[] getRawType(Object obj); 17 | 18 | public JSValue eval(final String s) { 19 | final Object[] res = evalRaw(s); 20 | return wrapValue((String) res[0], res[1]); 21 | } 22 | 23 | /** Evals the provided string and returns the raw javascript result. 24 | * 25 | * In the returned array, the first element is of type `String`, containing the result of JS `typeof`. 26 | * and the second element is the actual result of eval. 27 | * */ 28 | private static native Object[] evalRaw(String s); 29 | 30 | public JSValue wrapValue(String description, Object obj) { 31 | switch (description) { 32 | case "object": 33 | case "function": 34 | return new DoppioJSObject(obj); 35 | case "undefined": 36 | case "boolean": 37 | case "number": 38 | case "string": 39 | return new DoppioJSPrimitive(obj); 40 | default: 41 | // TODO 42 | return null; 43 | } 44 | } 45 | 46 | /** Wraps the provided JS object so that it can be introspected in Java */ 47 | public JSValue reflectJSValue(final Object[] obj) { 48 | final Object[] data = getRawType(obj[0]); 49 | return wrapValue((String) data[0], data[1]); 50 | } 51 | 52 | public Object[] reflectParams(final Object[] params) { 53 | final Object[] reflected = new Object[params.length]; 54 | 55 | for (int i = 0;i < params.length; i++) { 56 | if (isJSNativeObj(params[i])) { 57 | reflected[i] = reflectJSValue(new Object[]{params[i]}); 58 | } else { 59 | reflected[i] = params[i]; 60 | } 61 | } 62 | 63 | return reflected; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/DoppioJSObject.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.Main; 4 | import com.javapoly.reflect.*; 5 | 6 | class DoppioJSObject extends DoppioJSValue implements JSObject { 7 | DoppioJSObject(final Object rawValue) { 8 | super(rawValue); 9 | } 10 | 11 | public JSValue getProperty(String name) { 12 | return Main.wrapValue(getProperty(rawValue, name)); 13 | } 14 | 15 | public JSValue invoke(Object... args) { 16 | final Object[] unwrappedArgs = new Object[args.length]; 17 | for (int i = 0; i < args.length; i++) { 18 | final Object e = args[i]; 19 | unwrappedArgs[i] = (e instanceof DoppioJSValue) ? ((DoppioJSValue) e).rawValue : e; 20 | } 21 | return Main.wrapValue(invoke(rawValue, unwrappedArgs)); 22 | } 23 | 24 | private static native Object[] invoke(Object functionObj, Object... args); 25 | 26 | private static native Object[] getProperty(Object obj, String name); 27 | } 28 | 29 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/DoppioJSPrimitive.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.Eval; 4 | import com.javapoly.reflect.*; 5 | 6 | class DoppioJSPrimitive extends DoppioJSValue implements JSPrimitive { 7 | DoppioJSPrimitive(final Object rawValue) { 8 | super(rawValue); 9 | } 10 | 11 | public boolean isNull() { 12 | return rawValue == null; 13 | } 14 | 15 | public double asDouble() { 16 | return asDouble(rawValue); 17 | } 18 | 19 | public int asInteger() { 20 | return asInteger(rawValue); 21 | } 22 | 23 | public long asLong() { 24 | return asLong(rawValue); 25 | } 26 | 27 | public String asString() { 28 | return asString(rawValue); 29 | } 30 | 31 | private static native double asDouble(Object obj); 32 | private static native String asString(Object obj); 33 | private static native int asInteger(Object obj); 34 | private static native long asLong(Object obj); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/DoppioJSValue.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | abstract class DoppioJSValue implements JSValue { 6 | final Object rawValue; 7 | DoppioJSValue(final Object rawValue) { 8 | this.rawValue = rawValue; 9 | } 10 | 11 | /* Although this works, doppio dev build has an assertion that prevents a java function from returning pure JS objects */ 12 | /* 13 | public Object getRawValue() { 14 | return rawValue; 15 | } 16 | */ 17 | }; 18 | 19 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/Eval.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | public final class Eval { 6 | 7 | /** Evals the provided string and returns a wrapped result that can be introspected in Java */ 8 | public static final JSValue eval(String s) { 9 | final SecurityManager security = System.getSecurityManager(); 10 | if (security != null) { 11 | security.checkPermission(new RuntimePermission("javascriptEval")); 12 | } 13 | return Main.bridgedEval(s); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/FlatThrowable.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | /** A flattened version of Throwable for easy serialisation to JS */ 4 | class FlatThrowable { 5 | final String name; 6 | final String message; 7 | final String[] stack; 8 | final FlatThrowable causedBy; 9 | 10 | FlatThrowable(final String name, final String message, final String[] stack, final FlatThrowable causedBy) { 11 | this.name = name; 12 | this.message = message; 13 | this.stack = stack; 14 | this.causedBy = causedBy; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/JavaPolyClassLoader.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | import java.net.URLClassLoader; 6 | 7 | public class JavaPolyClassLoader extends URLClassLoader { 8 | 9 | public JavaPolyClassLoader(URL[] urls) { 10 | super(urls); 11 | } 12 | 13 | public void addUrl(String url) throws MalformedURLException{ 14 | super.addURL(new URL(url)); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/SimpleHttpServer.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import java.net.*; 4 | import java.io.*; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | final class SimpleHttpServer { 9 | 10 | private final ServerSocket socket; 11 | 12 | SimpleHttpServer(final int port) throws IOException { 13 | socket = new ServerSocket(port); 14 | } 15 | 16 | public int getPort() { 17 | return socket.getLocalPort(); 18 | } 19 | 20 | public void process(ConnectionHandler handler) { 21 | try { 22 | final Socket connection = socket.accept(); 23 | handleConnection(connection, handler); 24 | } catch (IOException e) { 25 | System.out.println("Exception: " + e.getMessage()); 26 | e.printStackTrace(); 27 | } 28 | } 29 | 30 | public interface ConnectionHandler { 31 | void handle(Map headers, final String requestMethod, final String requestUrl, final String body, final Socket connection); 32 | } 33 | 34 | // Note: The handler is expected to close the connection eventually. 35 | private static void handleConnection(final Socket connection, final ConnectionHandler handler) { 36 | try { 37 | final BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); 38 | 39 | final String request = in.readLine(); 40 | final Map headers = new HashMap<>(); 41 | boolean headerFound = true; 42 | do { 43 | final String header = in.readLine(); 44 | if (header != null && (!"".equals(header))) { 45 | final String[] fields = header.split(":\\s*"); 46 | headers.put(fields[0], fields[1]); 47 | } else { 48 | headerFound = false; 49 | } 50 | } while (headerFound); 51 | 52 | final String contentLength = headers.get("Content-Length"); 53 | final int expectedLength = contentLength != null ? Integer.parseInt(contentLength) : -1; 54 | // System.out.println("Headers: " + headers.size()); 55 | 56 | StringBuffer bodySb = new StringBuffer(); 57 | int ch = 0; 58 | int count = 0; 59 | do { 60 | ch = in.read(); 61 | if (ch >= 0) { 62 | bodySb.append((char) ch); 63 | count++; 64 | } 65 | } while (ch >= 0 && (expectedLength == -1 || count < expectedLength)); 66 | 67 | // System.out.println("After header: " + bodySb.toString()); 68 | 69 | final String[] requestFields = request.split("\\s+"); 70 | handler.handle(headers, requestFields[0], requestFields[1], bodySb.toString(), connection); 71 | 72 | } catch (IOException e) { 73 | System.out.println("Exception: " + e.getMessage()); 74 | e.printStackTrace(); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/SystemJSObject.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | class SystemJSObject extends SystemJSValue implements JSObject { 6 | private final SystemBridge bridge; 7 | 8 | SystemJSObject(final Object rawValue, final SystemBridge bridge) { 9 | super(rawValue); 10 | this.bridge = bridge; 11 | } 12 | 13 | public JSValue getProperty(String name) { 14 | final JSValue result = bridge.getObjectProperty(name, (Integer) rawValue); 15 | return result; 16 | } 17 | 18 | public JSValue invoke(Object... args) { 19 | final Object[] unwrappedArgs = new Object[args.length]; 20 | for (int i = 0; i < args.length; i++) { 21 | final Object e = args[i]; 22 | unwrappedArgs[i] = (e instanceof SystemJSValue) ? ((SystemJSValue) e).rawValue : e; 23 | } 24 | return invoke(rawValue, unwrappedArgs); 25 | } 26 | 27 | private JSValue invoke(Object functionObj, Object... args) { 28 | return bridge.invoke(functionObj, args); 29 | } 30 | 31 | public int getRawValue() { 32 | return (Integer) rawValue; 33 | } 34 | } 35 | 36 | 37 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/SystemJSPrimitive.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.Eval; 4 | import com.javapoly.reflect.*; 5 | 6 | final class SystemJSPrimitive extends SystemJSValue implements JSPrimitive { 7 | public SystemJSPrimitive(final Object rawValue) { 8 | super(rawValue); 9 | } 10 | 11 | public boolean isNull() { 12 | return rawValue == null; 13 | } 14 | 15 | public double asDouble() { 16 | return ((Number) rawValue).doubleValue(); 17 | } 18 | 19 | public int asInteger() { 20 | return ((Number) rawValue).intValue(); 21 | } 22 | 23 | public long asLong() { 24 | return ((Number) rawValue).longValue(); 25 | } 26 | 27 | public String asString() { 28 | return (String)rawValue; 29 | } 30 | 31 | public Object getRawValue() { 32 | // TODO: Check for null 33 | return rawValue; 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/SystemJSValue.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import com.javapoly.reflect.*; 4 | 5 | abstract class SystemJSValue implements JSValue { 6 | final protected Object rawValue; 7 | 8 | public SystemJSValue(final Object rawValue) { 9 | this.rawValue = rawValue; 10 | } 11 | 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/XHRHttpURLConnection.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.io.ByteArrayOutputStream; 7 | import java.net.HttpURLConnection; 8 | import java.net.URL; 9 | import java.net.URLConnection; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.ExecutionException; 13 | import java.util.Map; 14 | import java.util.List; 15 | import java.util.Iterator; 16 | 17 | class XHRHttpURLConnection extends HttpURLConnection { 18 | private final AtomicBoolean connectionStarted = new AtomicBoolean(false); 19 | private final CompletableFuture responseFuture = new CompletableFuture<>(); 20 | private ByteArrayOutputStream outputStream = null; 21 | 22 | XHRHttpURLConnection(final URL url) { 23 | super(url); 24 | } 25 | 26 | /* Connect only if doOutput == false */ 27 | private void connectLazy() { 28 | if (!getDoOutput()) { 29 | connect(); 30 | } 31 | } 32 | 33 | @Override public void connect() { 34 | if (!connectionStarted.get()) { 35 | connectionStarted.set(true); 36 | new Thread() { 37 | public void run() { 38 | final Map> requestPropertyMap= getRequestProperties(); 39 | final Iterator requestPropretyKeys = requestPropertyMap.keySet().iterator(); 40 | final String[] requestProperties = new String[requestPropertyMap.size() * 2]; 41 | { 42 | int i = 0; 43 | while (requestPropretyKeys.hasNext()) { 44 | final String key = requestPropretyKeys.next(); 45 | requestProperties[i++] = key; 46 | requestProperties[i++] = String.join(", ", requestPropertyMap.get(key)); 47 | } 48 | } 49 | 50 | final byte[] outputBytes = outputStream == null ? null : outputStream.toByteArray(); 51 | 52 | final XHRResponse response= getResponse(requestProperties, getRequestMethod(), getURL().toString(), outputBytes); 53 | responseFuture.complete(response); 54 | XHRHttpURLConnection.this.connected = true; 55 | } 56 | }.start(); 57 | } 58 | } 59 | 60 | @Override public String getHeaderField(final String name) { 61 | connect(); 62 | 63 | try { 64 | return responseFuture.get().getHeaderField(name); 65 | } catch (final InterruptedException ie) { 66 | throw new RuntimeException("Interrupted while waiting for connection", ie); 67 | } catch (final ExecutionException ee) { 68 | throw new RuntimeException("Error connecting to URL", ee); 69 | } 70 | } 71 | 72 | @Override public InputStream getInputStream() throws IOException { 73 | connectLazy(); 74 | 75 | return new LazyResponseInputStream(); 76 | } 77 | 78 | @Override public OutputStream getOutputStream() throws IOException { 79 | if (outputStream == null) { 80 | outputStream = new ByteArrayOutputStream(); 81 | } 82 | 83 | return outputStream; 84 | } 85 | 86 | @Override public void disconnect() { 87 | // TODO 88 | System.out.println("disconnect request to: " + getURL()); 89 | } 90 | 91 | @Override public boolean usingProxy() { 92 | return false; 93 | } 94 | 95 | @Override public final void setRequestProperty(String field, String newValue) { 96 | // TODO 97 | System.out.println("Need to set request property: " + field + ": " + newValue); 98 | } 99 | 100 | private static native XHRResponse getResponse(final String[] requestProperties, final String method, final String url, final byte[] outputBytes); 101 | 102 | private class LazyResponseInputStream extends InputStream { 103 | private int currPos = -1; 104 | private byte[] responseBytes = null; 105 | 106 | @Override public int read() { 107 | connect(); 108 | 109 | try { 110 | if (currPos < 0) { 111 | responseBytes = responseFuture.get().getResponseBytes(); 112 | currPos = 0; 113 | } 114 | 115 | if (currPos < responseBytes.length) { 116 | return responseBytes[currPos++]; 117 | } else { 118 | return -1; 119 | } 120 | } catch (final InterruptedException ie) { 121 | throw new RuntimeException("Interrupted while waiting for connection", ie); 122 | } catch (final ExecutionException ee) { 123 | throw new RuntimeException("Error connecting to URL", ee); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/XHRResponse.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | class XHRResponse { 4 | final Object xhrNativeObj; 5 | 6 | XHRResponse(final Object xhrNativeObj) { 7 | this.xhrNativeObj = xhrNativeObj; 8 | } 9 | 10 | byte[] getResponseBytes() { 11 | return getResponseBytes(xhrNativeObj); 12 | } 13 | 14 | String getHeaderField(String name) { 15 | return getHeaderField(xhrNativeObj, name); 16 | } 17 | 18 | private static native byte[] getResponseBytes(Object xhrNativeObj); 19 | 20 | private static native String getHeaderField(Object xhrNativeObj, String name); 21 | } 22 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/XHRUrlStreamHandlerFactory.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.net.HttpURLConnection; 6 | import java.net.URL; 7 | import java.net.URLConnection; 8 | import java.net.URLStreamHandler; 9 | import java.net.URLStreamHandlerFactory; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | import java.util.concurrent.CompletableFuture; 12 | import java.util.concurrent.ExecutionException; 13 | 14 | public class XHRUrlStreamHandlerFactory implements URLStreamHandlerFactory { 15 | public static void register() { 16 | URL.setURLStreamHandlerFactory(new XHRUrlStreamHandlerFactory()); 17 | } 18 | 19 | public URLStreamHandler createURLStreamHandler(String protocol) { 20 | if ("http".equals(protocol) || "https".equals(protocol)) { 21 | return new URLStreamHandler() { 22 | public HttpURLConnection openConnection(URL url) { 23 | return new XHRHttpURLConnection(url); 24 | } 25 | }; 26 | } else { 27 | return null; 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/dom/Math.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.dom; 2 | 3 | import com.javapoly.Eval; 4 | import com.javapoly.reflect.JSObject; 5 | import com.javapoly.reflect.JSPrimitive; 6 | 7 | public class Math { 8 | 9 | public static double abs(double val) { 10 | final JSPrimitive result = (JSPrimitive)((JSObject)Eval.eval("Math.abs")).invoke(val); 11 | return result.asDouble(); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/dom/Window.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.dom; 2 | 3 | import com.javapoly.Eval; 4 | import com.javapoly.reflect.JSObject; 5 | 6 | public class Window { 7 | 8 | public static void alert(String message) { 9 | ((JSObject)Eval.eval("window.alert")).invoke(message); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/invoke/ConstructorUtils.java: -------------------------------------------------------------------------------- 1 | // Forked from apache.common.reflect 2 | 3 | /* 4 | * Licensed to the Apache Software Foundation (ASF) under one or more 5 | * contributor license agreements. See the NOTICE file distributed with 6 | * this work for additional information regarding copyright ownership. 7 | * The ASF licenses this file to You under the Apache License, Version 2.0 8 | * (the "License"); you may not use this file except in compliance with 9 | * the License. You may obtain a copy of the License at 10 | * 11 | * http://www.apache.org/licenses/LICENSE-2.0 12 | * 13 | * Unless required by applicable law or agreed to in writing, software 14 | * distributed under the License is distributed on an "AS IS" BASIS, 15 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | * See the License for the specific language governing permissions and 17 | * limitations under the License. 18 | */ 19 | 20 | package com.javapoly.invoke; 21 | 22 | import java.lang.reflect.Constructor; 23 | import java.lang.reflect.InvocationTargetException; 24 | import java.lang.reflect.Modifier; 25 | 26 | import com.javapoly.invoke.internal.ArrayUtils; 27 | import com.javapoly.invoke.internal.ClassUtils; 28 | import com.javapoly.invoke.internal.MemberUtils; 29 | 30 | /** 31 | *

Utility reflection methods focused on constructors, modeled after 32 | * {@link MethodUtils}.

33 | * 34 | *

Known Limitations

Accessing Public Constructors In A Default 35 | * Access Superclass

There is an issue when invoking {@code public} constructors 36 | * contained in a default access superclass. Reflection correctly locates these 37 | * constructors and assigns them as {@code public}. However, an 38 | * {@link IllegalAccessException} is thrown if the constructor is 39 | * invoked.

40 | * 41 | *

{@link ConstructorUtils} contains a workaround for this situation: it 42 | * will attempt to call {@link java.lang.reflect.AccessibleObject#setAccessible(boolean)} on this constructor. If this 43 | * call succeeds, then the method can be invoked as normal. This call will only 44 | * succeed when the application has sufficient security privileges. If this call 45 | * fails then a warning will be logged and the method may fail.

46 | * 47 | * @since 2.5 48 | */ 49 | public class ConstructorUtils { 50 | 51 | /** 52 | *

ConstructorUtils instances should NOT be constructed in standard 53 | * programming. Instead, the class should be used as 54 | * {@code ConstructorUtils.invokeConstructor(cls, args)}.

55 | * 56 | *

This constructor is {@code public} to permit tools that require a JavaBean 57 | * instance to operate.

58 | */ 59 | public ConstructorUtils() { 60 | super(); 61 | } 62 | 63 | /** 64 | *

Checks if the specified constructor is accessible.

65 | * 66 | *

This simply ensures that the constructor is accessible.

67 | * 68 | * @param the constructor type 69 | * @param ctor the prototype constructor object, not {@code null} 70 | * @return the constructor, {@code null} if no matching accessible constructor found 71 | * @see java.lang.SecurityManager 72 | * @throws NullPointerException if {@code ctor} is {@code null} 73 | */ 74 | public static Constructor getAccessibleConstructor(final Constructor ctor) { 75 | return MemberUtils.isAccessible(ctor) 76 | && isAccessible(ctor.getDeclaringClass()) ? ctor : null; 77 | } 78 | 79 | /** 80 | * Learn whether the specified class is generally accessible, i.e. is 81 | * declared in an entirely {@code public} manner. 82 | * @param type to check 83 | * @return {@code true} if {@code type} and any enclosing classes are 84 | * {@code public}. 85 | */ 86 | private static boolean isAccessible(final Class type) { 87 | Class cls = type; 88 | while (cls != null) { 89 | if (!Modifier.isPublic(cls.getModifiers())) { 90 | return false; 91 | } 92 | cls = cls.getEnclosingClass(); 93 | } 94 | return true; 95 | } 96 | 97 | // -------------- 98 | // Fuzzy matching 99 | 100 | public static T invokeConstructorFuzzy(final Class cls, Object... args) 101 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, 102 | InstantiationException { 103 | args = ArrayUtils.nullToEmpty(args); 104 | final Class[] parameterTypes = ClassUtils.toClass(args); 105 | final Constructor ctor = getMatchingAccessibleConstructorFuzzy(cls, parameterTypes, args); 106 | if (ctor == null) { 107 | throw new NoSuchMethodException( 108 | "No such accessible constructor on object: " + cls.getName()); 109 | } 110 | args = MethodUtils.castArgumentsForMethodFuzzy(parameterTypes, ctor.getParameterTypes(), args); 111 | return ctor.newInstance(args); 112 | } 113 | 114 | public static Constructor getMatchingAccessibleConstructorFuzzy(final Class cls, 115 | final Class[] parameterTypes, Object[] args) { 116 | // see if we can find the constructor directly 117 | // most of the time this works and it's much faster 118 | try { 119 | final Constructor ctor = cls.getConstructor(parameterTypes); 120 | MemberUtils.setAccessibleWorkaround(ctor); 121 | return ctor; 122 | } catch (final NoSuchMethodException e) { // NOPMD - Swallow 123 | } 124 | Constructor result = null; 125 | /* 126 | * (1) Class.getConstructors() is documented to return Constructor so as 127 | * long as the array is not subsequently modified, everything's fine. 128 | */ 129 | final Constructor[] ctors = cls.getConstructors(); 130 | 131 | // return best match: 132 | for (Constructor ctor : ctors) { 133 | // compare parameters 134 | if (MethodUtils.isAssignableFuzzy(parameterTypes, ctor.getParameterTypes(), args)) { 135 | // get accessible version of constructor 136 | ctor = getAccessibleConstructor(ctor); 137 | if (ctor != null) { 138 | MemberUtils.setAccessibleWorkaround(ctor); 139 | if (result == null 140 | || MemberUtils.compareParameterTypes(ctor.getParameterTypes(), result 141 | .getParameterTypes(), parameterTypes) < 0) { 142 | final Constructor constructor = (Constructor)ctor; 143 | result = constructor; 144 | } 145 | } 146 | } 147 | } 148 | return result; 149 | } 150 | 151 | // Fuzzy matching 152 | // -------------- 153 | } 154 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/invoke/internal/ArrayUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.javapoly.invoke.internal; 18 | 19 | import java.lang.reflect.Array; 20 | 21 | public class ArrayUtils { 22 | 23 | /** 24 | * An empty immutable {@code Object} array. 25 | */ 26 | public static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; 27 | 28 | /** 29 | * An empty immutable {@code Class} array. 30 | */ 31 | public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; 32 | 33 | /** 34 | *

ArrayUtils instances should NOT be constructed in standard programming. 35 | * Instead, the class should be used as ArrayUtils.clone(new int[] {2}).

36 | * 37 | *

This constructor is public to permit tools that require a JavaBean instance 38 | * to operate.

39 | */ 40 | public ArrayUtils() { 41 | super(); 42 | } 43 | 44 | /** 45 | *

Defensive programming technique to change a {@code null} 46 | * reference to an empty one.

47 | * 48 | *

This method returns an empty array for a {@code null} input array.

49 | * 50 | *

As a memory optimizing technique an empty array passed in will be overridden with 51 | * the empty {@code public static} references in this class.

52 | * 53 | * @param array the array to check for {@code null} or empty 54 | * @return the same array, {@code public static} empty array if {@code null} or empty input 55 | * @since 2.5 56 | */ 57 | public static Object[] nullToEmpty(final Object[] array) { 58 | if (isEmpty(array)) { 59 | return EMPTY_OBJECT_ARRAY; 60 | } 61 | return array; 62 | } 63 | 64 | /** 65 | *

Checks if an array of Objects is empty or {@code null}.

66 | * 67 | * @param array the array to test 68 | * @return {@code true} if the array is empty or {@code null} 69 | * @since 2.1 70 | */ 71 | public static boolean isEmpty(final Object[] array) { 72 | return getLength(array) == 0; 73 | } 74 | 75 | /** 76 | *

Returns the length of the specified array. 77 | * This method can deal with {@code Object} arrays and with primitive arrays.

78 | * 79 | *

If the input array is {@code null}, {@code 0} is returned.

80 | * 81 | *
 82 |    * ArrayUtils.getLength(null)            = 0
 83 |    * ArrayUtils.getLength([])              = 0
 84 |    * ArrayUtils.getLength([null])          = 1
 85 |    * ArrayUtils.getLength([true, false])   = 2
 86 |    * ArrayUtils.getLength([1, 2, 3])       = 3
 87 |    * ArrayUtils.getLength(["a", "b", "c"]) = 3
 88 |    * 
89 | * 90 | * @param array the array to retrieve the length from, may be null 91 | * @return The length of the array, or {@code 0} if the array is {@code null} 92 | * @throws IllegalArgumentException if the object argument is not an array. 93 | * @since 2.1 94 | */ 95 | public static int getLength(final Object array) { 96 | if (array == null) { 97 | return 0; 98 | } 99 | return Array.getLength(array); 100 | } 101 | 102 | /** 103 | *

Checks whether two arrays are the same length, treating 104 | * {@code null} arrays as length {@code 0}. 105 | * 106 | *

Any multi-dimensional aspects of the arrays are ignored.

107 | * 108 | * @param array1 the first array, may be {@code null} 109 | * @param array2 the second array, may be {@code null} 110 | * @return {@code true} if length of arrays matches, treating 111 | * {@code null} as an empty array 112 | */ 113 | public static boolean isSameLength(final Object[] array1, final Object[] array2) { 114 | return getLength(array1) == getLength(array2); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/invoke/internal/MemberUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package com.javapoly.invoke.internal; 18 | 19 | import java.lang.reflect.AccessibleObject; 20 | import java.lang.reflect.Member; 21 | import java.lang.reflect.Modifier; 22 | 23 | /** 24 | * Contains common code for working with {@link java.lang.reflect.Method Methods}/{@link java.lang.reflect.Constructor Constructors}, 25 | * extracted and refactored from {@link MethodUtils} when it was imported from Commons BeanUtils. 26 | * 27 | * @since 2.5 28 | */ 29 | public abstract class MemberUtils { 30 | // TODO extract an interface to implement compareParameterSets(...)? 31 | 32 | private static final int ACCESS_TEST = Modifier.PUBLIC | Modifier.PROTECTED | Modifier.PRIVATE; 33 | 34 | /** Array of primitive number types ordered by "promotability" */ 35 | private static final Class[] ORDERED_PRIMITIVE_TYPES = { Byte.TYPE, Short.TYPE, 36 | Character.TYPE, Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE }; 37 | 38 | /** 39 | * XXX Default access superclass workaround. 40 | * 41 | * When a {@code public} class has a default access superclass with {@code public} members, 42 | * these members are accessible. Calling them from compiled code works fine. 43 | * Unfortunately, on some JVMs, using reflection to invoke these members 44 | * seems to (wrongly) prevent access even when the modifier is {@code public}. 45 | * Calling {@code setAccessible(true)} solves the problem but will only work from 46 | * sufficiently privileged code. Better workarounds would be gratefully 47 | * accepted. 48 | * @param o the AccessibleObject to set as accessible 49 | * @return a boolean indicating whether the accessibility of the object was set to true. 50 | */ 51 | public static boolean setAccessibleWorkaround(final AccessibleObject o) { 52 | if (o == null || o.isAccessible()) { 53 | return false; 54 | } 55 | final Member m = (Member) o; 56 | if (!o.isAccessible() && Modifier.isPublic(m.getModifiers()) && isPackageAccess(m.getDeclaringClass().getModifiers())) { 57 | try { 58 | o.setAccessible(true); 59 | return true; 60 | } catch (final SecurityException e) { // NOPMD 61 | // ignore in favor of subsequent IllegalAccessException 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | /** 68 | * Returns whether a given set of modifiers implies package access. 69 | * @param modifiers to test 70 | * @return {@code true} unless {@code package}/{@code protected}/{@code private} modifier detected 71 | */ 72 | static boolean isPackageAccess(final int modifiers) { 73 | return (modifiers & ACCESS_TEST) == 0; 74 | } 75 | 76 | /** 77 | * Returns whether a {@link Member} is accessible. 78 | * @param m Member to check 79 | * @return {@code true} if m is accessible 80 | */ 81 | public static boolean isAccessible(final Member m) { 82 | return m != null && Modifier.isPublic(m.getModifiers()) && !m.isSynthetic(); 83 | } 84 | 85 | /** 86 | * Compares the relative fitness of two sets of parameter types in terms of 87 | * matching a third set of runtime parameter types, such that a list ordered 88 | * by the results of the comparison would return the best match first 89 | * (least). 90 | * 91 | * @param left the "left" parameter set 92 | * @param right the "right" parameter set 93 | * @param actual the runtime parameter types to match against 94 | * {@code left}/{@code right} 95 | * @return int consistent with {@code compare} semantics 96 | */ 97 | public static int compareParameterTypes(final Class[] left, final Class[] right, final Class[] actual) { 98 | final float leftCost = getTotalTransformationCost(actual, left); 99 | final float rightCost = getTotalTransformationCost(actual, right); 100 | return leftCost < rightCost ? -1 : rightCost < leftCost ? 1 : 0; 101 | } 102 | 103 | /** 104 | * Returns the sum of the object transformation cost for each class in the 105 | * source argument list. 106 | * @param srcArgs The source arguments 107 | * @param destArgs The destination arguments 108 | * @return The total transformation cost 109 | */ 110 | private static float getTotalTransformationCost(final Class[] srcArgs, final Class[] destArgs) { 111 | float totalCost = 0.0f; 112 | for (int i = 0; i < srcArgs.length; i++) { 113 | Class srcClass, destClass; 114 | srcClass = srcArgs[i]; 115 | destClass = destArgs[i]; 116 | totalCost += getObjectTransformationCost(srcClass, destClass); 117 | } 118 | return totalCost; 119 | } 120 | 121 | /** 122 | * Gets the number of steps required needed to turn the source class into 123 | * the destination class. This represents the number of steps in the object 124 | * hierarchy graph. 125 | * @param srcClass The source class 126 | * @param destClass The destination class 127 | * @return The cost of transforming an object 128 | */ 129 | private static float getObjectTransformationCost(Class srcClass, final Class destClass) { 130 | if (destClass.isPrimitive()) { 131 | return getPrimitivePromotionCost(srcClass, destClass); 132 | } 133 | float cost = 0.0f; 134 | while (srcClass != null && !destClass.equals(srcClass)) { 135 | if (destClass.isInterface() && ClassUtils.isAssignable(srcClass, destClass)) { 136 | // slight penalty for interface match. 137 | // we still want an exact match to override an interface match, 138 | // but 139 | // an interface match should override anything where we have to 140 | // get a superclass. 141 | cost += 0.25f; 142 | break; 143 | } 144 | cost++; 145 | srcClass = srcClass.getSuperclass(); 146 | } 147 | /* 148 | * If the destination class is null, we've travelled all the way up to 149 | * an Object match. We'll penalize this by adding 1.5 to the cost. 150 | */ 151 | if (srcClass == null) { 152 | cost += 1.5f; 153 | } 154 | return cost; 155 | } 156 | 157 | /** 158 | * Gets the number of steps required to promote a primitive number to another 159 | * type. 160 | * @param srcClass the (primitive) source class 161 | * @param destClass the (primitive) destination class 162 | * @return The cost of promoting the primitive 163 | */ 164 | private static float getPrimitivePromotionCost(final Class srcClass, final Class destClass) { 165 | float cost = 0.0f; 166 | Class cls = srcClass; 167 | if (!cls.isPrimitive()) { 168 | // slight unwrapping penalty 169 | cost += 0.1f; 170 | cls = ClassUtils.wrapperToPrimitive(cls); 171 | } 172 | for (int i = 0; cls != destClass && i < ORDERED_PRIMITIVE_TYPES.length; i++) { 173 | if (cls == ORDERED_PRIMITIVE_TYPES[i]) { 174 | cost += 0.1f; 175 | if (i < ORDERED_PRIMITIVE_TYPES.length - 1) { 176 | cls = ORDERED_PRIMITIVE_TYPES[i + 1]; 177 | } 178 | } 179 | } 180 | return cost; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/reflect/JSObject.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.reflect; 2 | 3 | import com.javapoly.Eval; 4 | 5 | public interface JSObject extends JSValue { 6 | public JSValue getProperty(String name); 7 | 8 | public JSValue invoke(Object... args); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/reflect/JSPrimitive.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.reflect; 2 | 3 | import com.javapoly.Eval; 4 | 5 | public interface JSPrimitive extends JSValue { 6 | public boolean isNull(); 7 | 8 | public double asDouble(); 9 | 10 | public int asInteger(); 11 | 12 | public long asLong(); 13 | 14 | public String asString(); 15 | } 16 | 17 | -------------------------------------------------------------------------------- /src/classes/com/javapoly/reflect/JSValue.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.reflect; 2 | 3 | public interface JSValue { 4 | /* Although this works, doppio dev build has an assertion that prevents a java function from returning pure JS objects */ 5 | /* 6 | public Object getRawValue(); 7 | */ 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /src/core/CommonUtils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaParser from 'jsjavaparser'; 3 | 4 | const CLASS_MAGIC_NUMBER = 'cafebabe'; 5 | const ZIP_MAGIC_NUMBER = '504b0304'; 6 | 7 | 8 | class CommonUtils { 9 | static xhrRetrieve (url, responseType) { 10 | return new Promise((resolve, reject) => { 11 | const xmlr = new XMLHttpRequest(); 12 | xmlr.open('GET', url, true); 13 | xmlr.responseType = responseType; 14 | xmlr.onreadystatechange = ()=> { 15 | if (xmlr.readyState === 4) { 16 | if (xmlr.status === 200) { 17 | resolve(xmlr.response); 18 | } else { 19 | reject(); 20 | } 21 | } 22 | } 23 | xmlr.send(null); 24 | }); 25 | } 26 | 27 | static hexFromBuffer(buffer, from, count) { 28 | var str = []; 29 | let bufferGetter = (global.BrowserFS) ? (index) => { return buffer.get(index); } : (index) => {return buffer[index]; }; 30 | for(let i = 0; i < count; i++) { 31 | var ss = bufferGetter(from + i).toString(16); 32 | if (ss.length < 2) ss = '0' + ss; 33 | str.push(ss); 34 | } 35 | return str.join(''); 36 | } 37 | 38 | /** 39 | * Detects if passed 'data' is zip file 40 | * @param {String|Buffer} data URL string or data buffer 41 | * @return {Boolean} 42 | */ 43 | static isZipFile(data){ 44 | if (typeof data === 'string') { 45 | return data.endsWith('.jar') || data.endsWith('.zip'); 46 | } else { 47 | return ZIP_MAGIC_NUMBER === CommonUtils.hexFromBuffer(data, 0, 4); 48 | } 49 | } 50 | 51 | /** 52 | * Detects if passed 'data' is class file 53 | * @param {String|Buffer} data URL string or data buffer 54 | * @return {Boolean} 55 | */ 56 | static isClassFile(data){ 57 | if (typeof data === 'string') { 58 | return data.endsWith('.class'); 59 | } else { 60 | return CLASS_MAGIC_NUMBER === CommonUtils.hexFromBuffer(data, 0, 4); 61 | } 62 | } 63 | 64 | /** 65 | * This functions parse Java source file and detects its name and package 66 | * @param {String} source Java source 67 | * @return {Object} Object with fields: package and class 68 | */ 69 | static detectClassAndPackageNames(source) { 70 | let className = null, packageName = null; 71 | 72 | let parsedSource; 73 | try { 74 | parsedSource = JavaParser.parse(source); 75 | } catch (e) { 76 | return null; 77 | } 78 | 79 | if (parsedSource.node === 'CompilationUnit') { 80 | for (var i = 0; i < parsedSource.types.length; i++) { 81 | if (CommonUtils.isPublic(parsedSource.types[i])) { 82 | className = parsedSource.types[i].name.identifier; 83 | break; 84 | } 85 | } 86 | if (parsedSource.package) { 87 | packageName = CommonUtils.getPackageName(parsedSource.package.name); 88 | } 89 | } 90 | 91 | return { 92 | package: packageName, 93 | class: className 94 | } 95 | } 96 | 97 | static isPublic(node) { 98 | if (node.modifiers) { 99 | for (var i = 0; i < node.modifiers.length; i++) { 100 | if (node.modifiers[i].keyword === 'public') { 101 | return true; 102 | } 103 | } 104 | } 105 | return false; 106 | } 107 | 108 | static getPackageName(node) { 109 | if (node.node === 'QualifiedName') { 110 | return CommonUtils.getPackageName(node.qualifier) + '.' + node.name.identifier; 111 | } else { 112 | return node.identifier; 113 | } 114 | } 115 | 116 | // Utility function to create a deferred promise 117 | static deferred() { 118 | this.promise = new Promise(function(resolve, reject) { 119 | this.resolve = resolve; 120 | this.reject = reject; 121 | }.bind(this)); 122 | Object.freeze(this); 123 | return this; 124 | } 125 | 126 | } 127 | 128 | export default CommonUtils; 129 | -------------------------------------------------------------------------------- /src/core/JavaClassWrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import Wrapper from "./Wrapper"; 3 | import WrapperUtil from "./WrapperUtil"; 4 | 5 | class JavaClassWrapper extends Wrapper { 6 | 7 | static runProxyMethod(javapoly, methodObject, argumentsList) { 8 | return new Promise( 9 | (resolve, reject) => { 10 | JavaClassWrapper.getClassWrapperByName(javapoly, methodObject._parent._identifier).then(classWrapper => { 11 | classWrapper[methodObject._name](...argumentsList).then(returnValue => resolve(returnValue)); 12 | }); 13 | } 14 | ); 15 | } 16 | 17 | static getClassWrapperByName(javapoly, clsName) { 18 | return new Promise( 19 | (resolve, reject) => { 20 | if (JavaClassWrapper.cache === undefined) 21 | JavaClassWrapper.cache = {}; 22 | if (JavaClassWrapper.cache[javapoly.getId()] === undefined) 23 | JavaClassWrapper.cache[javapoly.getId()] = {}; 24 | const cache = JavaClassWrapper.cache[javapoly.getId()]; 25 | if (cache[clsName] !== undefined) { 26 | resolve(cache[clsName]); 27 | } else { 28 | const data = [clsName]; 29 | WrapperUtil.dispatchOnJVM(javapoly, 'CLASS_LOADING', 0, data, (result) => { 30 | const javaClassWrapper = new JavaClassWrapper(javapoly, result[0], result[1], result[2], clsName); 31 | cache[clsName] = javaClassWrapper; 32 | resolve(javaClassWrapper); 33 | }, reject); 34 | } 35 | } 36 | ); 37 | } 38 | 39 | constructor(javapoly, methods, nonFinalFields, finalFields, clsName) { 40 | super(javapoly.dispatcher); 41 | this.clsName = clsName; 42 | this.javapoly = javapoly; 43 | 44 | const wrapper = this; 45 | function objConstructorFunction() { 46 | return wrapper.runConstructorWithJavaDispatching(Array.prototype.slice.call(arguments)) 47 | } 48 | 49 | // Note: There is some JS magic here. This JS constructor function returns an object which is different than the one 50 | // being constructed (this). The returned object is a function extended with this. The idea is that `new` operator 51 | // can be called on the returned object to mimic Java's `new` operator. 52 | const retFunction = Object.assign(objConstructorFunction, this); 53 | 54 | this.init(retFunction, methods, nonFinalFields, finalFields); 55 | 56 | return retFunction; 57 | } 58 | 59 | runConstructorWithJavaDispatching(argumentsList) { 60 | return new Promise((resolve, reject) => { 61 | const data = [this.clsName, argumentsList]; 62 | WrapperUtil.dispatchOnJVM(this.javapoly, 'CLASS_CONSTRUCTOR_INVOCATION', 0, data, resolve, reject); 63 | }); 64 | } 65 | 66 | runMethodWithJavaDispatching(methodName, argumentsList) { 67 | return new Promise((resolve, reject) => { 68 | const data = [this.clsName, methodName, argumentsList]; 69 | WrapperUtil.dispatchOnJVM(this.javapoly, 'CLASS_METHOD_INVOCATION', 0, data, resolve, reject); 70 | }); 71 | } 72 | 73 | // This is to prevent recursion, because reflectJSValue itself needs to be dispatched on the JVM. 74 | isReflectMethod(methodName) { 75 | return (methodName === "reflectJSValue") && (this.clsName == "com.javapoly.Main"); 76 | } 77 | 78 | getFieldWithJavaDispatching(name) { 79 | return new Promise((resolve, reject) => { 80 | const data = [this.clsName, name]; 81 | WrapperUtil.dispatchOnJVM(this.javapoly, 'CLASS_FIELD_READ', 0, data, resolve, reject); 82 | }); 83 | } 84 | 85 | setFieldWithJavaDispatching(name, value) { 86 | return new Promise((resolve, reject) => { 87 | const data = [this.clsName, name, value]; 88 | WrapperUtil.dispatchOnJVM(this.javapoly, 'CLASS_FIELD_WRITE', 0, data, resolve, reject); 89 | }); 90 | } 91 | 92 | } 93 | 94 | export default JavaClassWrapper; 95 | -------------------------------------------------------------------------------- /src/core/JavaObjectWrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import Wrapper from "./Wrapper"; 3 | import WrapperUtil from "./WrapperUtil"; 4 | 5 | class JavaObjectWrapper extends Wrapper { 6 | constructor(javapoly, javaObj, methods, nonFinalFields, finalFields) { 7 | super(javapoly.dispatcher); 8 | this.javapoly = javapoly; 9 | this._javaObj = javaObj; 10 | this.init(this, methods, nonFinalFields, finalFields); 11 | } 12 | 13 | getFieldWithJavaDispatching(name) { 14 | return new Promise((resolve, reject) => { 15 | const data = [this._javaObj, name]; 16 | WrapperUtil.dispatchOnJVM(this.javapoly, 'OBJ_FIELD_READ', 0, data, resolve, reject); 17 | }); 18 | } 19 | 20 | setFieldWithJavaDispatching(name, value) { 21 | return new Promise((resolve, reject) => { 22 | const data = [this._javaObj, name, value]; 23 | WrapperUtil.dispatchOnJVM(this.javapoly, 'OBJ_FIELD_WRITE', 0, data, resolve, reject); 24 | }); 25 | } 26 | 27 | isReflectMethod(methodName) { 28 | return false; 29 | } 30 | 31 | runMethodWithJavaDispatching(methodName, argumentsList) { 32 | return new Promise((resolve, reject) => { 33 | const data = [this._javaObj, methodName, argumentsList]; 34 | WrapperUtil.dispatchOnJVM(this.javapoly, 'OBJ_METHOD_INVOCATION', 0, data, resolve, reject); 35 | }); 36 | } 37 | 38 | } 39 | 40 | export default JavaObjectWrapper; 41 | -------------------------------------------------------------------------------- /src/core/JavaPoly.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as _ from 'underscore'; 3 | import JavaPolyBase from './JavaPolyBase'; 4 | import BrowserDispatcher from '../dispatcher/BrowserDispatcher.js' 5 | import WorkerCallBackDispatcher from '../dispatcher/WorkerCallBackDispatcher.js' 6 | import WrapperUtil from './WrapperUtil.js'; 7 | import CommonUtils from './CommonUtils.js'; 8 | 9 | const DEFAULT_JAVAPOLY_OPTIONS = { 10 | /** 11 | * When page is loading look for all corresponding MIME-types and create objects for Java automatically 12 | * @type {Boolean} 13 | */ 14 | initOnStart: true, 15 | /** 16 | * Directory name that stores all class-files, jars and/or java-files 17 | * @type {String} 18 | */ 19 | storageDir: '/tmp/data', 20 | /** 21 | * URL where we download the doppio library. 22 | * @type {String} 23 | * 1.'doppio/', download from user owner domain(${your.domain}/doppio), eg. localhost for locally test 24 | * 2. or a public url, eg. https://www.javapoly.com/doppio/ 25 | */ 26 | doppioLibUrl: '/doppio/', 27 | 28 | /** 29 | * URL where we download the BrowserFS library 30 | * @type {String} 31 | */ 32 | browserfsLibUrl: '/browserfs/', 33 | 34 | /** 35 | * Optional: javaPolyBaseUrl 36 | * When defined, this is used as the base URL for loading JavaPoly data such as system classes and native functions. 37 | * If empty, JavaPoly will try to automatically figure it out during initialization. 38 | */ 39 | javaPolyBaseUrl: null, 40 | 41 | /** 42 | * Javapoly worker path. null or a path, eg. build/javapoly_worker.js 43 | * 44 | * @type {String} 45 | * when defined not null, we will try to use the webworkers path to run the core javapoly and jvm. 46 | * if web worker is not supported by browser, we will just load javapoly and jvm in browser main Thread. 47 | */ 48 | worker : null, // 'build/javapoly_worker.js' 49 | 50 | /** 51 | * Enable Java Assertions 52 | * 53 | * @type {boolean} 54 | */ 55 | assertionsEnabled : false 56 | }; 57 | 58 | /** 59 | * Main JavaPoly class that do all underliying job for initialization 60 | * Simple usage: 61 | * 1. Create object: (new JavaPoly()); 62 | * 2. Use JavaPoly API classes such as `J` and `Java`. 63 | * 64 | * (new JavaPoly()); 65 | * JavaPoly.type(....).then(() => { } ); 66 | */ 67 | class JavaPoly extends JavaPolyBase { 68 | constructor(_options) { 69 | const options = _.extend(DEFAULT_JAVAPOLY_OPTIONS, _options); 70 | if (!options.javaPolyBaseUrl) { 71 | options.javaPolyBaseUrl = JavaPoly.getScriptBase(); 72 | } 73 | 74 | super(options); 75 | 76 | // Init objects for user to make possible start to work with JavaPoly instantly 77 | // only bind this api to global.window for the default javapoly instance (the 1th instance, created in main.js). 78 | return this.initApiObjects(JavaPolyBase.idCount === 1 ? global.window : undefined); 79 | } 80 | 81 | beginLoading(resolveDispatcherReady) { 82 | // User worker only if worker option is enabled and browser supports WebWorkers 83 | if (this.options.worker && global.Worker){ 84 | this.loadJavaPolyCoreInWebWorker(resolveDispatcherReady); 85 | }else{ 86 | this.loadJavaPolyCoreInBrowser(resolveDispatcherReady); 87 | } 88 | 89 | // case when async loading javapoly lib and the DOM contented already loaded 90 | if (global.document.readyState !== 'loading'){ 91 | this.processScripts(); 92 | WrapperUtil.dispatchOnJVM(this,'META_START_JVM', 0, null); 93 | } else { 94 | global.document.addEventListener('DOMContentLoaded', e => { 95 | this.processScripts(); 96 | WrapperUtil.dispatchOnJVM(this,'META_START_JVM', 0, null); 97 | }, false); 98 | } 99 | 100 | } 101 | 102 | processScripts() { 103 | _.each(global.document.scripts, script => { 104 | this.processScript(script); 105 | }); 106 | } 107 | 108 | processScript(script) { 109 | if(script.type.toLowerCase() !== 'text/java' && script.type.toLowerCase() !== 'application/java') 110 | return; 111 | 112 | if(script.analyzed) return; 113 | 114 | script.analyzed = true; 115 | 116 | //embedded source code 117 | if (script.text){ 118 | const classInfo = CommonUtils.detectClassAndPackageNames(script.text); 119 | this.createProxyForClass(global.window, classInfo.class, classInfo.package); 120 | return this.compileJavaSource(script.text); 121 | } 122 | 123 | if (!script.src){ 124 | console.warning('please specify the text or src of text/java'); 125 | return; 126 | } 127 | 128 | return WrapperUtil.dispatchOnJVM(this, 'FS_MOUNT_JAVA', 10, {src:script.src}); 129 | } 130 | 131 | loadJavaPolyCoreInBrowser(resolveDispatcherReady) { 132 | this.dispatcher = new BrowserDispatcher(this); 133 | resolveDispatcherReady(this.dispatcher); 134 | } 135 | 136 | loadJavaPolyCoreInWebWorker(resolveDispatcherReady) { 137 | this.dispatcher = new WorkerCallBackDispatcher(this.options, new global.Worker(this.options.worker)); 138 | 139 | resolveDispatcherReady(this.dispatcher); 140 | 141 | } 142 | 143 | /* This should be called outside of Promise, or any such async call */ 144 | static getScriptBase() { 145 | var scriptSrc = JavaPoly.getScriptSrc(); 146 | return scriptSrc.slice(0, scriptSrc.lastIndexOf("/") + 1); 147 | } 148 | 149 | static getScriptSrc() { 150 | if (document.currentScript) { 151 | return document.currentScript.src; 152 | } else { 153 | var scripts = document.getElementsByTagName('script'), 154 | script = scripts[scripts.length - 1]; 155 | 156 | if (script.getAttribute.length !== undefined) { 157 | return script.src 158 | } 159 | 160 | return script.getAttribute('src', -1) 161 | } 162 | } 163 | 164 | } 165 | 166 | global.window.JavaPoly = JavaPoly; 167 | 168 | export default JavaPoly; 169 | -------------------------------------------------------------------------------- /src/core/JavaPolyBase.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as _ from 'underscore'; 3 | import JavaClassWrapper from './JavaClassWrapper'; 4 | import JavaObjectWrapper from './JavaObjectWrapper'; 5 | import ProxyWrapper from './ProxyWrapper'; 6 | import WrapperUtil from './WrapperUtil.js'; 7 | import CommonUtils from './CommonUtils.js'; 8 | 9 | /** 10 | * Base JavaPoly class that contains common functionality. 11 | */ 12 | export default class JavaPolyBase { 13 | constructor(_options) { 14 | /** 15 | * Object with options of JavaPoly 16 | * @type {Object} 17 | */ 18 | this.options = _options; 19 | 20 | /** 21 | * The dispatcher for handle jvm command message 22 | * @Type {Object} 23 | */ 24 | this.dispatcher = null; 25 | 26 | const dispatcherDeferred = new CommonUtils.deferred(); 27 | this.dispatcherReady = dispatcherDeferred.promise; 28 | this.initJavaPoly(dispatcherDeferred.resolve, dispatcherDeferred.reject); 29 | 30 | const id = (++JavaPolyBase.idCount)+''; 31 | JavaPolyBase.instances[id] = this; 32 | this.getId = () => id; 33 | 34 | } 35 | 36 | static getInstance(javapolyId){ 37 | return JavaPolyBase.instances[javapolyId]; 38 | } 39 | 40 | initJavaPoly(resolve, reject) { 41 | if (this.options.initOnStart === true) { 42 | return this.beginLoading(resolve); 43 | } else { 44 | return reject('not initialised'); 45 | } 46 | } 47 | 48 | compileJavaSource(scriptText, resolve, reject){ 49 | const classInfo = CommonUtils.detectClassAndPackageNames(scriptText); 50 | 51 | const className = classInfo.class; 52 | const packageName = classInfo.package; 53 | 54 | WrapperUtil.dispatchOnJVM( 55 | this, "FILE_COMPILE", 10, 56 | [className, packageName ? packageName : "", this.options.storageDir, scriptText], resolve, reject 57 | ) 58 | } 59 | 60 | /** 61 | * init the api objects of JavaPoly. 62 | * @param globalObject 63 | * if specified, we want access this java poly instance by a global object, such as global.window. 64 | */ 65 | initApiObjects(globalObject) { 66 | let api = {}; 67 | api.id = this.getId(); 68 | api.options = this.options; 69 | 70 | // Initialize proxies for the most common/built-in packages. 71 | // This will make built-in jvm packages available, since the built-ins won't have their source code in the script tags (and thus wouldn't be analyzed at parse time). 72 | // Most importantly, it will setup warnings when Proxy is not defined in legacy browsers (warn upon access of one of these super common packages) 73 | if(globalObject) { 74 | this.createProxyForClass(globalObject, null, 'com'); 75 | this.createProxyForClass(globalObject, null, 'org'); 76 | this.createProxyForClass(globalObject, null, 'net'); 77 | this.createProxyForClass(globalObject, null, 'sun'); 78 | this.createProxyForClass(globalObject, null, 'java'); 79 | this.createProxyForClass(globalObject, null, 'javax'); 80 | } 81 | 82 | if (typeof Proxy !== 'undefined') { 83 | api.J = ProxyWrapper.createRootEntity(this, null); 84 | if(globalObject) globalObject.J = api.J; 85 | } else { 86 | this.defineProxyWarning(api, 'J', 'accessor'); 87 | if(globalObject) this.defineProxyWarning(globalObject, 'J', 'accessor'); 88 | } 89 | 90 | this.processScripts(); 91 | /* 92 | TODO: use the reflect command 93 | reflect: (jsObj) => { 94 | return javaType("com.javapoly.Main").then((Main) => { 95 | return Main.reflectJSValue(jsObj); 96 | }); 97 | } 98 | };*/ 99 | 100 | api.addClass = (data) => this.addClass(data); 101 | 102 | if (globalObject) { 103 | globalObject.addClass = api.addClass; 104 | } 105 | 106 | return api; 107 | } 108 | 109 | static initialJavaPoly(JavaPolyProto) { 110 | return (JavaPolyBase.idCount === 0 ? new JavaPolyProto() : JavaPolyBase.instances['1']); 111 | } 112 | 113 | static new(name, ...args) { 114 | return JavaPolyBase.initialJavaPoly(this).type(name).then((classWrapper) => new classWrapper(...args)); 115 | } 116 | 117 | static addClass(data) { 118 | return JavaPolyBase.initialJavaPoly(this).addClass(data); 119 | } 120 | 121 | static type(clsName) { 122 | return JavaPolyBase.initialJavaPoly(this).type(clsName); 123 | } 124 | 125 | type(clsName) { 126 | return JavaClassWrapper.getClassWrapperByName(this, clsName); 127 | } 128 | 129 | // data could be text string of java source or the url of remote java class/jar/source 130 | addClass(data){ 131 | return new Promise((resolve, reject) => { 132 | // try to parse it as java souce string 133 | const classInfo = CommonUtils.detectClassAndPackageNames(data) ; 134 | // parse success, embedded java source code 135 | if (classInfo && classInfo.class ){ 136 | return this.compileJavaSource(data, resolve, reject); 137 | } 138 | 139 | // try add remote java/class/jar file 140 | return WrapperUtil.dispatchOnJVM(this, 'FS_DYNAMIC_MOUNT_JAVA', 10, {src:data}, resolve, reject); 141 | 142 | }); 143 | } 144 | 145 | createProxyForClass(obj, classname, packagename) { 146 | let name = null; 147 | let type = null; 148 | if (packagename != null) { 149 | name = packagename.split('.')[0]; 150 | type = 'package'; 151 | } else { 152 | name = classname; 153 | type = 'class'; 154 | } 155 | 156 | if (typeof Proxy !== 'undefined') { 157 | obj[name] = ProxyWrapper.createRootEntity(this, name); 158 | } 159 | else { 160 | this.defineProxyWarning(obj, name, type); 161 | } 162 | } 163 | 164 | defineProxyWarning(obj, name, type) { 165 | var self = this; 166 | Object.defineProperty(obj, name, {configurable: true, get: function(){ if(!self.proxyWarnings) self.proxyWarnings = {}; if(!self.proxyWarnings[name]) console.error('Your browser does not support Proxy objects, so the `'+name+'` '+type+' must be accessed using JavaPoly.type(\''+(type === 'class' ? 'YourClass' : 'com.yourpackage.YourClass')+'\') instead of using the class\' fully qualified name directly from javascript. Note that `JavaPoly.type` will return a promise for a class instead of a direct class reference. For more info: https://javapoly.com/details.html#Java_Classes_using_JavaPoly.type()'); self.proxyWarnings[name] = true;}}); 167 | } 168 | 169 | wrapJavaObject(obj, methods, nonFinalFields, finalFields) { 170 | return new JavaObjectWrapper(this, obj, methods, nonFinalFields, finalFields); 171 | } 172 | 173 | unwrapJavaObject(obj) { 174 | // TODO: is a better check possible using prototypes 175 | if (obj && obj._javaObj) { 176 | return obj._javaObj; 177 | } else { 178 | return null; 179 | } 180 | } 181 | 182 | } 183 | 184 | JavaPolyBase.idCount = 0; 185 | JavaPolyBase.instances = {}; 186 | -------------------------------------------------------------------------------- /src/core/JavaPolyNodeDoppio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as _ from 'underscore'; 3 | import JavaPolyBase from './JavaPolyBase'; 4 | import ProxyWrapper from './ProxyWrapper'; 5 | import NodeDoppioDispatcher from '../dispatcher/NodeDoppioDispatcher.js' 6 | import WrapperUtil from './WrapperUtil.js'; 7 | import CommonUtils from './CommonUtils.js'; 8 | import NodeManager from './NodeManager'; 9 | 10 | const DEFAULT_JAVAPOLY_NODE_DOPPIO_OPTIONS = { 11 | doppioBase: '', 12 | javapolyBase: '', 13 | 14 | /** 15 | * When page is loading look for all corresponding MIME-types and create objects for Java automatically 16 | * @type {Boolean} 17 | */ 18 | initOnStart: true, 19 | 20 | /** 21 | * Directory name that stores all class-files, jars and/or java-files 22 | * @type {String} 23 | */ 24 | storageDir: NodeManager.getTempDirectory(), 25 | 26 | /** 27 | * Enable Java Assertions 28 | * 29 | * @type {boolean} 30 | */ 31 | assertionsEnabled : false 32 | } 33 | 34 | /** 35 | * Main JavaPoly class that do all underliying job for initialization 36 | * Simple usage: 37 | * 1. Create object: (new JavaPolyStandalone()); 38 | * 2. Use JavaPoly API classes such as `J` and `Java`. 39 | * 40 | * (new JavaPoly()); 41 | * JavaPoly.type(....).then(() => { } ); 42 | */ 43 | export default class JavaPoly extends JavaPolyBase { 44 | constructor(_options) { 45 | const options = _.extend(DEFAULT_JAVAPOLY_NODE_DOPPIO_OPTIONS, _options); 46 | super(options); 47 | 48 | // Init objects for user to make possible start to work with JavaPoly instantly 49 | // only bind this api to global.window for the default javapoly instance (the 1th instance, created in main.js). 50 | return this.initApiObjects(JavaPolyBase.idCount === 1 ? global : undefined); 51 | } 52 | 53 | beginLoading(resolveDispatcherReady) { 54 | this.dispatcher = new NodeDoppioDispatcher(this); 55 | resolveDispatcherReady(this.dispatcher); 56 | 57 | WrapperUtil.dispatchOnJVM(this,'META_START_JVM', 0, null); 58 | } 59 | 60 | processScripts() { 61 | // NOP 62 | } 63 | } 64 | 65 | -------------------------------------------------------------------------------- /src/core/JavaPolyNodeSystem.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import * as _ from 'underscore'; 3 | import JavaPolyBase from './JavaPolyBase'; 4 | import ProxyWrapper from './ProxyWrapper'; 5 | import NodeSystemDispatcher from '../dispatcher/NodeSystemDispatcher.js' 6 | import WrapperUtil from './WrapperUtil.js'; 7 | import CommonUtils from './CommonUtils.js'; 8 | import NodeManager from './NodeManager'; 9 | 10 | const DEFAULT_JAVAPOLY_NODE_SYSTEM_OPTIONS = { 11 | javapolyBase: '', 12 | 13 | /** 14 | * When page is loading look for all corresponding MIME-types and create objects for Java automatically 15 | * @type {Boolean} 16 | */ 17 | initOnStart: true, 18 | 19 | /** 20 | * Directory name that stores all class-files, jars and/or java-files 21 | * @type {String} 22 | */ 23 | storageDir: NodeManager.getTempDirectory(), 24 | 25 | /** 26 | * Enable Java Assertions 27 | * 28 | * @type {boolean} 29 | */ 30 | assertionsEnabled : false 31 | } 32 | 33 | /** 34 | * Main JavaPoly class that do all underliying job for initialization 35 | * Simple usage: 36 | * 1. Create object: (new JavaPolyStandalone()); 37 | * 2. Use JavaPoly API classes such as `J` and `Java`. 38 | * 39 | * (new JavaPoly()); 40 | * JavaPoly.type(....).then(() => { } ); 41 | */ 42 | export default class JavaPoly extends JavaPolyBase { 43 | constructor(_options) { 44 | const options = _.extend(DEFAULT_JAVAPOLY_NODE_SYSTEM_OPTIONS, _options); 45 | super(options); 46 | 47 | // Init objects for user to make possible start to work with JavaPoly instantly 48 | // only bind this api to global.window for the default javapoly instance (the 1th instance, created in main.js). 49 | return this.initApiObjects(JavaPolyBase.idCount === 1 ? global : undefined); 50 | } 51 | 52 | beginLoading(resolveDispatcherReady) { 53 | this.dispatcher = new NodeSystemDispatcher(this); 54 | resolveDispatcherReady(this.dispatcher); 55 | 56 | WrapperUtil.dispatchOnJVM(this,'META_START_JVM', 0, null); 57 | } 58 | 59 | processScripts() { 60 | // NOP 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/core/NodeManager.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by titan on 12.05.16. 3 | */ 4 | "use strict"; 5 | let tempDirectory = (function () { 6 | let path = require('path'); 7 | let os = require('os'); 8 | let directoryPath = path.join(os.tmpdir(), 'javapoly-' + process.pid.toString()); 9 | let fs = require('fs'); 10 | fs.mkdirSync(directoryPath); 11 | console.log('Temp directory', directoryPath, 'was created.'); 12 | return directoryPath; 13 | })(); 14 | 15 | let tempAlreadyDeleted = false; 16 | 17 | let deletingTempDirectory = function () { 18 | if (tempAlreadyDeleted) { 19 | return; 20 | } 21 | try { 22 | let path = require('path'); 23 | let fs = require('fs'); 24 | let deleteFolderRecursive = function (pathTo) { 25 | if (fs.existsSync(pathTo)) { 26 | fs.readdirSync(pathTo).forEach(function (file, index) { 27 | var curPath = pathTo + "/" + file; 28 | if (fs.lstatSync(curPath).isDirectory()) { // recurse 29 | deleteFolderRecursive(curPath); 30 | } else { // delete file 31 | fs.unlinkSync(curPath); 32 | } 33 | }); 34 | fs.rmdirSync(pathTo); 35 | } 36 | }; 37 | deleteFolderRecursive(tempDirectory); 38 | console.log('Temp directory', tempDirectory, 'successfully deleted.'); 39 | } catch (error) { 40 | console.error('Error on while deleting temp directory.'); 41 | console.error(error); 42 | } 43 | tempAlreadyDeleted = true; 44 | }; 45 | 46 | process.on('exit', () => { 47 | console.log('exit'); 48 | deletingTempDirectory(); 49 | }); 50 | 51 | process.on('SIGINT', () => { 52 | console.log('SIGINT'); 53 | deletingTempDirectory(); 54 | process.exit(2); 55 | }); 56 | 57 | process.on('uncaughtException', (error) => { 58 | console.error('uncaughtException'); 59 | console.error(error); 60 | deletingTempDirectory(); 61 | }); 62 | 63 | export default class NodeManager { 64 | static getTempDirectory() { 65 | return tempDirectory; 66 | } 67 | } -------------------------------------------------------------------------------- /src/core/ProxyWrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaClassWrapper from './JavaClassWrapper'; 3 | 4 | class ProxyWrapper { 5 | 6 | static createEntity(javapoly, name, parent) { 7 | // We don't now in advance is it a function or just an Object 8 | // But objects cannot be called, so it is a function 9 | const object = function() {}; 10 | object._parent = parent; 11 | object._name = name; 12 | if (parent !== null) { 13 | object._identifier = (parent._identifier === null ? '' : parent._identifier + '.') + name; 14 | } else { 15 | object._identifier = name; 16 | } 17 | object._call = function(thisArg, argumentsList) { 18 | return new Promise( 19 | (resolve, reject) => { 20 | JavaClassWrapper.runProxyMethod(javapoly, object, argumentsList).then(rv => resolve(rv)); 21 | } 22 | ); 23 | }; 24 | 25 | const proxy = new Proxy(object, { 26 | get: (target, property) => { 27 | if (!target.hasOwnProperty(property)) { 28 | target[property] = this.createEntity(javapoly, property, target); 29 | } 30 | return target[property]; 31 | }, 32 | apply: (target, thisArg, argumentsList) => { 33 | return target._call(thisArg, argumentsList); 34 | } 35 | }); 36 | 37 | return proxy; 38 | } 39 | 40 | static createRootEntity(javapoly, name) { 41 | return this.createEntity(javapoly, name, null); 42 | } 43 | } 44 | 45 | export default ProxyWrapper; 46 | -------------------------------------------------------------------------------- /src/core/Wrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaClassWrapper from "./JavaClassWrapper"; 3 | import WrapperUtil from "./WrapperUtil"; 4 | 5 | class Wrapper { 6 | constructor(dispatcher) { 7 | this.dispatcher = dispatcher; 8 | } 9 | 10 | init(obj, methods, nonFinalFields, finalFields) { 11 | const wrapper = this; 12 | 13 | // Add method handlers 14 | for (const name of methods) { 15 | obj[name] = function() { 16 | return wrapper.runMethodWithJSReflection(name, Array.prototype.slice.call(arguments)) 17 | }; 18 | } 19 | 20 | function methodExists(name) { 21 | return methods.findIndex(n => n == name) >= 0; 22 | } 23 | 24 | // Add getters and setters for non-final fields 25 | for (const name of nonFinalFields) { 26 | if (!methodExists(name)) { 27 | Object.defineProperty(obj, name, { 28 | get: () => wrapper.getFieldWithJavaDispatching(name), 29 | set: (newValue) => { wrapper.setFieldWithJavaDispatching(name, newValue) } 30 | }); 31 | } 32 | } 33 | 34 | // Add getters for final fields 35 | for (const name of finalFields) { 36 | if (!methodExists(name)) { 37 | Object.defineProperty(obj, name, { 38 | get: () => wrapper.getFieldWithJavaDispatching(name) 39 | }); 40 | } 41 | } 42 | } 43 | 44 | runMethodWithJSReflection(methodName, args) { 45 | const wrapper = this; 46 | const okToReflect = !wrapper.isReflectMethod(methodName); 47 | 48 | const reflectedArgs = args.map((e) => wrapper.dispatcher.reflect(e)); 49 | 50 | const resultPromise = wrapper.runMethodWithJavaDispatching(methodName, reflectedArgs); 51 | 52 | if (okToReflect) { 53 | return resultPromise.then((result) => wrapper.dispatcher.unreflect(result)); 54 | } else { 55 | return resultPromise; 56 | } 57 | } 58 | } 59 | 60 | export default Wrapper; 61 | -------------------------------------------------------------------------------- /src/core/WrapperUtil.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | class WrapperUtil { 3 | static dispatchOnJVM(javapoly, messageType, priority, data, resolve, reject) { 4 | javapoly.dispatcherReady.then(dispatcher => dispatcher.postMessage(messageType, priority, data, (response) => { 5 | if (response.success) { 6 | if (resolve) 7 | resolve(response.returnValue); 8 | } else { 9 | 10 | /* This function is added here, because it is not possible to serialise functions across web-worker sandbox */ 11 | response.cause.printStackTrace = function() { 12 | for (const se in response.cause.stack) { 13 | console.warn(response.cause.stack[se]); 14 | } 15 | }; 16 | if (reject) 17 | reject(response.cause); 18 | } 19 | })); 20 | } 21 | 22 | } 23 | 24 | export default WrapperUtil; 25 | -------------------------------------------------------------------------------- /src/dispatcher/BrowserDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonDispatcher from './CommonDispatcher.js' 3 | import DoppioManager from '../jvmManager/DoppioManager.js' 4 | import WrapperUtil from "../core/WrapperUtil"; 5 | 6 | /* Used for the case when javaploy is running in Browser */ 7 | class BrowserDispatcher extends CommonDispatcher { 8 | 9 | constructor(javapoly) { 10 | super(javapoly); 11 | this.javapoly = javapoly; 12 | } 13 | 14 | initDoppioManager(javapoly) { 15 | return this.loadExternalJs(javapoly.options.browserfsLibUrl + 'browserfs.min.js').then(()=> { 16 | return this.loadExternalJs(javapoly.options.doppioLibUrl + 'doppio.js').then(() => { 17 | return new DoppioManager(javapoly); 18 | }); 19 | }); 20 | } 21 | 22 | postMessage(messageType, priority, data, callback) { 23 | const id = this.javaPolyIdCount++; 24 | 25 | this.handleIncomingMessage(id, priority, messageType, data, callback); 26 | } 27 | 28 | /** 29 | * load js library file. 30 | * @param fileSrc 31 | * the uri src of the file 32 | * @return Promise 33 | * we could use Promise to wait for js loaded finished. 34 | */ 35 | loadExternalJs(fileSrc){ 36 | return new Promise((resolve, reject) => { 37 | const jsElm = global.document.createElement("script"); 38 | jsElm.type = "text/javascript"; 39 | 40 | if(jsElm.readyState){ 41 | jsElm.onreadystatechange = function(){ 42 | if (jsElm.readyState=="loaded" || jsElm.readyState=="complete"){ 43 | jsElm.onreadysteatechange=null; 44 | resolve(); 45 | } 46 | } 47 | }else{ 48 | jsElm.onload=function(){ 49 | resolve(); 50 | // FIXME reject when timeout 51 | } 52 | } 53 | 54 | jsElm.src = fileSrc; 55 | global.document.getElementsByTagName("head")[0].appendChild(jsElm); 56 | }); 57 | }; 58 | 59 | reflect(jsObj) { 60 | return jsObj; 61 | } 62 | 63 | unreflect(result) { 64 | if ((!!result) && (typeof(result) === "object") && (!!result._javaObj)) { 65 | const className = result._javaObj.getClass().className; 66 | if (className === "Lcom/javapoly/DoppioJSObject;" || className === "Lcom/javapoly/DoppioJSPrimitive;") { 67 | return result._javaObj["com/javapoly/DoppioJSValue/rawValue"]; 68 | } 69 | } 70 | return result; 71 | } 72 | 73 | } 74 | 75 | export default BrowserDispatcher; 76 | -------------------------------------------------------------------------------- /src/dispatcher/CommonDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * The CommonDispatcher is an abstract base class for other dispatchers. 4 | * 5 | * we define the some global Object{javaPolyMessageTypes,javaPolyCallbacks,javaPolyData} 6 | * for sharing args, callback function. 7 | * 8 | */ 9 | 10 | class CommonDispatcher { 11 | 12 | constructor(javapoly) { 13 | // This class is abstract (can't be instantiated directly) 14 | if (this.constructor === CommonDispatcher) { 15 | throw TypeError("new of abstract class CommonDispatcher"); 16 | } 17 | 18 | this.options = javapoly.options; 19 | 20 | this.initDispatcher(javapoly); 21 | } 22 | 23 | initDispatcher(javapoly) { 24 | this.javaPolyEvents = []; 25 | this.javaPolyMessageTypes = {}; 26 | this.javaPolyCallbacks = {}; 27 | this.javaPolyData = {}; 28 | this.javaPolyIdCount = 0; 29 | 30 | this.javaPolyCallback = null; 31 | 32 | this.doppioManager = this.initDoppioManager(javapoly); 33 | } 34 | 35 | handleIncomingMessage(id, priority, messageType, data, callback) { 36 | if (messageType.startsWith("META_")) { 37 | this.handleMetaMessage(id, priority, messageType, data, callback); 38 | } else if (messageType.startsWith("FS_")) { 39 | this.handleFSMessage(id, priority, messageType, data, callback); 40 | } else { 41 | this.handleJVMMessage(id, priority, messageType, data, callback); 42 | } 43 | } 44 | 45 | // JVM messages are added to a queue and dequed from the JVM main thread. 46 | handleJVMMessage(id, priority, messageType, data, callback) { 47 | 48 | this.addMessage(id, priority, messageType, data, callback); 49 | 50 | if (this.javaPolyCallback){ 51 | this.javaPolyCallback(); 52 | } 53 | } 54 | 55 | // FS messages are processed immediately 56 | handleFSMessage(id, priority, messageType, data, callback) { 57 | switch(messageType) { 58 | case "FS_MOUNT_JAVA": 59 | this.doppioManager.then(dm => dm.mountJava(data.src)); 60 | break; 61 | case "FS_DYNAMIC_MOUNT_JAVA": 62 | this.doppioManager.then(dm => dm.dynamicMountJava(data.src).then((msg) => { 63 | if (msg === "OK") { 64 | callback({success:true, returnValue: 'Add Class success'}); 65 | } else { 66 | console.log("Failed to mount java file:", msg, data); 67 | callback({success:false, returnValue: 'Add Class fail'}); 68 | } 69 | }, (msg) => callback({success:false, cause:{stack:[msg]}}))); 70 | break; 71 | default: 72 | console.log("FS TODO", messageType); 73 | break; 74 | } 75 | } 76 | 77 | // Meta messages are processed immediately 78 | handleMetaMessage(id, priority, messageType, data, callback) { 79 | switch(messageType) { 80 | case "META_START_JVM": 81 | this.doppioManager.then(dm => dm.initJVM()); 82 | break; 83 | default: 84 | console.log("META TODO", messageType); 85 | break; 86 | } 87 | } 88 | 89 | /* Add message with higher priority messages ahead of the lower priority ones */ 90 | addMessage(id, priority, messageType, data, callback) { 91 | this.javaPolyMessageTypes[id] = messageType; 92 | this.javaPolyData[id] = data; 93 | this.javaPolyCallbacks[id] = callback; 94 | 95 | const queue = this.javaPolyEvents; 96 | const pos = queue.findIndex(e => e[1] < priority); 97 | const value = [""+id, priority]; 98 | if (pos < 0) { 99 | // insert at end 100 | queue.push(value); 101 | } else { 102 | // insert at position 103 | queue.splice(pos, 0, value); 104 | } 105 | } 106 | 107 | /** 108 | * dequeue a message and get the messageID. Returns undefined when there is no message in the queue. 109 | */ 110 | getMessageId() { 111 | const msg = this.javaPolyEvents.shift(); 112 | if (msg) { 113 | const id = msg[0]; 114 | return id; 115 | } else { 116 | return undefined; 117 | } 118 | } 119 | 120 | getMessageType(msgId){ 121 | // may want to delete the data after fetch 122 | const messageType = this.javaPolyMessageTypes[msgId]; 123 | delete this.javaPolyMessageTypes[msgId]; 124 | return messageType; 125 | } 126 | 127 | getMessageData(msgId){ 128 | // may want to delete the data after fetch 129 | const messageData = this.javaPolyData[msgId]; 130 | delete this.javaPolyData[msgId]; 131 | return messageData; 132 | } 133 | 134 | getMessageCallback(msgId){ 135 | const callback = this.javaPolyCallbacks[msgId]; 136 | delete this.javaPolyCallbacks[msgId]; 137 | return callback; 138 | } 139 | 140 | setJavaPolyCallback(callback){ 141 | this.javaPolyCallback = callback; 142 | } 143 | 144 | callbackMessage(msgId, returnValue){ 145 | const callback = this.javaPolyCallbacks[msgId]; 146 | delete this.javaPolyCallbacks[msgId]; 147 | if (callback) { 148 | callback(returnValue); 149 | } 150 | } 151 | 152 | } 153 | 154 | export default CommonDispatcher; 155 | -------------------------------------------------------------------------------- /src/dispatcher/NodeDoppioDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonDispatcher from './CommonDispatcher.js' 3 | import NodeDoppioManager from '../jvmManager/NodeDoppioManager.js' 4 | 5 | /* Used for the case when javaploy is running in node with doppio */ 6 | export default class NodeDoppioDispatcher extends CommonDispatcher { 7 | 8 | constructor(javapoly) { 9 | super(javapoly); 10 | } 11 | 12 | initDoppioManager(javapoly) { 13 | return Promise.resolve(new NodeDoppioManager(javapoly)); 14 | } 15 | 16 | postMessage(messageType, priority, data, callback) { 17 | const id = this.javaPolyIdCount++; 18 | 19 | this.handleIncomingMessage(id, priority, messageType, data, callback); 20 | } 21 | 22 | reflect(jsObj) { 23 | return jsObj; 24 | } 25 | 26 | unreflect(result) { 27 | if ((!!result) && (typeof(result) === "object") && (!!result._javaObj)) { 28 | const className = result._javaObj.getClass().className; 29 | if (className === "Lcom/javapoly/DoppioJSObject;" || className === "Lcom/javapoly/DoppioJSPrimitive;") { 30 | return result._javaObj["com/javapoly/DoppioJSValue/rawValue"]; 31 | } 32 | } 33 | return result; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/dispatcher/NodeSystemDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonUtils from '../core/CommonUtils.js'; 3 | import CommonDispatcher from './CommonDispatcher.js' 4 | import NodeSystemManager from '../jvmManager/NodeSystemManager.js' 5 | import WrapperUtil from '../core/WrapperUtil.js'; 6 | import Tokens from 'csrf'; 7 | import http from 'http'; 8 | import url from 'url'; 9 | 10 | /* Used for the case when javaploy is running in node with system JVM */ 11 | export default class NodeSystemDispatcher extends CommonDispatcher { 12 | 13 | constructor(javapoly) { 14 | super(javapoly); 15 | this.javapoly = javapoly; 16 | 17 | this.heartBeatPeriodMillis = 1000; 18 | this.reflected = []; 19 | this.reflectedCount = 0; 20 | 21 | const _this = this; 22 | this.count = 0; 23 | this.terminating = false; 24 | const process = require('process'); 25 | process.on('beforeExit', () => { 26 | // console.log("before exit: ", _this.count); 27 | if (!_this.terminating) { 28 | WrapperUtil.dispatchOnJVM(javapoly, 'TERMINATE', 0, [], (willTerminate) => { 29 | _this.count++; 30 | _this.terminating = willTerminate; 31 | if (!willTerminate) { 32 | setTimeout(() => { }, 500); // breathing space to avoid busy polling. TODO: Exponential backoff with ceiling 33 | } else { 34 | WrapperUtil.dispatchOnJVM(javapoly, 'TERMINATE_NOW', 0, [], (willTerminate) => { }); 35 | } 36 | }); 37 | } 38 | }); 39 | 40 | process.on('exit', () => { 41 | // console.log("node process Exit"); 42 | _this.terminating = true; 43 | }); 44 | 45 | process.on('SIGINT', () => { 46 | _this.terminating = true; 47 | WrapperUtil.dispatchOnJVM(javapoly, 'TERMINATE_NOW', 0, [], (willTerminate) => { }); 48 | }); 49 | 50 | process.on('uncaughtException', (e) => { 51 | console.log("Uncaught exception: " + e); 52 | console.log("stack: " + e.stack); 53 | _this.terminating = true; 54 | WrapperUtil.dispatchOnJVM(javapoly, 'TERMINATE_NOW', 0, [], (willTerminate) => { }); 55 | }); 56 | 57 | 58 | const timer = setInterval(() => { 59 | if (!_this.terminating) { 60 | WrapperUtil.dispatchOnJVM(javapoly, 'HEARTBEAT', 0, [], (willTerminate) => { }); 61 | } else { 62 | clearInterval(timer); 63 | } 64 | }, this.heartBeatPeriodMillis); 65 | 66 | timer.unref(); 67 | 68 | } 69 | 70 | initDoppioManager(javapoly) { 71 | this.httpPortDeffered = new CommonUtils.deferred(); 72 | this.tokens = new Tokens(); 73 | this.secret = this.tokens.secretSync() 74 | const mgr = new NodeSystemManager(javapoly, this.secret, this.httpPortDeffered, this.startJSServer()); 75 | return Promise.resolve(mgr); 76 | } 77 | 78 | verifyToken(token) { 79 | return this.tokens.verify(this.secret, token); 80 | } 81 | 82 | postMessage(messageType, priority, data, callback) { 83 | const id = this.javaPolyIdCount++; 84 | 85 | this.handleIncomingMessage(id, priority, messageType, data, callback); 86 | } 87 | 88 | handleRequest(incoming, response) { 89 | const _this = this; 90 | const urlParts = url.parse(incoming.url, true); 91 | if (urlParts.pathname === "/informPort") { 92 | this.httpPortDeffered.resolve(incoming.headers["jvm-port"]); 93 | response.writeHead(200, {'Content-Type': 'text/plain' }); 94 | response.end(); 95 | } else if (urlParts.pathname === "/releaseObject") { 96 | const objId = incoming.headers["obj-id"]; 97 | delete this.reflected[objId]; 98 | response.writeHead(200, {'Content-Type': 'text/plain' }); 99 | response.end(); 100 | } else if (urlParts.pathname === "/getProperty") { 101 | const queryData = urlParts.query; 102 | const jsId = queryData.id; 103 | const fieldName = queryData.fieldName; 104 | const jsObj = this.reflected[jsId]; 105 | const field = jsObj[fieldName]; 106 | response.writeHead(200, {'Content-Type': 'text/plain' }); 107 | response.write(JSON.stringify({result: this.reflect(field)})); 108 | response.end(); 109 | } else if (urlParts.pathname === "/eval") { 110 | this.readStream(incoming, (s) => { 111 | const result = eval(s); 112 | response.writeHead(200, {'Content-Type': 'text/plain' }); 113 | response.write(JSON.stringify({result: _this.reflect(result)})); 114 | response.end(); 115 | }); 116 | } else if (urlParts.pathname === "/invoke") { 117 | this.readStream(incoming, (s) => { 118 | const json = JSON.parse(s); 119 | const func = this.reflected[json.functionId]; 120 | const result = func.apply(null, json.args); 121 | response.writeHead(200, {'Content-Type': 'text/plain' }); 122 | response.write(JSON.stringify({result: _this.reflect(result)})); 123 | response.end(); 124 | }); 125 | } 126 | } 127 | 128 | readStream(rr, cb) { 129 | rr.setEncoding('utf8'); 130 | let data = ""; 131 | rr.on('readable', () => { 132 | const rcvd = rr.read(); 133 | if (rcvd != null) { 134 | data += rcvd; 135 | } 136 | }); 137 | rr.on('end', () => { 138 | cb(data); 139 | }); 140 | } 141 | 142 | startJSServer() { 143 | const _this = this; 144 | 145 | return new Promise((resolve, reject) => { 146 | const srv = http.createServer((incoming, response) => { 147 | if (_this.verifyToken(incoming.headers["token"])) { 148 | _this.handleRequest(incoming, response); 149 | } else { 150 | response.writeHead(404, {'Content-Type': 'text/plain' }); 151 | response.end(); 152 | } 153 | srv.unref(); 154 | }); 155 | srv.listen(0, 'localhost', () => { 156 | resolve(srv.address().port); 157 | }); 158 | }); 159 | } 160 | 161 | handleJVMMessage(id, priority, messageType, data, callback) { 162 | const _this = this; 163 | const thisDispatcher = this; 164 | const token = this.tokens.create(this.secret); 165 | this.javaPolyCallbacks[id] = callback; 166 | this.httpPortDeffered.promise.then((port) => { 167 | const msgObj = {id: ""+id, priority: priority, messageType: messageType, data: data, token: token}; 168 | const msg = JSON.stringify(msgObj); 169 | const requestOptions = { 170 | port: port, 171 | hostname: "localhost", 172 | path:"/message", 173 | headers: {"Content-Length": msg.length}, 174 | agent: false 175 | }; 176 | const req = http.request(requestOptions, (res) => { 177 | res.setEncoding('utf8'); 178 | res.on('data', (chunk) => { 179 | const msg = JSON.parse(chunk); 180 | if (thisDispatcher.verifyToken(msg.token)) { 181 | thisDispatcher.callbackMessage(msg.id, msg.result); 182 | } else { 183 | console.log("[Node] Invalid CSRF token in message's response from jvm, ignoring message"); 184 | console.log("[Node] Rcvd token: ", msg.token); 185 | } 186 | }); 187 | }); 188 | req.on('error', () => { 189 | if (_this.terminating) { 190 | // Expected 191 | } else { 192 | throw new Error("Unexpected error in request"); 193 | } 194 | }); 195 | req.write(msg); 196 | req.end(); 197 | }); 198 | } 199 | 200 | reflect(jsObj) { 201 | const objType = typeof jsObj; 202 | if ((objType === 'function') || ((objType === 'object')) && (!jsObj._javaObj)) { 203 | const id = this.reflectedCount++; 204 | this.reflected[id] = jsObj; 205 | return {"jsId": id, "type": objType}; 206 | } else { 207 | return jsObj; 208 | } 209 | } 210 | 211 | unreflect(result) { 212 | if ((!!result) && (typeof(result) === "object") && (!!result.jsObj)) { 213 | return this.reflected[result.jsObj]; 214 | } 215 | return result; 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /src/dispatcher/WorkerCallBackDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /** 3 | * The WorkerCallBackDispatcher is executed in Browser side. 4 | * This should be init in Browser main Thread. 5 | * 6 | * it's used to send message/java-command(METHOD_INVOKATION,CLASS_LOADING...) 7 | * to Javapoly workers(JVM also included in the web workers), will also listen the return message (include the return value) from javapoly 8 | * workers. 9 | * 10 | */ 11 | class WorkerCallBackDispatcher { 12 | 13 | constructor(options, worker){ 14 | this.options = options; 15 | this.worker = worker; 16 | 17 | this.javaPolyIdCount = 0; 18 | //used to record callback for every message. 19 | this.javaPolyCallbacks = {}; 20 | 21 | this.installListener(); 22 | } 23 | 24 | //listen at browser side, to recv return value and callback 25 | installListener(){ 26 | this.worker.addEventListener('message', e => { 27 | const data = e.data.javapoly; 28 | 29 | const cb = this.javaPolyCallbacks[data.messageId]; 30 | delete this.javaPolyCallbacks[data.messageId]; 31 | 32 | // 1. JVM Init response 33 | // 2. JVM command(METHOD_INVOKATION/CLASS_LOADING/...) response 34 | if (cb) { 35 | cb(data.returnValue); 36 | } 37 | 38 | 39 | }, false); 40 | 41 | // send init request to webworker 42 | // we need to send options also. 43 | this.postMessage('WORKER_INIT', 0, {options:this.options}, (success) =>{ 44 | if (success == true){ 45 | console.log('Worker init success'); 46 | } else { 47 | console.log('Worker init failed in webWorkers'); 48 | // try to load in main thread directly when JVM init failed in WebWorkers ? 49 | 50 | // TODO: This won't be as simple as calling this function as messages would have been already entered into 51 | // dispatcher. We need to delay calling resovleDispatcherReady until the JVM success result comes back. 52 | // this.loadJavaPolyCoreInBrowser(javaMimeScripts, resolveDispatcherReady); 53 | } 54 | }); 55 | } 56 | 57 | /** 58 | * For Web Worker, we also need to pass command args to other side. 59 | * because Browser main thread and Web worker are in different Context. 60 | * (for non-workers mode, we can easily share args, callback function in Global object). 61 | * 62 | * We also need to record callback function for every message. 63 | */ 64 | postMessage(messageType, priority, data, callback) { 65 | 66 | const id = this.javaPolyIdCount++; 67 | this.javaPolyCallbacks[id] = callback; 68 | 69 | this.worker.postMessage({javapoly:{messageId:""+id, messageType:messageType, priority: priority, data:data}}); 70 | } 71 | 72 | reflect(jsObj) { 73 | return jsObj; 74 | } 75 | 76 | unreflect(result) { 77 | return result; 78 | } 79 | } 80 | 81 | export default WorkerCallBackDispatcher; 82 | -------------------------------------------------------------------------------- /src/dispatcher/WorkerDispatcher.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonDispatcher from './CommonDispatcher.js' 3 | import DoppioManager from '../jvmManager/DoppioManager.js' 4 | 5 | /** 6 | * The WorkerDispatcher is executed in web workers side. 7 | * it is used to handle message/java-command from Browser side. 8 | * 9 | * It recv message from browser side and pass the message to JVM, and then return the returnValue to browser side. 10 | * The message dispatch from worker to JVM using the way javapoly do (extends from CommonDispatcher). 11 | * 12 | */ 13 | class WorkerDispatcher extends CommonDispatcher{ 14 | 15 | constructor(javapoly){ 16 | super(javapoly); 17 | this.idCount = 0; 18 | } 19 | 20 | initDoppioManager(javapoly) { 21 | const options = javapoly.options; 22 | importScripts(options.browserfsLibUrl + 'browserfs.min.js'); 23 | importScripts(options.doppioLibUrl + 'doppio.js'); 24 | 25 | return Promise.resolve(new DoppioManager(javapoly)); 26 | } 27 | 28 | // Called by the worker when loading scripts 29 | postMessage(messageType, priority, data, callback) { 30 | const id = this.idCount++; 31 | this.handleIncomingMessage("localMessage" + id, priority, messageType, data, callback); 32 | } 33 | 34 | // Handle message data coming from the web-worker message bridge 35 | handleWorkerMessage(data, callback) { 36 | const id = data.messageId; 37 | 38 | if (!callback) { 39 | callback = (returnValue) => { 40 | global.self.postMessage({ 41 | javapoly:{ 42 | messageId: id, messageType:data.messageType, returnValue:returnValue 43 | }}); 44 | }; 45 | } 46 | 47 | this.handleIncomingMessage(id, data.priority, data.messageType, data.data, callback); 48 | } 49 | 50 | }; 51 | 52 | export default WorkerDispatcher; 53 | -------------------------------------------------------------------------------- /src/jars/java_websocket.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdstroy/JavaPoly/ceb75ecefc9372db4c2d6d99c7700881f5d25a0b/src/jars/java_websocket.jar -------------------------------------------------------------------------------- /src/jars/javax.json-1.0.4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdstroy/JavaPoly/ceb75ecefc9372db4c2d6d99c7700881f5d25a0b/src/jars/javax.json-1.0.4.jar -------------------------------------------------------------------------------- /src/jvmManager/DoppioManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonUtils from '../core/CommonUtils.js'; 3 | import WrapperUtil from '../core/WrapperUtil.js' 4 | 5 | const classfile = require('./../tools/classfile.js'); 6 | 7 | /** 8 | * The DoppioManager manages the JVM and filesystem. 9 | * It can be run in Browser or WebWorker. 10 | * It assumes that the .js for BrowserFS and Doppio is already loaded. 11 | */ 12 | class DoppioManager { 13 | constructor(javapoly) { 14 | this.javapoly = javapoly; 15 | 16 | this.fs = null; 17 | 18 | /** 19 | * It's a MountableFileSystem that contains XHR mounted file systems. 20 | * It's needed for loading JAR-files without loading and resaving it. 21 | */ 22 | this.xhrdirs = new BrowserFS.FileSystem.MountableFileSystem(); 23 | 24 | /** 25 | * Stores referense to the special extension for fs (for example it contains recursive mkdir) 26 | * @type {[type]} 27 | */ 28 | this.fsext = null; 29 | 30 | /** 31 | * Array that contains classpath, include the root path of class files , jar file path. 32 | * @type {Array} 33 | */ 34 | this.classpath = [this.getOptions().storageDir]; 35 | 36 | this.mountHub = []; 37 | 38 | this.initBrowserFS(); 39 | } 40 | 41 | getOptions() { 42 | return this.javapoly.options; 43 | } 44 | 45 | /** 46 | * Initialization of BrowserFS 47 | */ 48 | initBrowserFS(){ 49 | const mfs = new BrowserFS.FileSystem.MountableFileSystem(); 50 | BrowserFS.initialize(mfs); 51 | mfs.mount('/tmp', new BrowserFS.FileSystem.InMemory()); 52 | 53 | // FIXME local storage can't be used in WebWorker, check if it affect anything. 54 | if (!this.javapoly.isJavaPolyWorker) { 55 | mfs.mount('/home', new BrowserFS.FileSystem.LocalStorage()); 56 | } 57 | 58 | const options = this.getOptions(); 59 | 60 | this.bfsReady = new Promise((resolve) => { 61 | CommonUtils.xhrRetrieve(options.doppioLibUrl + "/listings.json", "json").then(doppioListings => { 62 | CommonUtils.xhrRetrieve(options.javaPolyBaseUrl + "/listings.json", "json").then(javapolyListings => { 63 | mfs.mount('/sys', new BrowserFS.FileSystem.XmlHttpRequest(doppioListings, options.doppioLibUrl)); 64 | mfs.mount('/javapoly', new BrowserFS.FileSystem.XmlHttpRequest(javapolyListings, options.javaPolyBaseUrl)); 65 | mfs.mount('/xhrdirs', this.xhrdirs); 66 | 67 | this.fs = BrowserFS.BFSRequire('fs'); 68 | this.path = BrowserFS.BFSRequire('path'); 69 | this.fsext = require('./../tools/fsext')(this.fs, this.path); 70 | this.fsext.rmkdirSync(options.storageDir); 71 | BrowserFS.install(this); 72 | this.installStreamHandlers(); 73 | resolve(); 74 | }); 75 | }); 76 | }); 77 | 78 | } 79 | 80 | _mountJava(src, fRegJarPath) { 81 | const Buffer = global.BrowserFS.BFSRequire('buffer').Buffer; 82 | const options = this.getOptions(); 83 | 84 | return new Promise((resolve, reject) => { 85 | if (CommonUtils.isZipFile(src)) { 86 | return this.mountFileViaXHR(src).then( 87 | fRegJarPath.bind(this, resolve, reject), 88 | reject ); 89 | } else { 90 | // remote file, we need to download the file data of that url and parse the type. 91 | CommonUtils.xhrRetrieve(src, 'arraybuffer').then(fileData => { 92 | const fileDataBuf = new Buffer(fileData); 93 | 94 | // remote java class/jar file 95 | if (CommonUtils.isClassFile(fileDataBuf)){ 96 | //return WrapperUtil.dispatchOnJVM(this, 'FS_MOUNT_CLASS', 10, {src:script.src}); 97 | return this.writeRemoteClassFileIntoFS(src, fileDataBuf).then(resolve, reject); 98 | } else if (CommonUtils.isZipFile(fileDataBuf)) { 99 | //return WrapperUtil.dispatchOnJVM(this, 'FS_MOUNT_JAR', 10, {src:script.src}); 100 | return this.writeRemoteJarFileIntoFS(src, fileDataBuf).then( 101 | fRegJarPath.bind(this, resolve, reject), 102 | reject ); 103 | } 104 | 105 | // remote java source code file 106 | const classInfo = CommonUtils.detectClassAndPackageNames(fileDataBuf.toString()); 107 | if (classInfo && classInfo.class) { 108 | const className = classInfo.class; 109 | const packageName = classInfo.package; 110 | return WrapperUtil.dispatchOnJVM( 111 | this.javapoly, 'FILE_COMPILE', 10, 112 | [className, packageName ? packageName : '', options.storageDir, fileDataBuf.toString()], resolve, reject 113 | ); 114 | } 115 | 116 | console.log('Unknown java file type', src); 117 | reject('Unknown java file type'+src); 118 | }, () => { 119 | console.log('URL Not Found', src); 120 | reject('Unknown java file type'+src); 121 | }); 122 | } 123 | }); 124 | } 125 | 126 | mountJava(src) { 127 | this.bfsReady.then(() => { 128 | this.mountHub.push( 129 | this._mountJava(src, 130 | (resolve, reject, jarStorePath) => {this.classpath.push(jarStorePath); resolve("OK");} 131 | ) 132 | ) 133 | }); 134 | } 135 | 136 | dynamicMountJava(src) { 137 | const Buffer = global.BrowserFS.BFSRequire('buffer').Buffer; 138 | const options = this.getOptions(); 139 | return this._mountJava(src, 140 | (resolve, reject, jarStorePath) => WrapperUtil.dispatchOnJVM(this.javapoly, 'JAR_PATH_ADD', 10, ['file://' + jarStorePath], resolve, reject) 141 | ); 142 | } 143 | 144 | mountFileViaXHR(src) { 145 | const options = this.getOptions(); 146 | 147 | return new Promise((resolve, reject) => { 148 | const fileName = this.path.basename(src); 149 | const dirName = this.path.join(src.replace(/[\///\:]/gi, '')); 150 | 151 | if (!this.fs.existsSync('/xhrdirs/' + dirName)) { 152 | let listingObject = {}; listingObject[fileName] = null; 153 | let lastSlash = 0; 154 | for(let ti = 0; (ti = src.indexOf('/', lastSlash + 1)) > 0; lastSlash = ti) ; 155 | const mountPoint = new BrowserFS.FileSystem.XmlHttpRequest(listingObject, src.substr(0,lastSlash)); 156 | 157 | this.xhrdirs.mount('/' + dirName, mountPoint); 158 | } 159 | 160 | resolve(this.path.join('/xhrdirs', dirName, fileName)); 161 | }); 162 | } 163 | 164 | writeRemoteJarFileIntoFS(src,jarFileData) { 165 | const Buffer = global.BrowserFS.BFSRequire('buffer').Buffer; 166 | const options = this.getOptions(); 167 | return new Promise((resolve, reject) => { 168 | const jarName = this.path.basename(src); 169 | const jarStorePath = this.path.join(options.storageDir, jarName); 170 | // store the .jar file to $storageDir 171 | this.fs.writeFile(jarStorePath, jarFileData, (err) => { 172 | if (err) { 173 | console.error(err.message); 174 | reject(err.message); 175 | } else { 176 | // add .jar file path to the URL of URLClassLoader 177 | //this.classpath.push(jarStorePath); 178 | 179 | //need to pass the path, will add that path to ClassLoader of Main.java 180 | resolve(jarStorePath); 181 | } 182 | }); 183 | }); 184 | } 185 | 186 | writeRemoteClassFileIntoFS(src, classFileData){ 187 | const Buffer = global.BrowserFS.BFSRequire('buffer').Buffer; 188 | const options = this.getOptions(); 189 | return new Promise((resolve, reject) => { 190 | const classFileInfo = classfile.analyze(classFileData); 191 | const className = this.path.basename(classFileInfo.this_class); 192 | const packageName = this.path.dirname(classFileInfo.this_class); 193 | 194 | this.fsext.rmkdirSync(this.path.join(options.storageDir, packageName)); 195 | 196 | this.fs.writeFile(this.path.join(options.storageDir, classFileInfo.this_class + '.class'), 197 | classFileData, (err) => { 198 | if (err) { 199 | console.error(err.message); 200 | reject(err.message); 201 | } else { 202 | resolve("OK"); 203 | } 204 | } 205 | ); 206 | }); 207 | } 208 | 209 | initJVM() { 210 | const options = this.getOptions(); 211 | const responsiveness = this.javapoly.isJavaPolyWorker ? 100 : 10; 212 | this.classpath.unshift("/javapoly/classes/"); 213 | this.bfsReady.then(() => { 214 | Promise.all(this.mountHub).then(() => { 215 | this.javapoly.jvm = new Doppio.VM.JVM({ 216 | doppioHomePath: '/sys', 217 | classpath: this.classpath, 218 | javaHomePath: '/sys/vendor/java_home', 219 | extractionPath: '/tmp', 220 | nativeClasspath: ['/sys/natives', "/javapoly/natives"], 221 | assertionsEnabled: options.assertionsEnabled, 222 | responsiveness: responsiveness 223 | }, (err, jvm) => { 224 | if (err) { 225 | console.log('err loading JVM ' + this.javapoly.getId() + ' :', err); 226 | } else { 227 | jvm.runClass('com.javapoly.Main', [this.javapoly.getId()], function(exitCode) { 228 | // Control flow shouldn't reach here under normal circumstances, 229 | // because Main thread keeps polling for messages. 230 | console.log("JVM Exit code: ", exitCode); 231 | }); 232 | } 233 | }); 234 | }); 235 | }); 236 | } 237 | 238 | installStreamHandlers() { 239 | this.process.stdout.on('data', (data) => { 240 | const ds = data.toString(); 241 | if (ds != "\n") { 242 | console.log("JVM " + this.javapoly.getId() + " stdout>", ds); 243 | } 244 | }); 245 | this.process.stderr.on('data', (data) => { 246 | const ds = data.toString(); 247 | if (ds != "\n") { 248 | console.warn("JVM " + this.javapoly.getId() + " stderr>", ds); 249 | } 250 | }); 251 | } 252 | 253 | } 254 | 255 | export default DoppioManager; 256 | -------------------------------------------------------------------------------- /src/jvmManager/NodeDoppioManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonUtils from '../core/CommonUtils.js'; 3 | import WrapperUtil from '../core/WrapperUtil.js'; 4 | 5 | /** 6 | * The NodeDoppioManager manages the Doppio JVM on Node 7 | */ 8 | export default class NodeDoppioManager { 9 | constructor(javapoly) { 10 | this.javapoly = javapoly; 11 | 12 | /** 13 | * Array that contains classpath, include the root path of class files , jar file path. 14 | * @type {Array} 15 | */ 16 | const options = this.getOptions(); 17 | this.classpath = [options.javapolyBase + "/classes", options.storageDir]; 18 | 19 | } 20 | 21 | getOptions() { 22 | return this.javapoly.options; 23 | } 24 | 25 | dynamicMountJava(src) { 26 | const fs = require("fs"); 27 | const options = this.getOptions(); 28 | return new Promise((resolve, reject) => { 29 | // remote file, we need to download the file data of that url and parse the type. 30 | fs.readFile(src, (err, fileDataBuf) => { 31 | if (err) { 32 | reject(err); 33 | } else { 34 | // remote java class/jar file 35 | if (CommonUtils.isClassFile(fileDataBuf)){ 36 | this.writeRemoteClassFileIntoFS(src, fileDataBuf).then(resolve, reject); 37 | } else if (CommonUtils.isZipFile(fileDataBuf)){ 38 | WrapperUtil.dispatchOnJVM(this.javapoly, 'JAR_PATH_ADD', 10, ['file://'+src], resolve, reject); 39 | } else { 40 | 41 | // remote java source code file 42 | const classInfo = CommonUtils.detectClassAndPackageNames(fileDataBuf.toString()) ; 43 | if (classInfo && classInfo.class ){ 44 | const className = classInfo.class; 45 | const packageName = classInfo.package; 46 | return WrapperUtil.dispatchOnJVM( 47 | this.javapoly, "FILE_COMPILE", 10, 48 | [className, packageName ? packageName : "", options.storageDir, fileDataBuf.toString()], resolve, reject 49 | ); 50 | } 51 | 52 | console.log('Unknown java file type', src); 53 | reject('Unknown java file type'+src); 54 | } 55 | } 56 | }); 57 | }) 58 | } 59 | 60 | writeRemoteClassFileIntoFS(src, classFileData){ 61 | const path = require('path'); 62 | const fs = require('fs'); 63 | const options = this.getOptions(); 64 | const classfile = require('./../tools/classfile.js'); 65 | const fsext = require('./../tools/fsext')(fs, path); 66 | 67 | return new Promise((resolve, reject) => { 68 | const classFileInfo = classfile.analyze(classFileData); 69 | const className = path.basename(classFileInfo.this_class); 70 | const packageName = path.dirname(classFileInfo.this_class); 71 | 72 | fsext.rmkdirSync(path.join(options.storageDir, packageName)); 73 | 74 | fs.writeFile(path.join(options.storageDir, classFileInfo.this_class + '.class'), 75 | classFileData, (err) => { 76 | if (err) { 77 | console.error(err.message); 78 | reject(err.message); 79 | } else { 80 | resolve("OK"); 81 | } 82 | } 83 | ); 84 | }); 85 | } 86 | 87 | initJVM() { 88 | const options = this.getOptions(); 89 | const responsiveness = this.javapoly.isJavaPolyWorker ? 100 : 10; 90 | const javaHomePath = options.doppioBase + '/vendor/java_home'; 91 | 92 | this.javapoly.jvm = new Doppio.VM.JVM({ 93 | classpath: this.classpath, 94 | doppioHomePath: options.doppioBase, 95 | nativeClasspath: [options.doppioBase + '/src/natives', options.javapolyBase + "/natives"], 96 | assertionsEnabled: options.assertionsEnabled, 97 | responsiveness: responsiveness 98 | }, (err, jvm) => { 99 | if (err) { 100 | console.error('err loading JVM ' + this.javapoly.getId() + ' :', err); 101 | } else { 102 | jvm.runClass('com.javapoly.Main', [this.javapoly.getId()], function(exitCode) { 103 | // Control flow shouldn't reach here under normal circumstances, 104 | // because Main thread keeps polling for messages. 105 | console.log("JVM Exit code: ", exitCode); 106 | }); 107 | } 108 | }); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/jvmManager/NodeSystemManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import CommonUtils from '../core/CommonUtils.js'; 3 | import WrapperUtil from '../core/WrapperUtil.js'; 4 | import NodeManager from '../core/NodeManager'; 5 | 6 | /** 7 | * The NodeSystemManager manages the Doppio JVM on Node 8 | */ 9 | export default class NodeSystemManager { 10 | constructor(javapoly, secret, httpPortDeffered, jsServer) { 11 | this.javapoly = javapoly; 12 | this.httpPortDeffered = httpPortDeffered; 13 | this.secret = secret; 14 | this.jsServer = jsServer; 15 | 16 | /** 17 | * Array that contains classpath, include the root path of class files , jar file path. 18 | * @type {Array} 19 | */ 20 | const options = this.getOptions(); 21 | this.classpath = [options.javapolyBase + "/classes", options.storageDir]; 22 | this.javaBin = this.getJavaExec(); 23 | } 24 | 25 | getOptions() { 26 | return this.javapoly.options; 27 | } 28 | 29 | dynamicMountJava(src) { 30 | const fs = require("fs"); 31 | const options = this.getOptions(); 32 | return new Promise((resolve, reject) => { 33 | // remote file, we need to download the file data of that url and parse the type. 34 | fs.readFile(src, (err, fileDataBuf) => { 35 | if (err) { 36 | reject(err); 37 | } else { 38 | // remote java class/jar file 39 | if (CommonUtils.isClassFile(fileDataBuf)){ 40 | this.writeRemoteClassFileIntoFS(src, fileDataBuf).then(resolve, reject); 41 | } else if (CommonUtils.isZipFile(fileDataBuf)){ 42 | WrapperUtil.dispatchOnJVM(this.javapoly, 'JAR_PATH_ADD', 10, ['file://'+src], resolve, reject); 43 | } else { 44 | 45 | // remote java source code file 46 | const classInfo = CommonUtils.detectClassAndPackageNames(fileDataBuf.toString()) ; 47 | if (classInfo && classInfo.class ){ 48 | const className = classInfo.class; 49 | const packageName = classInfo.package; 50 | return WrapperUtil.dispatchOnJVM( 51 | this.javapoly, "FILE_COMPILE", 10, 52 | [className, packageName ? packageName : "", options.storageDir, fileDataBuf.toString()], resolve, reject 53 | ); 54 | } 55 | 56 | console.log('Unknown java file type', src); 57 | reject('Unknown java file type'+src); 58 | } 59 | } 60 | }); 61 | }) 62 | } 63 | 64 | writeRemoteClassFileIntoFS(src, classFileData){ 65 | const path = require('path'); 66 | const fs = require("fs"); 67 | const options = this.getOptions(); 68 | const classfile = require('./../tools/classfile.js'); 69 | const fsext = require('./../tools/fsext')(fs, path); 70 | 71 | return new Promise((resolve, reject) => { 72 | const classFileInfo = classfile.analyze(classFileData); 73 | const className = path.basename(classFileInfo.this_class); 74 | const packageName = path.dirname(classFileInfo.this_class); 75 | 76 | fsext.rmkdirSync(path.join(options.storageDir, packageName)); 77 | 78 | fs.writeFile(path.join(options.storageDir, classFileInfo.this_class + '.class'), 79 | classFileData, (err) => { 80 | if (err) { 81 | console.error(err.message); 82 | reject(err.message); 83 | } else { 84 | resolve("OK"); 85 | } 86 | } 87 | ); 88 | }); 89 | } 90 | 91 | getJavaExec() { 92 | const path = require('path'); 93 | const fs = require("fs"); 94 | 95 | const homeRootDirectory = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME']; 96 | const binDirName = '.jvm'; 97 | const javaExec = (process.platform === 'win32') ? "java.exe" : "java"; 98 | const javaFullPath = path.join(homeRootDirectory, binDirName, 'jre', 'bin', javaExec); 99 | 100 | try { 101 | const stat = fs.statSync(javaFullPath); 102 | if (stat.isFile()) { 103 | return javaFullPath; 104 | } 105 | } catch (e) { 106 | if (e.code === 'ENOENT') { 107 | // Java wasn't installed locally 108 | } else { 109 | // Hm unknown error 110 | console.error(e); 111 | } 112 | } 113 | return "java"; 114 | } 115 | 116 | initJVM() { 117 | var javaBin = this.javaBin; 118 | this.jsServer.then((serverPort) => { 119 | const childProcess = require('child_process'); 120 | const spawn = childProcess.spawn; 121 | 122 | const path = require('path'); 123 | const currentDirectory = __dirname; 124 | const packageRoot = path.resolve(currentDirectory, ".."); 125 | 126 | const classPath = packageRoot + '/jars/java_websocket.jar:' + packageRoot + '/jars/javax.json-1.0.4.jar:' + 127 | packageRoot + '/classes:' + NodeManager.getTempDirectory(); 128 | 129 | const args = ['-cp', classPath, 'com.javapoly.Main', this.javapoly.getId(), "system", this.secret, serverPort]; 130 | // const child = spawn('java', args, {detached: true, stdio: ['ignore', 'ignore', 'ignore']}); 131 | const child = spawn(javaBin, args, {detached: true, stdio: 'inherit'}); 132 | child.unref(); 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaPoly from './core/JavaPoly'; 3 | 4 | // Create main object that will be accessed via global objects J and Java 5 | global.window.javapoly = new JavaPoly({ 6 | doppioLibUrl: 'https://www.javapoly.com/doppio/', 7 | browserfsLibUrl: 'https://www.javapoly.com/browserfs/' 8 | }); 9 | 10 | module.exports = JavaPoly; -------------------------------------------------------------------------------- /src/main.test.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaPoly from './core/JavaPoly'; 3 | 4 | /* Parses URL search string. Eg of valid input: "?worker=true&dispatch=fast" */ 5 | function getParams(searchString) { 6 | function cast(str) { 7 | try { 8 | return JSON.parse(str); 9 | } catch (e) { 10 | return str; 11 | } 12 | } 13 | 14 | const result = {}; 15 | if (searchString.startsWith("?")) { 16 | const fields = searchString.substring(1).split("&"); 17 | for (const f of fields) { 18 | const pair = f.split("="); 19 | if (pair.length == 2) { 20 | result[pair[0]] = cast(pair[1]); 21 | } 22 | } 23 | } 24 | return result; 25 | } 26 | 27 | const params = getParams(window.location.search); 28 | 29 | const config = {assertionsEnabled: true}; 30 | 31 | Object.assign(config, params); 32 | 33 | // Create a default JavaPoly Instance in global.window.defaultJavaPoly. 34 | // The main object that will be accessed via global objects J and Java 35 | global.window.defaultJavapoly = new JavaPoly(config); 36 | -------------------------------------------------------------------------------- /src/natives/DoppioBridge.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var javapoly0; 3 | 4 | function toPrimitiveTypeName(name) { 5 | // TODO: Consider using a map 6 | switch(name) { 7 | case 'Ljava/lang/Integer;': return "I"; 8 | case 'Ljava/lang/Double;': return "D"; 9 | case 'Ljava/lang/Byte;': return "B"; 10 | case 'Ljava/lang/Character;': return "C"; 11 | case 'Ljava/lang/Float;': return "F"; 12 | case 'Ljava/lang/Long;': return "J"; 13 | case 'Ljava/lang/Short;': return "S"; 14 | case 'Ljava/lang/Boolean;': return "Z"; 15 | case 'Ljava/lang/Void;': return "V"; 16 | default: return name; 17 | } 18 | }; 19 | 20 | function wrapObject(thread, obj) { 21 | var objectType = typeof obj; 22 | if (Array.isArray(obj)) { 23 | return wrapArray(thread, obj); 24 | } else if (objectType === 'string') { 25 | return wrapString(thread, obj); 26 | } else if (objectType === 'number') { 27 | return wrapNumber(thread, obj); 28 | } else if (objectType === 'boolean') { 29 | // We need to cast to number because Doppio JVM represents booleans as numbers 30 | const intValue = obj ? 1 : 0; 31 | return util.boxPrimitiveValue(thread, 'Z', intValue); 32 | } else { 33 | var possibleJavaObj = javapoly0.unwrapJavaObject(obj); 34 | return possibleJavaObj == null ? obj : possibleJavaObj; 35 | } 36 | } 37 | 38 | function wrapString(thread, obj) { 39 | return javapoly0.jvm.internString(obj); 40 | } 41 | 42 | function wrapArray(thread, obj) { 43 | var wrappedArr = []; 44 | for (var i = 0; i < obj.length; i++) { 45 | wrappedArr.push(wrapObject(thread, obj[i])); 46 | } 47 | return util.newArrayFromData(thread, thread.getBsCl(), '[Ljava/lang/Object;', wrappedArr); 48 | } 49 | 50 | function wrapNumber(thread, obj) { 51 | return util.boxPrimitiveValue(thread, 'D', obj) 52 | } 53 | 54 | function getPublicFields(obj) { 55 | // Mimicking a set, based on http://stackoverflow.com/a/18890005 56 | var nonFinalNamesSet = Object.create(null); 57 | var finalNamesSet = Object.create(null); 58 | 59 | var fields = obj.constructor.cls.fields; 60 | // console.log("cls", obj.constructor.cls, " fields", fields); 61 | for (var i in fields) { 62 | var field = fields[i]; 63 | if (field.accessFlags.isPublic()) { 64 | if (field.accessFlags.isFinal()) { 65 | finalNamesSet[field.name] = true; 66 | } else { 67 | nonFinalNamesSet[field.name] = true; 68 | } 69 | } 70 | } 71 | return {nonFinal: Object.keys(nonFinalNamesSet), "final": Object.keys(finalNamesSet)}; 72 | } 73 | 74 | function getPublicInstanceMethodsFromClass(cls, set) { 75 | var methods = cls.getMethods(); 76 | for (var i in methods) { 77 | var method = methods[i]; 78 | set[method.name] = true; 79 | } 80 | if (cls.superClass) { 81 | getPublicInstanceMethodsFromClass(cls.superClass, set); 82 | } 83 | } 84 | 85 | function getPublicInstanceMethods(obj) { 86 | // Mimicking a set, based on http://stackoverflow.com/a/18890005 87 | var methodNamesSet = Object.create(null); 88 | getPublicInstanceMethodsFromClass(obj.constructor.cls, methodNamesSet); 89 | return Object.keys(methodNamesSet); 90 | } 91 | 92 | function isValidNumber(gLong) { 93 | var max = 2097151; 94 | var absgLong = gLong.isNegative() ? gLong.negate() : gLong; 95 | return absgLong.getHighBits() < max || (absgLong.getHighBits() === max && absgLong.getLowBits() < 0 ); 96 | } 97 | 98 | /* Converts a Java object to a JS friendly object. Primitive numbers, primitive booleans, strings and arrays are 99 | * converted to their JS counter-parts. Others are wrapped with JavaObjectWrapper */ 100 | function javaObjToJS(thread, obj) { 101 | if (obj === null) 102 | return null; 103 | if (obj['getClass']) { 104 | var cls = obj.getClass(); 105 | if (cls.className === 'Ljava/lang/String;') { 106 | return obj.toString(); 107 | } else if (cls.className === 'Ljava/lang/Boolean;') { 108 | return obj['java/lang/Boolean/value'] == 1; 109 | } else if (cls.className === 'Ljava/lang/Long;') { 110 | var gLong = obj.unbox(); 111 | if (isValidNumber(gLong)) { 112 | return gLong.toNumber(); 113 | } else { 114 | throw new RangeError('Unfortunately, JavaScript does not yet support 64 bit integers'); 115 | } 116 | } else if (cls.className.charAt(0) === '[') { 117 | var nativeArray = []; 118 | for (var i = 0; i < obj.array.length; i++) { 119 | nativeArray.push(javaObjToJS(thread, obj.array[i])); 120 | } 121 | return nativeArray; 122 | } else { 123 | if (obj.unbox) { 124 | return obj.unbox(); 125 | } else { 126 | var fields = getPublicFields(obj); 127 | return javapoly0.wrapJavaObject(obj, getPublicInstanceMethods(obj), fields.nonFinal, fields.final); 128 | } 129 | } 130 | } 131 | } 132 | 133 | function flatThrowableToJS(ft) { 134 | var cause = ft["com/javapoly/FlatThrowable/causedBy"]; 135 | var name = ft["com/javapoly/FlatThrowable/name"]; 136 | var message = ft["com/javapoly/FlatThrowable/message"]; 137 | return { 138 | name: name === null ? null : name.toString(), 139 | message: message === null ? null : message.toString(), 140 | stack: ft["com/javapoly/FlatThrowable/stack"].array.map(e => e.toString()), 141 | causedBy: cause === null ? null : flatThrowableToJS(cause) 142 | } 143 | } 144 | 145 | /* This function is used to wrap and return an object and its type to Java land. 146 | * It returns an array of Object[], where the first element is a string describing the JS type, 147 | * and the second is the obj. 148 | */ 149 | function getRawType(thread, obj) { 150 | return util.newArrayFromData(thread, thread.getBsCl(), '[Ljava/lang/Object;', [util.initString(thread.getBsCl(), typeof obj), obj]) 151 | } 152 | 153 | registerNatives({ 154 | 'com/javapoly/DoppioBridge': { 155 | 156 | 'evalRaw(Ljava/lang/String;)[Ljava/lang/Object;': function(thread, toEval) { 157 | var expr = toEval.toString(); 158 | var res = eval(expr); 159 | return util.newArrayFromData(thread, thread.getBsCl(), '[Ljava/lang/Object;', [util.initString(thread.getBsCl(), typeof res), res]); 160 | }, 161 | 162 | 'dispatchMessage(Ljava/lang/String;)V': function(thread, obj, msgId) { 163 | var callback = javapoly0.dispatcher.getMessageCallback(msgId); 164 | thread.setStatus(6); // ASYNC_WAITING 165 | callback(thread, function() { 166 | thread.asyncReturn(); 167 | }); 168 | }, 169 | 170 | 'returnResult(Ljava/lang/String;Ljava/lang/Object;)V': function(thread, obj, msgId, returnValue) { 171 | try { 172 | javapoly0.dispatcher.callbackMessage(msgId,{success:true, returnValue: javaObjToJS(thread, returnValue)}); 173 | } catch (e) { 174 | javapoly0.dispatcher.callbackMessage(msgId, {success:false, cause: e}); 175 | } 176 | }, 177 | 178 | 'returnErrorFlat(Ljava/lang/String;Lcom/javapoly/FlatThrowable;)V': function(thread, obj, msgId, flatThrowable) { 179 | javapoly0.dispatcher.callbackMessage(msgId, {success:false, cause: flatThrowableToJS(flatThrowable)}); 180 | }, 181 | 182 | 'getMessageId()Ljava/lang/String;': function(thread, obj) { 183 | var id = javapoly0.dispatcher.getMessageId(); 184 | if (id) { 185 | return wrapObject(thread, id); 186 | } else { 187 | thread.setStatus(6); // ASYNC_WAITING 188 | javapoly0.dispatcher.setJavaPolyCallback(function() { 189 | javapoly0.dispatcher.setJavaPolyCallback(null); 190 | thread.asyncReturn( wrapObject(thread, javapoly0.dispatcher.getMessageId())); 191 | }); 192 | } 193 | }, 194 | 195 | 'isJSNativeObj(Ljava/lang/Object;)Z': function(thread, e) { 196 | // TODO: find a better way to check for a JS native object 197 | return (typeof(e) === "object") && (e !== null && !e.hasOwnProperty("$monitor")); 198 | }, 199 | 200 | 'getMessageType(Ljava/lang/String;)Ljava/lang/String;': function(thread, obj, msgId) { 201 | var unwrappedData = javapoly0.dispatcher.getMessageType(msgId); 202 | if (typeof unwrappedData !== 'undefined') { 203 | return wrapObject(thread, unwrappedData); 204 | } else { 205 | return null; 206 | } 207 | }, 208 | 209 | 'getData(Ljava/lang/String;)[Ljava/lang/Object;': function(thread, obj, msgId) { 210 | var unwrappedData = javapoly0.dispatcher.getMessageData(msgId); 211 | if (typeof unwrappedData !== 'undefined') { 212 | return wrapObject(thread, unwrappedData); 213 | } else { 214 | return null; 215 | } 216 | }, 217 | 218 | 'setJavaPolyInstanceId(Ljava/lang/String;)V': function(thread, obj, javapolyId){ 219 | javapoly0 = JavaPoly.getInstance(javapolyId); 220 | }, 221 | 222 | 'getRawType(Ljava/lang/Object;)[Ljava/lang/Object;': function(thread, obj) { 223 | return getRawType(thread, obj); 224 | } 225 | }, 226 | 227 | 'com/javapoly/DoppioJSObject': { 228 | 'getProperty(Ljava/lang/Object;Ljava/lang/String;)[Ljava/lang/Object;': function(thread, obj, name) { 229 | var nameStr = name.toString(); 230 | return getRawType(thread, obj[nameStr]); 231 | }, 232 | 233 | 'invoke(Ljava/lang/Object;[Ljava/lang/Object;)[Ljava/lang/Object;': function(thread, toInvoke, args) { 234 | var ubArgs = args.array.map( (e) => { 235 | if (typeof e === "object" && typeof e['getClass'] === "function") { 236 | var intName = e.getClass().getInternalName(); 237 | if (util.is_primitive_type(toPrimitiveTypeName(intName))) { 238 | return e.unbox(); 239 | } else if (intName === 'Ljava/lang/String;') { 240 | return e.toString(); 241 | } else { 242 | return e; 243 | } 244 | } else { 245 | return e; 246 | } 247 | }); 248 | var res = toInvoke.apply(null, ubArgs); 249 | return getRawType(thread, res); 250 | } 251 | }, 252 | 253 | 'com/javapoly/DoppioJSPrimitive': { 254 | 'asDouble(Ljava/lang/Object;)D': function(thread, arg0) { 255 | return arg0; 256 | }, 257 | 258 | 'asInteger(Ljava/lang/Object;)I': function(thread, arg0) { 259 | return arg0; 260 | }, 261 | 262 | 'asLong(Ljava/lang/Object;)J': function(thread, arg0) { 263 | return Doppio.VM.Long.fromNumber(arg0); 264 | }, 265 | 266 | 'asString(Ljava/lang/Object;)Ljava/lang/String;': function(thread, arg0) { 267 | return util.initString(thread.getBsCl(), arg0); 268 | } 269 | } 270 | }); 271 | -------------------------------------------------------------------------------- /src/natives/XHRConnection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | registerNatives({ 3 | 'com/javapoly/XHRHttpURLConnection': { 4 | 5 | 'getResponse([Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[B)Lcom/javapoly/XHRResponse;': function(thread, headers, method, url, outputBytes) { 6 | const methodStr = method.toString(); 7 | const urlStr = url.toString(); 8 | const myRequest = new XMLHttpRequest(); 9 | myRequest.open(methodStr, urlStr); 10 | 11 | // Set the headers 12 | { 13 | const headerArray = headers.array; 14 | const headerCount = headerArray.length / 2; 15 | for (let i = 0; i < headerCount; i++) { 16 | myRequest.setRequestHeader(headerArray[2*i], headerArray[2*i + 1]); 17 | } 18 | } 19 | 20 | myRequest.responseType = "arraybuffer"; 21 | myRequest.addEventListener("load", () => { 22 | thread.getBsCl().initializeClass(thread, 'Lcom/javapoly/XHRResponse;', () => { 23 | const responseObj = util.newObject(thread, thread.getBsCl(), 'Lcom/javapoly/XHRResponse;'); 24 | responseObj['(Ljava/lang/Object;)V'](thread, [myRequest], (e) => { 25 | if (e) { 26 | thread.throwException(e); 27 | } else { 28 | thread.asyncReturn(responseObj); 29 | } 30 | }); 31 | }); 32 | }); 33 | 34 | thread.setStatus(6); // ASYNC_WAITING 35 | 36 | if (outputBytes == null) { 37 | myRequest.send(); 38 | } else { 39 | myRequest.send(outputBytes.array); 40 | } 41 | 42 | } 43 | }, 44 | 45 | 'com/javapoly/XHRResponse': { 46 | 47 | 'getResponseBytes(Ljava/lang/Object;)[B': function(thread, xhrObj) { 48 | const array = Array.from(new Uint8Array(xhrObj.response)); 49 | return util.newArrayFromData(thread, thread.getBsCl(), "[B", array); 50 | }, 51 | 52 | 'getHeaderField(Ljava/lang/Object;Ljava/lang/String;)Ljava/lang/String;': function(thread, xhrObj, name) { 53 | return util.initString(thread.getBsCl(), xhrObj.getResponseHeader(name)); 54 | } 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /src/node-doppio.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaPoly from './core/JavaPolyNodeDoppio'; 3 | 4 | // Create main object that will be accessed via global objects J and Java 5 | global.JavaPoly = JavaPoly; 6 | 7 | // For running this code as Node module 8 | module.exports = JavaPoly; -------------------------------------------------------------------------------- /src/node-system.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import JavaPoly from './core/JavaPolyNodeSystem'; 3 | 4 | // Create main object that will be accessed via global objects J and Java 5 | global.JavaPoly = JavaPoly; 6 | 7 | // For running this code as Node module 8 | module.exports = JavaPoly; -------------------------------------------------------------------------------- /src/tools/classfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const MAGIC_NUMBER = 'cafebabe'; 4 | const CP_TAG_SIZE = 1; 5 | 6 | function uintFromBuffer(buffer, from, count) { 7 | let res = 0; 8 | let bufferGetter = (global.BrowserFS) ? (index) => { return buffer.get(index); } : (index) => {return buffer[index]; }; 9 | for(let i = 0; i < count; i++) { 10 | res = res * 256 + bufferGetter(from + i); 11 | } 12 | return res; 13 | } 14 | 15 | function stringFromBuffer(buffer, from, count) { 16 | let res = ''; 17 | let bufferGetter = (global.BrowserFS) ? (index) => { return buffer.get(index); } : (index) => {return buffer[index]; }; 18 | for(let i = 0; i < count; i++) { 19 | res += String.fromCharCode(bufferGetter(from + i)); 20 | } 21 | return res; 22 | } 23 | 24 | function hexFromBuffer(buffer, from, count) { 25 | let res = ''; 26 | let bufferGetter = (global.BrowserFS) ? (index) => { return buffer.get(index); } : (index) => {return buffer[index]; }; 27 | for(let i = 0; i < count; i++) { 28 | res += bufferGetter(from + i).toString(16); 29 | } 30 | return res; 31 | } 32 | 33 | function ConstantPoolClassRef(buffer, from) { 34 | this.ref = uintFromBuffer(buffer, from, 2); 35 | } 36 | 37 | /** 38 | * This function analyze buffer that contains class-file and returns basic info about it. 39 | * @param {Buffer} buffer that contains binary data of class-file 40 | * @return {Object} object that represents key-value info of file 41 | */ 42 | function analyze(data) { 43 | 'use strict'; 44 | 45 | let magic_number = hexFromBuffer(data, 0, 4); 46 | if (magic_number !== MAGIC_NUMBER) throw `Class file should starts with ${MAGIC_NUMBER} string`; 47 | 48 | let minor_version = uintFromBuffer(data, 4, 2); 49 | let major_version = uintFromBuffer(data, 6, 2); 50 | let constant_pool_count= uintFromBuffer(data, 8, 2); 51 | 52 | let constant_pool = []; 53 | 54 | let cpsize = 10; 55 | 56 | for(let i = 1; i < constant_pool_count; i++) { 57 | let cp_tag = uintFromBuffer(data, cpsize, CP_TAG_SIZE); 58 | let size = 0; 59 | switch(cp_tag) { 60 | case 1: 61 | size = 2 + uintFromBuffer(data, cpsize + 1, 2); 62 | constant_pool[i] = stringFromBuffer(data,cpsize + 3, size - 2); 63 | break; 64 | case 3: size = 4; break; 65 | case 4: size = 4; break; 66 | case 5: size = 8; i++; break; 67 | case 6: size = 8; i++; break; 68 | case 7: 69 | size = 2; 70 | constant_pool[i] = new ConstantPoolClassRef(data, cpsize + 1); 71 | break; 72 | case 8: size = 2; break; 73 | case 9: size = 4; break; 74 | case 10: size = 4; break; 75 | case 11: size = 4; break; 76 | case 12: size = 4; break; 77 | case 15: size = 3; break; 78 | case 16: size = 2; break; 79 | case 18: size = 4; break; 80 | default: throw `Wrong cp_tag: ${cp_tag}`; 81 | } 82 | cpsize += CP_TAG_SIZE + size; 83 | } 84 | 85 | let access_flags = uintFromBuffer(data, cpsize , 2); 86 | let this_class = uintFromBuffer(data, cpsize + 2, 2); 87 | let super_class = uintFromBuffer(data, cpsize + 4, 2); 88 | 89 | return { 90 | minor_version, 91 | major_version, 92 | constant_pool, 93 | this_class: constant_pool[constant_pool[this_class].ref], 94 | super_class: constant_pool[constant_pool[super_class].ref] 95 | }; 96 | } 97 | 98 | module.exports.analyze = analyze; 99 | 100 | -------------------------------------------------------------------------------- /src/tools/fsext.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | module.exports = function(fs, path) { 3 | 'use strict'; 4 | 5 | var _0777 = parseInt('0777', 8); 6 | 7 | /** 8 | * mkdir that creates folders recursive 9 | * it's based on https://github.com/substack/node-mkdirp 10 | */ 11 | var rmkdirSync = function sync (p, opts, made) { 12 | if (!opts || typeof opts !== 'object') { 13 | opts = { mode: opts }; 14 | } 15 | 16 | var mode = opts.mode; 17 | var xfs = opts.fs || fs; 18 | 19 | if (!made) made = null; 20 | 21 | p = path.resolve(p); 22 | 23 | try { 24 | xfs.mkdirSync(p, mode); 25 | made = made || p; 26 | } 27 | catch (err0) { 28 | switch (err0.code) { 29 | case 'ENOENT' : 30 | made = sync(path.dirname(p), opts, made); 31 | sync(p, opts, made); 32 | break; 33 | 34 | // In the case of any other error, just see if there's a dir 35 | // there already. If so, then hooray! If not, then something 36 | // is borked. 37 | default: 38 | var stat; 39 | try { 40 | stat = xfs.statSync(p); 41 | } 42 | catch (err1) { 43 | throw err0; 44 | } 45 | if (!stat.isDirectory()) throw err0; 46 | break; 47 | } 48 | } 49 | 50 | return made; 51 | }; 52 | 53 | return { 54 | rmkdirSync 55 | } 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /src/webworkers/JavaPolyWorker.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | import WorkerDispatcher from '../dispatcher/WorkerDispatcher.js' 3 | 4 | class JavaPolyWorker { 5 | constructor(options) { 6 | this.options = options; 7 | this.isJavaPolyWorker = true; 8 | } 9 | 10 | getId(){ 11 | // running multiple javapoly instances in webworker will generate corresponding number of web workers. 12 | // with only one javapoly instance in each workers. 13 | // so we cloud just store the only one javapoly instance in global.self to simplify. 14 | return 'default'; 15 | } 16 | 17 | static getInstance(javapolyId){ 18 | return self.javapoly; 19 | } 20 | 21 | init(dispatcher) { 22 | this.dispatcher = dispatcher; 23 | this.dispatcherReady = Promise.resolve(this.dispatcher); 24 | } 25 | 26 | } 27 | 28 | // NOTES, hack global window variable used in doppio, javapoly. 29 | global.window = global.self; 30 | global.self.JavaPoly = JavaPolyWorker; 31 | 32 | self.addEventListener('message', function(e) { 33 | if (!e.data || !e.data.javapoly) { 34 | //invalid command, ignore 35 | return; 36 | } 37 | 38 | const data = e.data.javapoly; 39 | 40 | switch (data.messageType) { 41 | case 'WORKER_INIT': 42 | const options = data.data.options; 43 | self.javapoly = new JavaPolyWorker(options); 44 | self.javapoly.init(new WorkerDispatcher(self.javapoly)); 45 | global.self.postMessage({javapoly:{messageId:data.messageId, messageType:'WORKER_INIT', returnValue:true}}); 46 | break; 47 | default: 48 | self.javapoly.dispatcher.handleWorkerMessage(data); 49 | break; 50 | }; 51 | }, false); 52 | -------------------------------------------------------------------------------- /tasks/grunt-compare-version.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var semver = require('semver'); 4 | 5 | var isFileExists = function(name) { 6 | return new Promise(function(resolve, reject) { 7 | var stats; 8 | try { 9 | fs.lstat(name, function(err, stats) { 10 | if (stats && stats.isFile()) { 11 | resolve(true); 12 | } else { 13 | resolve(false); 14 | } 15 | }); 16 | } catch(e) { 17 | resolve(false); 18 | } 19 | }); 20 | } 21 | 22 | module.exports = function(grunt) { 23 | grunt.registerMultiTask('compare_version', function() { 24 | var DATA_FROM = this.data.from; 25 | var DATA_TO = this.data.to; 26 | var tasks = this.data.tasks; 27 | var done = this.async(); 28 | isFileExists(path.join(DATA_FROM, 'package.json')) 29 | .then(function(fromExists) { 30 | if (fromExists) { 31 | isFileExists(path.join(DATA_TO, 'package.json')) 32 | .then(function(toExists) { 33 | var fromVersion = grunt.file.readJSON(path.join(DATA_FROM, 'package.json')).version; 34 | var toVersion = toExists ? grunt.file.readJSON(path.join(DATA_TO, 'package.json')).version : '0.0.0'; 35 | 36 | if (semver.neq(fromVersion, toVersion)) { 37 | grunt.task.run(tasks); 38 | } 39 | 40 | done(); 41 | }); 42 | } else { 43 | grunt.log.error('You don`t have FROM file.') 44 | done(); 45 | } 46 | }); 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /tasks/grunt-listings.js: -------------------------------------------------------------------------------- 1 | // https://github.com/plasma-umass/doppio/blob/master/tasks/listings.ts 2 | var fs = require('fs'); 3 | function generateListings(dir, ignore) { 4 | var symLinks = {}; 5 | function rdSync(dpath, tree, name) { 6 | var files = fs.readdirSync(dpath), i, file, fpath; 7 | for (i = 0; i < files.length; i++) { 8 | file = files[i]; 9 | if (ignore.indexOf(file) === -1) { 10 | fpath = dpath + "/" + file; 11 | try { 12 | var lstat = fs.lstatSync(fpath); 13 | if (lstat.isSymbolicLink()) { 14 | var realdir = fs.readlinkSync(fpath); 15 | // Ignore if we've seen it before. 16 | if (symLinks[realdir]) { 17 | continue; 18 | } 19 | else { 20 | symLinks[realdir] = 1; 21 | } 22 | } 23 | var fstat = fs.statSync(fpath); 24 | if (fstat.isDirectory()) { 25 | tree[file] = {}; 26 | rdSync(fpath, tree[file], file); 27 | } 28 | else { 29 | tree[file] = null; 30 | } 31 | } 32 | catch (e) { 33 | } 34 | } 35 | } 36 | return tree; 37 | } 38 | return rdSync(dir, {}, '/'); 39 | } 40 | function listings(grunt) { 41 | grunt.registerMultiTask('listings', 'Generates listings.json', function () { 42 | var options = this.options(), cwd = options.cwd; 43 | fs.writeFileSync(options.output, JSON.stringify(generateListings(cwd, ['.git', 'node_modules', '.DS_Store']))); 44 | }); 45 | } 46 | module.exports = listings; 47 | -------------------------------------------------------------------------------- /tasks/package/README.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | var JavaPoly = require('javapoly'); 3 | JavaPoly.addClass('/path/to/MyClass.java'); 4 | JavaPoly.addClass('/path/to/MyClass2.class'); 5 | JavaPoly.type('MyClass').then(function(MyClass){MyClass.doSomething();}); 6 | JavaPoly.type('MyClass2').then(function(MyClass2){MyClass2.doSomething();}); 7 | ``` 8 | 9 | JavaPoly will work in nodejs and/or in the web browser, even if the user doesn't have Java installed! 10 | 11 | For more details: https://www.javapoly.com -------------------------------------------------------------------------------- /tasks/package/index.js: -------------------------------------------------------------------------------- 1 | if (typeof window === "undefined") { 2 | // Running in Node 3 | console.log("JavaPolyNodeSystem running in Node"); 4 | 5 | var loadModule = function() { 6 | var path = require('path'); 7 | var currentDir = __dirname; 8 | var javaPolyPath = path.join(currentDir, 'javapoly-node-system-raw.js'); 9 | var result; 10 | try { 11 | result = require(javaPolyPath); 12 | } catch (e) { 13 | console.error('Cannot load module on path: %s', javaPolyPath); 14 | } 15 | return result; 16 | }; 17 | 18 | // Constructor 19 | const JavaPolyNodeSystem = loadModule(); 20 | if (JavaPolyNodeSystem) { 21 | // For running this code as Node module 22 | module.exports = JavaPolyNodeSystem; 23 | } else { 24 | console.error("JavaPolyNodeSystem wasn't loaded"); 25 | } 26 | } else { 27 | // Running in browser 28 | console.log("JavaPolyBrowser running in browser"); 29 | 30 | // 31 | module.exports = require('./javapoly-browser.js'); 32 | } 33 | -------------------------------------------------------------------------------- /tasks/package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javapoly", 3 | "version": "0.0.1", 4 | "description": "JavaPoly.js is a library that polyfills native JVM support. It allows you to import Java code and invoke the code directly from Javascript, even if the user doesn't have Java installed on their computer.", 5 | "main": "javapoly-node-system-raw.js", 6 | "browser": "javapoly-browser.js", 7 | "author": "", 8 | "homepage": "https://www.javapoly.com", 9 | "license": "BSD-3-Clause", 10 | "dependencies": { 11 | "csrf": "^3.0.1", 12 | "jvminstall": "^0.1.0", 13 | "underscore": "^1.8.3", 14 | "jsjavaparser": "https://github.com/sreym/jsjavaparser.git", 15 | "ws": "^1.0.1" 16 | }, 17 | "scripts": { 18 | "postinstall": "node -e \"require('jvminstall');\"" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "http://git.javadeploy.net/jimsproch/JavaPoly.git" 23 | }, 24 | "keywords": [ 25 | "java", 26 | "javascript", 27 | "doppio" 28 | ] 29 | } -------------------------------------------------------------------------------- /test/DirectRun.js: -------------------------------------------------------------------------------- 1 | // const activeHandles = require('active-handles'); 2 | 3 | const path = require('path'); 4 | 5 | function initJavaPoly() { 6 | require('../build/javapoly-node-system.js'); 7 | 8 | global.isWorkerBased = false; 9 | 10 | const javapolyBase = path.resolve("build/"); 11 | 12 | const jp = new JavaPoly({javapolyBase: javapolyBase}); 13 | } 14 | 15 | initJavaPoly(); 16 | 17 | addClass(path.resolve('test/classes/Main3.java')).then(function(addClassResult){ 18 | return JavaPoly.type('com.javapoly.test.Main3').then(function(Main3) { 19 | return Main3.testIt().then(function (result){ 20 | console.log("pass: " + (result === "Main3::testIt()23541499653099")); 21 | // setTimeout(() => {activeHandles.print();}, 2000); 22 | }); 23 | }); 24 | return 0; 25 | }); 26 | 27 | addClass(path.resolve('test/classes/Threads.class')).then(function(addClassResult){ 28 | console.log("Threads added"); 29 | return JavaPoly.type('Threads').then(function(Threads) { 30 | return Threads.startSleepyThread().then(function () { 31 | return Threads.startBusyThread().then(function () { 32 | return Threads.testIt().then(function (result){ 33 | console.log("Result: " + result); 34 | console.log("pass: " + (result === 5842488)); 35 | }); 36 | }); 37 | }); 38 | }); 39 | return 0; 40 | }); 41 | 42 | /* 43 | setTimeout(() => { 44 | throw new Error("Check error handling"); 45 | }, 2000); 46 | */ 47 | 48 | // setTimeout(() => {console.log("Dummy timeout")}, 10000); 49 | -------------------------------------------------------------------------------- /test/TestJavaPolyNodeDoppio.js: -------------------------------------------------------------------------------- 1 | // `npm install source-map-support` and uncomment to get mapped sources in stack traces 2 | // require('source-map-support').install(); 3 | 4 | const NodeFS = require("fs"); 5 | 6 | const path = require('path'); 7 | 8 | global.expect = require("expect/umd/expect.min.js"); 9 | 10 | function initJavaPoly() { 11 | require('../build/javapoly-node-doppio-raw.js'); 12 | 13 | global.isWorkerBased = false; 14 | 15 | const doppioBase = path.resolve("node_modules/@hrj/doppiojvm-snapshot/dist/release-cli/") 16 | const javapolyBase = path.resolve("build/"); 17 | 18 | global.Doppio = require(doppioBase + "/src/doppiojvm.js"); 19 | const jp = new JavaPoly({doppioBase: doppioBase, javapolyBase: javapolyBase}); 20 | } 21 | 22 | function runScript(fileName) { 23 | const vm = require('vm') 24 | 25 | const content = NodeFS.readFileSync(path.resolve(fileName)); 26 | vm.runInThisContext(content, {filename: fileName}) 27 | } 28 | 29 | initJavaPoly(); 30 | 31 | describe('javapoly test', function() { 32 | 33 | 34 | this.timeout(100000); // Let jvm to start and do not interrupt promises 35 | 36 | /* 37 | Currently doesn't work because XMLHttpRequest is not available in node! 38 | runScript("test/units/urls.js"); 39 | testUrls(); 40 | */ 41 | 42 | it('add jar', function(){ 43 | return addClass(path.resolve('test/jars/commons-codec-1.10.jar')).then(function(addClassResult){ 44 | return JavaPoly.type('org.apache.commons.codec.binary.StringUtils').then(function(StringUtils) { 45 | return StringUtils.equals('a','a').then(function (result){ 46 | expect(result).toEqual(true); 47 | }); 48 | }); 49 | }); 50 | }); 51 | 52 | it('compile java source', function(){ 53 | return addClass(path.resolve('test/classes/Main3.java')).then(function(addClassResult){ 54 | return JavaPoly.type('com.javapoly.test.Main3').then(function(Main3) { 55 | return Main3.testIt().then(function (result){ 56 | expect(result).toEqual("Main3::testIt()"); 57 | }); 58 | }); 59 | }); 60 | }); 61 | 62 | runScript("test/units/proxy.js"); 63 | testProxy(); 64 | 65 | describe('Object wrapper Tests', function() { 66 | before(() => { 67 | const counterAdd = addClass(path.resolve('test/classes/Counter.class')); 68 | const longAdd = addClass(path.resolve('test/classes/com/javapoly/test/LongTest.class')); 69 | return Promise.all([counterAdd, longAdd]); 70 | }); 71 | 72 | runScript("test/units/objectWrappers.js"); 73 | testObjectWrappers(); 74 | }); 75 | 76 | describe('Reflection Tests', function() { 77 | before(() => { 78 | return addClass(path.resolve('test/classes/EvalTest.class')); 79 | }); 80 | 81 | runScript("test/units/reflect.js"); 82 | testReflect(); 83 | 84 | }); 85 | 86 | describe('Eval Tests', function() { 87 | before(() => { 88 | return addClass(path.resolve('test/classes/EvalTest.class')); 89 | }); 90 | 91 | runScript("test/units/eval.js"); 92 | testEval(); 93 | 94 | }); 95 | 96 | describe('Exception Tests', function() { 97 | before(() => { 98 | return addClass(path.resolve('test/classes/Main.class')); 99 | }); 100 | 101 | runScript("test/units/exceptions.js"); 102 | testExceptions(); 103 | 104 | }); 105 | 106 | }); 107 | -------------------------------------------------------------------------------- /test/TestJavaPolyNodeSystem.js: -------------------------------------------------------------------------------- 1 | // `npm install source-map-support` and uncomment to get mapped sources in stack traces 2 | // require('source-map-support').install(); 3 | 4 | const NodeFS = require("fs"); 5 | 6 | const path = require('path'); 7 | 8 | global.expect = require("expect/umd/expect.min.js"); 9 | 10 | function initJavaPoly() { 11 | require('../build/javapoly-node-system-raw.js'); 12 | // require('../src/node-system.js'); 13 | 14 | global.isWorkerBased = false; 15 | 16 | const javapolyBase = path.resolve("build/"); 17 | 18 | const jp = new JavaPoly({javapolyBase: javapolyBase}); 19 | } 20 | 21 | function runScript(fileName) { 22 | const vm = require('vm') 23 | 24 | const content = NodeFS.readFileSync(path.resolve(fileName)); 25 | vm.runInThisContext(content, {filename: fileName}) 26 | } 27 | 28 | initJavaPoly(); 29 | 30 | describe('javapoly test', function() { 31 | 32 | 33 | this.timeout(100000); // Let jvm to start and do not interrupt promises 34 | 35 | it('add jar', function(){ 36 | return addClass(path.resolve('test/jars/commons-codec-1.10.jar')).then(function(addClassResult){ 37 | return JavaPoly.type('org.apache.commons.codec.binary.StringUtils').then(function(StringUtils) { 38 | return StringUtils.equals('a','a').then(function (result){ 39 | expect(result).toEqual(true); 40 | }); 41 | }); 42 | }); 43 | }); 44 | 45 | it('compile java source', function(){ 46 | return addClass(path.resolve('test/classes/Main3.java')).then(function(addClassResult){ 47 | return JavaPoly.type('com.javapoly.test.Main3').then(function(Main3) { 48 | return Main3.testIt().then(function (result){ 49 | expect(result).toEqual("Main3::testIt()"); 50 | }); 51 | }); 52 | }); 53 | }); 54 | 55 | runScript("test/units/proxy.js"); 56 | testProxy(); 57 | 58 | it('should handle exceptions correctly', function() { 59 | return addClass(path.resolve('test/classes/Main.class')).then(function(addClassResult){ 60 | return JavaPoly.type('Main').then(function(Main) { 61 | return new Promise(function(resolve, reject) { 62 | Main.exceptionThrower().then(function() { 63 | reject(new Error("not expecting the promise to resolve")); 64 | }, function(e) { 65 | expect(e.name).toBe("java.lang.RuntimeException"); 66 | expect(e.message).toBe("Deliberate exception for testing"); 67 | expect(e.causedBy).toNotExist(); 68 | expect(e.printStackTrace).toExist(); 69 | resolve(); 70 | }); 71 | }); 72 | }); 73 | }, function(error) { 74 | console.log(error.printStackTrace()); 75 | }); 76 | }); 77 | 78 | describe('Exception Tests', function() { 79 | before(() => { 80 | return addClass(path.resolve('test/classes/Main.class')); 81 | }); 82 | 83 | runScript("test/units/exceptions.js"); 84 | testExceptions(); 85 | 86 | }); 87 | 88 | describe('Reference Tests', function() { 89 | before(() => { 90 | return addClass(path.resolve('test/classes/References.class')); 91 | }); 92 | 93 | runScript("test/units/reference.js"); 94 | testReference(); 95 | 96 | }); 97 | 98 | describe('Reflection Tests', function() { 99 | before(() => { 100 | return addClass(path.resolve('test/classes/EvalTest.class')); 101 | }); 102 | 103 | runScript("test/units/reflect.js"); 104 | testReflect(); 105 | 106 | }); 107 | 108 | describe('Eval Tests', function() { 109 | before(() => { 110 | return addClass(path.resolve('test/classes/EvalTest.class')); 111 | }); 112 | 113 | runScript("test/units/eval.js"); 114 | testEval(); 115 | 116 | }); 117 | 118 | }); 119 | -------------------------------------------------------------------------------- /test/classes/Counter.java: -------------------------------------------------------------------------------- 1 | public class Counter { 2 | public final double wrapLimit = 27; 3 | 4 | public double currentValue = 0; 5 | 6 | public Counter() { 7 | } 8 | 9 | public void increment(final double delta) { 10 | currentValue += delta; 11 | currentValue %= wrapLimit; 12 | } 13 | 14 | public boolean isValid() { 15 | return currentValue < wrapLimit; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/classes/EvalTest.java: -------------------------------------------------------------------------------- 1 | import com.javapoly.Eval; 2 | import com.javapoly.reflect.*; 3 | 4 | public class EvalTest { 5 | public static boolean test() { 6 | // First define a function 7 | final JSObject squareFunc = (JSObject) Eval.eval("(function(x){return x*x;})"); 8 | 9 | // Invoke it with 7 as argument 10 | final JSPrimitive square7 = (JSPrimitive) squareFunc.invoke(7); 11 | final boolean test1Pass = square7.asInteger() == 49; 12 | 13 | // Invoke it with 13 as argument 14 | final JSPrimitive square13 = (JSPrimitive) squareFunc.invoke(13); 15 | final boolean test2Pass = square13.asDouble() == 169.0; 16 | 17 | final JSPrimitive square169 = (JSPrimitive) squareFunc.invoke(square13); 18 | final boolean test3Pass = square169.asLong() == 28561; 19 | 20 | // define a string manipulation function 21 | final JSObject firstPartFunc = (JSObject) Eval.eval("(function(str, delim, n){return str.split(delim)[n];})"); 22 | 23 | // Invoke it with two string arguments and an integer 24 | final JSPrimitive firstString = (JSPrimitive) firstPartFunc.invoke("a,b,c,d", ",", 0); 25 | final boolean test4Pass = firstString.asString().equals("a"); 26 | 27 | final JSPrimitive secondString = (JSPrimitive) firstPartFunc.invoke("a,b,c,d", ",", 1); 28 | final boolean test5Pass = secondString.asString().equals("b"); 29 | 30 | return test1Pass && test2Pass && test3Pass && test4Pass && test5Pass; 31 | } 32 | 33 | public static JSValue getProperty(final JSObject jsObj, final String name) { 34 | return jsObj.getProperty(name); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /test/classes/Main.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | public static String test() { 3 | return "test message"; 4 | } 5 | 6 | public static void main(String args[]) { 7 | System.out.println("hello world2"); 8 | } 9 | 10 | public static void exceptionThrower() { 11 | throw new RuntimeException("Deliberate exception for testing"); 12 | } 13 | 14 | public static void exceptionThrowerWithNullMessage() { 15 | throw new RuntimeException((String) null); 16 | } 17 | 18 | // Can be used to throw a divide-by-zero exception 19 | public static int exceptionThrower2(int x) { 20 | return 100 / x; 21 | } 22 | 23 | /* A function that checks the length of a string. The idea is to return some boolean, to test the wrapping of 24 | * primitive boolean values */ 25 | public static boolean checkLength(final String str, final int n) { 26 | return str.length() == n; 27 | } 28 | 29 | /* A simple function to test round trip of boolean values, since booleans are treated as numbers internally and need 30 | * special conversions */ 31 | public static boolean flip(final boolean b) { 32 | return !b; 33 | } 34 | 35 | /* private method defined only to check accessibilty */ 36 | private static void privateMethod() { 37 | throw new RuntimeException("Should never be called"); 38 | } 39 | 40 | /* protected method defined only to check accessibilty */ 41 | protected static void protectedMethod() { 42 | throw new RuntimeException("Should never be called"); 43 | } 44 | 45 | /* private instance method defined only to check accessibilty */ 46 | private void privateInstanceMethod() { 47 | throw new RuntimeException("Should never be called"); 48 | } 49 | 50 | /* protected instance method defined only to check accessibilty */ 51 | protected void protectedInstanceMethod() { 52 | throw new RuntimeException("Should never be called"); 53 | } 54 | 55 | /* public instance method defined only to check accessibilty */ 56 | public void publicInstanceMethod() { 57 | throw new RuntimeException("Should never be called"); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /test/classes/Main2.java: -------------------------------------------------------------------------------- 1 | public class Main2 { 2 | public static String test() { 3 | return "test message in Main2"; 4 | } 5 | 6 | public static void main(String args[]) { 7 | System.out.println("hello world2"); 8 | } 9 | 10 | /* A function that checks the length of a string. The idea is to return some boolean, to test the wrapping of 11 | * primitive boolean values */ 12 | public static boolean checkLength(final String str, final int n) { 13 | return str.length() == n; 14 | } 15 | 16 | /* A simple function to test round trip of boolean values, since booleans are treated as numbers internally and need 17 | * special conversions */ 18 | public static boolean flip(final boolean b) { 19 | return !b; 20 | } 21 | 22 | /* private method defined only to check accessibilty */ 23 | private static void privateMethod() { 24 | throw new RuntimeException("Should never be called"); 25 | } 26 | 27 | /* protected method defined only to check accessibilty */ 28 | protected static void protectedMethod() { 29 | throw new RuntimeException("Should never be called"); 30 | } 31 | 32 | /* private instance method defined only to check accessibilty */ 33 | private void privateInstanceMethod() { 34 | throw new RuntimeException("Should never be called"); 35 | } 36 | 37 | /* protected instance method defined only to check accessibilty */ 38 | protected void protectedInstanceMethod() { 39 | throw new RuntimeException("Should never be called"); 40 | } 41 | 42 | /* public instance method defined only to check accessibilty */ 43 | public void publicInstanceMethod() { 44 | throw new RuntimeException("Should never be called"); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /test/classes/Main3.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.test; 2 | public class Main3 { 3 | public static String testIt() { 4 | return "Main3::testIt()"; 5 | } 6 | } -------------------------------------------------------------------------------- /test/classes/MainWithPackage.java: -------------------------------------------------------------------------------- 1 | package com.javapoly; 2 | 3 | public class MainWithPackage { 4 | public static String test() { 5 | return "MainWithPackage test() method"; 6 | } 7 | 8 | public static void main(String args[]) { 9 | System.out.println("hello world2"); 10 | } 11 | } -------------------------------------------------------------------------------- /test/classes/Overload.java: -------------------------------------------------------------------------------- 1 | import java.lang.reflect.Constructor; 2 | 3 | public class Overload { 4 | 5 | private String text; 6 | 7 | public Overload() { 8 | this.text = "empty"; 9 | } 10 | 11 | public Overload(Character c) { 12 | this.text = "Character:" + c; 13 | } 14 | 15 | public Overload(long l) { 16 | this.text = "long:" + l; 17 | } 18 | 19 | public Overload(Float f) { 20 | this.text = "Float:" + f; 21 | } 22 | 23 | public static String staticMethod(char ch) { 24 | return "char:" + ch; 25 | } 26 | 27 | public static String staticMethod(byte b) { 28 | return "byte:" + b; 29 | } 30 | 31 | public static String staticMethod(Float f) { 32 | return "Float:" + f; 33 | } 34 | 35 | public String method(String name) { 36 | return "String:" + name; 37 | } 38 | 39 | public String method(Byte b) { 40 | return "Byte:" + b; 41 | } 42 | 43 | public String method(Short b) { 44 | return "Short:" + b; 45 | } 46 | 47 | public String getText() { 48 | return text; 49 | } 50 | 51 | public static Object identityFunction(Object arg) { 52 | return arg; 53 | } 54 | } -------------------------------------------------------------------------------- /test/classes/References.java: -------------------------------------------------------------------------------- 1 | import com.javapoly.reflect.JSValue; 2 | 3 | public class References { 4 | 5 | private static JSValue obj = null; 6 | 7 | public static void hold(JSValue objParam) { 8 | obj = objParam; 9 | System.gc(); 10 | } 11 | 12 | public static void release() { 13 | obj = null; 14 | System.gc(); 15 | System.out.println("Released obj"); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test/classes/ShaTest.java: -------------------------------------------------------------------------------- 1 | import java.io.UnsupportedEncodingException; 2 | import java.security.MessageDigest; 3 | import java.security.NoSuchAlgorithmException; 4 | 5 | public class ShaTest { 6 | 7 | public static byte sha256(String input) throws NoSuchAlgorithmException{ 8 | MessageDigest md = MessageDigest.getInstance("SHA-256"); 9 | md.update(input.getBytes()); 10 | byte[] digest = md.digest(); 11 | return digest[0]; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/classes/Threads.java: -------------------------------------------------------------------------------- 1 | class Threads { 2 | private static Thread busyThread = null; 3 | private static Thread sleepyThread = null; 4 | 5 | private static int busyResult = 0; 6 | private static int sleepyResult = 0; 7 | 8 | public static void startBusyThread() { 9 | busyThread = new Thread(() -> { 10 | for (int i = 1; i < 20; i++) { 11 | if (isPrime(fib(i))) { 12 | busyResult += fib(i*2); 13 | } 14 | } 15 | System.out.println("busy result: " + busyResult); 16 | }); 17 | busyThread.start(); 18 | } 19 | 20 | public static void startSleepyThread() { 21 | sleepyThread = new Thread(() -> { 22 | try { 23 | for (int i = 2; i < 12; i++) { 24 | Thread.sleep(500); 25 | if (isPrime(fib(i))) { 26 | sleepyResult += i; 27 | } 28 | } 29 | System.out.println("sleepyResult: " + sleepyResult); 30 | } catch (InterruptedException ie) { 31 | ie.printStackTrace(); 32 | } 33 | }); 34 | sleepyThread.start(); 35 | } 36 | 37 | private static boolean isPrime(long n) { 38 | if (n <= 3) return true; 39 | if (n % 2 == 0) return false; 40 | final long upperBound = (int) Math.sqrt(n); 41 | for (long i = 3; i < upperBound; i += 2) { 42 | if (n % i == 0) { 43 | return false; 44 | } 45 | } 46 | return true; 47 | } 48 | 49 | private static int fib(int n) { 50 | return (n < 2) ? n : fib(n-1) + fib(n-2); 51 | } 52 | 53 | public static int testIt() { 54 | try { 55 | sleepyThread.join(); 56 | busyThread.join(); 57 | } catch (InterruptedException ie) { 58 | ie.printStackTrace(); 59 | } 60 | 61 | return sleepyResult + busyResult; 62 | } 63 | 64 | public static void main(String[] args) { 65 | startSleepyThread(); 66 | startBusyThread(); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /test/classes/URLConnectionTest.java: -------------------------------------------------------------------------------- 1 | import java.io.*; 2 | import java.net.*; 3 | 4 | class URLConnectionTest { 5 | public static InputStream sendData(final String urlStr) { 6 | try { 7 | final URL url = new URL(urlStr); 8 | final HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 9 | connection.setRequestMethod("POST"); 10 | 11 | connection.setDoOutput(true); 12 | 13 | final InputStream is = connection.getInputStream(); 14 | 15 | final OutputStream os = connection.getOutputStream(); 16 | 17 | for (int i = 0; i < 120; i++) { 18 | os.write(i); 19 | } 20 | 21 | os.close(); 22 | 23 | return is; 24 | } catch (Exception e) { 25 | throw new RuntimeException(e); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /test/classes/com/javapoly/test/LongTest.java: -------------------------------------------------------------------------------- 1 | package com.javapoly.test; 2 | 3 | public class LongTest { 4 | public static long testPassingPos() { 5 | return 9007199254740991L; 6 | } 7 | public static long testFailingPos() { 8 | return 9007199254740992L; 9 | } 10 | public static long testPassingNeg() { 11 | return -9007199254740991L; 12 | } 13 | public static long testFailingNeg() { 14 | return -9007199254740992L; 15 | } 16 | } -------------------------------------------------------------------------------- /test/classfile_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var classfile = require('./../src/tools/classfile.js'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | 6 | describe('classfile', function() { 7 | describe('#analyze', function () { 8 | it('classes/Main.class', function () { 9 | fs.readFile(path.join(__dirname, './classes/Main.class'), (err, data)=> { 10 | var info = classfile.analyze(data); 11 | assert.equal(info.this_class, 'Main'); 12 | assert.equal(info.super_class, 'java/lang/Object'); 13 | }) 14 | }); 15 | }); 16 | }); 17 | 18 | 19 | -------------------------------------------------------------------------------- /test/expect: -------------------------------------------------------------------------------- 1 | ../node_modules/expect -------------------------------------------------------------------------------- /test/fsext_test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | var fsext = require('./../src/tools/fsext.js')(fs, path); 5 | 6 | describe('fsext', function() { 7 | describe('#rmkdirSync', function () { 8 | it('create relative path dir', function () { 9 | try { 10 | fsext.rmkdirSync('hello/world/folder'); 11 | } finally { 12 | fs.rmdirSync('hello/world/folder'); 13 | fs.rmdirSync('hello/world'); 14 | fs.rmdirSync('hello'); 15 | } 16 | }); 17 | }); 18 | }); 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaPoly Tests 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 |
27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /test/jars/commons-codec-1.10.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdstroy/JavaPoly/ceb75ecefc9372db4c2d6d99c7700881f5d25a0b/test/jars/commons-codec-1.10.jar -------------------------------------------------------------------------------- /test/jars/javapoly-utils.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jdstroy/JavaPoly/ceb75ecefc9372db4c2d6d99c7700881f5d25a0b/test/jars/javapoly-utils.jar -------------------------------------------------------------------------------- /test/java_source_file.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaPoly Tests: Java Source Files 5 | 6 | 7 | 8 | 9 | 10 | 18 | 19 | 27 | 28 | 39 | 40 | 49 | 50 | 63 | 64 | 65 | 66 | 70 | 71 |
72 | 73 | 74 | 75 | 76 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /test/mocha: -------------------------------------------------------------------------------- 1 | ../node_modules/mocha/ -------------------------------------------------------------------------------- /test/multiple_instances.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | JavaPoly Tests: Multiple Javapoly instances 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 200 | 201 | -------------------------------------------------------------------------------- /test/natives/Utilities.js: -------------------------------------------------------------------------------- 1 | registerNatives({ 2 | 'com/javapoly/runtime/Utilities': { 3 | 4 | 'evalNative(Ljava/lang/String;)Ljava/lang/Object;': function(thread, expr) { 5 | // Note: the conversion to from jvm string to JS string is not required in some cases. 6 | var exprJS = expr.toString(); 7 | var res = window.eval(exprJS); 8 | return util.newArrayFromData(thread, thread.getBsCl(), '[Ljava/lang/Object;', [res]); 9 | } 10 | } 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /test/natives/polylistings.json: -------------------------------------------------------------------------------- 1 | {"Utilities.js":null} 2 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | const http = require('http'); 4 | const fs = require('fs'); 5 | const path = require('path'); 6 | const url = require('url'); 7 | const crypto = require('crypto'); 8 | 9 | 10 | const server = http.createServer((request, response) => { 11 | if (request.url.startsWith('/api')) { 12 | apiHandler(request, response); 13 | } else { 14 | fileHandler(request, response); 15 | } 16 | }).listen(8080); 17 | 18 | function apiHandler(request, response) { 19 | console.log("Got API request"); 20 | let dataBuf = new Buffer(0); 21 | request.on('data', (chunk) => { 22 | dataBuf = Buffer.concat([dataBuf, chunk]); 23 | }); 24 | request.on('end', () => { 25 | 26 | const query = url.parse(request.url, true).query; 27 | 28 | const json = { 29 | 'status': 'OK', 30 | 'headers': request.headers, 31 | 'httpVersion': request.httpVersion, 32 | 'method': request.method, 33 | 'input-count': dataBuf.length, 34 | 'input-md5': crypto.createHash('md5').update(dataBuf).digest("hex") 35 | }; 36 | 37 | response.writeHead(200, { 'Content-Type': "application/json", "server": "javapolyTest000" }); 38 | response.end(JSON.stringify(json), 'utf-8'); 39 | }); 40 | } 41 | 42 | function fileHandler(request, response) { 43 | let filePath = '.' + url.parse(request.url).pathname; 44 | if (filePath === './') { 45 | filePath = './index.html'; 46 | } 47 | console.log("filePath: ["+ filePath+ "]"); 48 | 49 | const extname = path.extname(filePath); 50 | let contentType = 'application/octet-stream'; 51 | switch (extname) { 52 | case '.html': 53 | contentType = 'text/html'; 54 | break; 55 | case '.js': 56 | contentType = 'text/javascript'; 57 | break; 58 | case '.css': 59 | contentType = 'text/css'; 60 | break; 61 | case '.json': 62 | contentType = 'application/json'; 63 | break; 64 | case '.png': 65 | contentType = 'image/png'; 66 | break; 67 | case '.jpg': 68 | contentType = 'image/jpg'; 69 | break; 70 | case '.wav': 71 | contentType = 'audio/wav'; 72 | break; 73 | } 74 | 75 | fs.readFile(filePath, function(error, content) { 76 | if (error) { 77 | if(error.code == 'ENOENT') { 78 | fs.readFile('./404.html', function(error, content) { 79 | response.writeHead(404, { 'Content-Type': contentType }); 80 | response.end(content, 'utf-8'); 81 | }); 82 | } else { 83 | console.log(error); 84 | response.writeHead(500); 85 | response.end('Sorry, check with the site admin for error: '+error.code+' ..\n'); 86 | response.end(); 87 | } 88 | } else { 89 | response.writeHead(200, { 'Content-Type': contentType }); 90 | response.end(content, 'utf-8'); 91 | } 92 | }); 93 | 94 | } 95 | 96 | console.log('Server running at http://127.0.0.1:8080/'); 97 | 98 | -------------------------------------------------------------------------------- /test/simpleResponse.bin: -------------------------------------------------------------------------------- 1 | abc 2 | -------------------------------------------------------------------------------- /test/style.css: -------------------------------------------------------------------------------- 1 | #mocha-report > .suite > ul { 2 | display: flex; 3 | flex-wrap: wrap; 4 | } 5 | #mocha-report > .suite > ul > .suite { 6 | border: 1px solid #eee; 7 | padding: 0 8px; 8 | margin-top: 15px; 9 | flex-grow: 1; 10 | } 11 | 12 | .workerChoice { list-style:none; padding: 1em;} 13 | .workerChoice > li { display:inline; } 14 | .workerChoice > li > a { 15 | padding:.3em 1em; background: #eee; border-radius:.3em; text-decoration: none; color: black; 16 | font-size: 1.2em; font-weight:bold; 17 | } 18 | .workerChoice > li > a:hover { background: greenyellow;} 19 | .workerChoice > li.selected > a { 20 | border:2px solid green; 21 | } 22 | -------------------------------------------------------------------------------- /test/units/crypto.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function testCrypto() { 4 | 5 | describe('Test javax crypto ', function() { 6 | it('Mac', function() { 7 | return JavaPoly.type('javax.crypto.Mac').then(function(Mac) { 8 | return Mac.getInstance("HmacMD5").then(function(hmacInstance) { 9 | expect(hmacInstance).toExist(); 10 | }); 11 | }); 12 | }); 13 | it('sha256', function() { 14 | return JavaPoly.type('ShaTest').then(function(ShaTest) { 15 | return ShaTest.sha256("noise").then(function(result) { 16 | expect(result).toEqual(-17); 17 | }); 18 | }); 19 | }); 20 | }); 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /test/units/dynamicAdd.js: -------------------------------------------------------------------------------- 1 | function testDynamicAdd() { 2 | var isProxySupported = (typeof Proxy !== 'undefined'); 3 | describe('dynamically addClass', function(){ 4 | it('add jar', function(){ 5 | return addClass('/jars/commons-codec-1.10.jar').then(function(addClassResult){ 6 | return JavaPoly.type('org.apache.commons.codec.binary.StringUtils').then(function(StringUtils) { 7 | return StringUtils.equals('a','a').then(function (result){ 8 | expect(result).toEqual(true); 9 | }); 10 | }); 11 | }); 12 | }); 13 | 14 | it('add class', function(){ 15 | return addClass('/classes/Main2.class').then(function(addClassResult){ 16 | return JavaPoly.type('Main2').then(function(Main) { 17 | return Main.test().then(function(result) { 18 | expect(result).toEqual('test message in Main2'); 19 | }); 20 | }); 21 | }); 22 | }); 23 | 24 | it('add remote source code', function() { 25 | return addClass('/classes/Main3.java').then(function(addClassResult){ 26 | return JavaPoly.type('com.javapoly.test.Main3').then(function(Main3) { 27 | return Main3.testIt().then(function(result) { 28 | expect(result).toEqual('Main3::testIt()'); 29 | }); 30 | }); 31 | }); 32 | }); 33 | 34 | it('add embedded source code', function() { 35 | var mainJavaSource = 'package com.javapoly.test; ' + 36 | 'public class Main4 { ' + 37 | 'public static String testIt() { ' + 38 | ' return "Main4:testIt()"; ' + 39 | '} ' + 40 | '}'; 41 | return addClass(mainJavaSource).then(function(addClassResult){ 42 | return JavaPoly.type('com.javapoly.test.Main4').then(function(Main4) { 43 | return Main4.testIt().then(function(result) { 44 | expect(result).toEqual('Main4:testIt()'); 45 | }); 46 | }); 47 | }); 48 | }); 49 | 50 | if (isProxySupported) { 51 | describe('Proxy access of dynamically added Class', function(){ 52 | 53 | it('proxy access of added jar, ', function(){ 54 | return addClass('/jars/commons-codec-1.10.jar').then(function(addClassResult){ 55 | return org.apache.commons.codec.binary.StringUtils.equals('a','a').then(function (result){ 56 | expect(result).toEqual(true); 57 | }); 58 | }); 59 | }); 60 | 61 | it('proxy access of added class', function(){ 62 | return addClass('/classes/Main2.class').then(function(addClassResult){ 63 | return J.Main2.test().then(function(result) { 64 | expect(result).toEqual('test message in Main2'); 65 | }); 66 | }); 67 | }); 68 | 69 | it('proxy access of added source code', function() { 70 | return addClass('/classes/Main3.java').then(function(addClassResult){ 71 | return com.javapoly.test.Main3.testIt().then(function(result) { 72 | expect(result).toEqual('Main3::testIt()'); 73 | }); 74 | }); 75 | }); 76 | 77 | it('add embedded source code', function() { 78 | var mainJavaSource = 'package com.javapoly.test;\n' + 79 | 'public class Main4 {\n' + 80 | 'public static String testIt() {\n' + 81 | 'return "Main4:testIt()";\n' + 82 | '}\n' + 83 | '}'; 84 | return addClass(mainJavaSource).then(function(addClassResult){ 85 | return com.javapoly.test.Main4.testIt().then(function(result) { 86 | expect(result).toEqual('Main4:testIt()'); 87 | }); 88 | }); 89 | }); 90 | }); 91 | } 92 | 93 | }); 94 | } 95 | -------------------------------------------------------------------------------- /test/units/eval.js: -------------------------------------------------------------------------------- 1 | function testEval() { 2 | describe('Eval', function() { 3 | 4 | it('should pass all tests from EvalTest.java', function() { 5 | return JavaPoly.type('EvalTest').then(function(EvalTest) { 6 | return EvalTest.test().then(function(result) { 7 | expect(result).toEqual(true); 8 | }, (error) => console.log(error)); 9 | }); 10 | }); 11 | 12 | }); 13 | } 14 | 15 | /* Disabling for now. We can re-enable after using the newer version of eval. 16 | 17 | describe('Utilities', function() { 18 | 19 | describe('eval', function() { 20 | it('integer arithmetic', function() { 21 | return JavaPoly.type('com.javapoly.runtime.Utilities').then(function(Utilities) { 22 | Utilities.eval("40 + 2").then(function(result) { 23 | expect(result).toEqual(42); 24 | }); 25 | }); 26 | }); 27 | 28 | it('string split', function() { 29 | return JavaPoly.type('com.javapoly.runtime.Utilities').then(function(Utilities) { 30 | Utilities.eval("'a,b,c'.split(',')").then(function(result) { 31 | expect(result).toEqual(["a", "b", "c"]); 32 | }); 33 | }); 34 | }); 35 | 36 | it('function definition', function() { 37 | return JavaPoly.type('com.javapoly.runtime.Utilities').then(function(Utilities) { 38 | Utilities.eval("(function(x){return x*x})").then(function(result) { 39 | expect(result(7)).toEqual(49); 40 | }); 41 | }); 42 | }); 43 | 44 | }); 45 | 46 | }); 47 | */ 48 | 49 | -------------------------------------------------------------------------------- /test/units/exceptions.js: -------------------------------------------------------------------------------- 1 | function testExceptions() { 2 | describe('Exception check', function() { 3 | 4 | it('should handle exceptions correctly', function() { 5 | return JavaPoly.type('Main').then(function(Main) { 6 | return new Promise(function(resolve, reject) { 7 | Main.exceptionThrower().then(function() { 8 | reject(new Error("not expecting the promise to resolve")); 9 | }, function(e) { 10 | expect(e.name).toBe("java.lang.RuntimeException"); 11 | expect(e.message).toBe("Deliberate exception for testing"); 12 | expect(e.causedBy).toNotExist(); 13 | expect(e.printStackTrace).toExist(); 14 | resolve(); 15 | }); 16 | }); 17 | }); 18 | }); 19 | 20 | it('should handle exceptions with null messages correctly', function() { 21 | return JavaPoly.type('Main').then(function(Main) { 22 | return new Promise(function(resolve, reject) { 23 | Main.exceptionThrowerWithNullMessage().then(function() { 24 | reject(new Error("not expecting the promise to resolve")); 25 | }, function(e) { 26 | expect(e.name).toBe("java.lang.RuntimeException"); 27 | expect(e.message).toBe(null); 28 | expect(e.causedBy).toNotExist(); 29 | expect(e.printStackTrace).toExist(); 30 | resolve(); 31 | }); 32 | }); 33 | }); 34 | }); 35 | 36 | it('should handle arithmetic exceptions correctly', function() { 37 | return JavaPoly.type('Main').then(function(Main) { 38 | return new Promise(function(resolve, reject) { 39 | Main.exceptionThrower2(0).then(function(result) { 40 | reject(new Error("not expecting the promise to resolve")); 41 | }, function(e) { 42 | expect(e.name).toBe("java.lang.ArithmeticException"); 43 | expect(e.message).toBe("/ by zero"); 44 | expect(e.causedBy).toNotExist(); 45 | expect(e.printStackTrace).toExist(); 46 | resolve(); 47 | }); 48 | }); 49 | }); 50 | }); 51 | 52 | }); 53 | } 54 | -------------------------------------------------------------------------------- /test/units/jars.js: -------------------------------------------------------------------------------- 1 | function testJarFile() { 2 | describe('Test JAR file', function() { 3 | it('com.javapoly.utils.Math.add', function() { 4 | return JavaPoly.type('com.javapoly.utils.Math').then(function(Math) { 5 | Math.add(10, 20).then(function(result) { 6 | expect(result).toEqual(30); 7 | }); 8 | }); 9 | }); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /test/units/objectWrappers.js: -------------------------------------------------------------------------------- 1 | function testObjectWrappers() { 2 | if (!isWorkerBased) { 3 | // Object wrappers are not supported in web-worker mode 4 | 5 | describe('Object wrappers', function() { 6 | 7 | it('should wrap fields', function() { 8 | return JavaPoly.new('Counter').then(function(counter) { 9 | var t1 = counter.increment(42).then(function() { 10 | return counter.currentValue.then(function(cValue) { 11 | expect(cValue).toEqual(15); 12 | }); 13 | }); 14 | return t1.then(function() { 15 | counter.currentValue = 42; 16 | return counter.isValid().then(function(itIsValid) { 17 | expect(itIsValid).toBe(false); 18 | }); 19 | }); 20 | }); 21 | }); 22 | 23 | it('should automatically unwrap when passed as parameter', function() { 24 | return JavaPoly.new("java.io.File", "/abc").then(function(abcFile) { 25 | return JavaPoly.new("java.io.File", "/xyz").then(function(xyzFile) { 26 | return abcFile.compareTo(xyzFile).then(function(comparison) { 27 | expect(comparison).toBeLessThan(0); 28 | }); 29 | }); 30 | }); 31 | }); 32 | 33 | it('should return positive 64 bit integers that JS can handle', function() { 34 | return JavaPoly.type("com.javapoly.test.LongTest").then(function(myclass) { 35 | return myclass.testPassingPos().then(function(result) { 36 | expect(result).toEqual(9007199254740991); 37 | }); 38 | }); 39 | }); 40 | 41 | it('should not return positive 64 bit integers too big for JS', function() { 42 | return new Promise(function(resolve, reject) { 43 | return JavaPoly.type("com.javapoly.test.LongTest").then(function(myclass) { 44 | return myclass.testFailingPos().then(function() { 45 | reject(new Error('Not expecting promise to resolve')); 46 | }, 47 | function(e) { 48 | expect(e.name).toBe("RangeError"); 49 | expect(e.message).toBe("Unfortunately, JavaScript does not yet support 64 bit integers"); 50 | expect(e.causedBy).toNotExist(); 51 | expect(e.printStackTrace).toExist(); 52 | resolve(); 53 | }); 54 | }); 55 | }); 56 | }); 57 | 58 | it('should return negative 64 bit integers that JS can handle', function() { 59 | return JavaPoly.type("com.javapoly.test.LongTest").then(function(myclass) { 60 | return myclass.testPassingNeg().then(function(result) { 61 | expect(result).toEqual(-9007199254740991); 62 | }); 63 | }); 64 | }); 65 | 66 | it('should not return negative 64 bit integers too big for JS', function() { 67 | return new Promise(function(resolve, reject) { 68 | return JavaPoly.type("com.javapoly.test.LongTest").then(function(myclass) { 69 | return myclass.testFailingNeg().then(function() { 70 | reject(new Error("not expecting the promise to resolve")); 71 | }, 72 | function(e) { 73 | expect(e.name).toBe("RangeError"); 74 | expect(e.message).toBe("Unfortunately, JavaScript does not yet support 64 bit integers"); 75 | expect(e.causedBy).toNotExist(); 76 | expect(e.printStackTrace).toExist(); 77 | resolve(); 78 | }); 79 | }); 80 | }); 81 | }); 82 | 83 | it('should be used for new objects defined with convenience function', function() { 84 | return JavaPoly.new('java.io.File', "/sys/someunlikelyfilenamethatwontexist").then(function(file) { 85 | return file.exists().then(function(itExists) { 86 | expect(itExists).toBe(false); 87 | }); 88 | }); 89 | }); 90 | 91 | it('should be used for new objects', function() { 92 | return JavaPoly.type('java.io.File').then(function(File) { 93 | return new File("/sys/someunlikelyfilenamethatwontexist").then(function(file) { 94 | return file.exists().then(function(itExists) { 95 | expect(itExists).toBe(false); 96 | }); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('should wrap Properties object', function() { 102 | it('get property', function() { 103 | return JavaPoly.type('java.lang.System').then(function(System) { 104 | return System.getProperties().then(function(properties) { 105 | 106 | function checkProperty(key, expectedValue) { 107 | return properties.getProperty(key).then(function(value) { 108 | expect(value).toEqual(expectedValue); 109 | }); 110 | } 111 | 112 | return Promise.all([ 113 | checkProperty("os.arch", "js"), 114 | checkProperty("user.name", "DoppioUser"), 115 | checkProperty("java.io.tmpdir", "/tmp"), 116 | checkProperty("java.vm.vendor", "PLASMA@UMass"), 117 | checkProperty("java.vm.name", "DoppioJVM 32-bit VM"), 118 | ]); 119 | }); 120 | }); 121 | }); 122 | }); 123 | 124 | }); 125 | 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /test/units/proxy.js: -------------------------------------------------------------------------------- 1 | function testProxy() { 2 | var isProxySupported = (typeof Proxy !== 'undefined'); 3 | 4 | describe('Proxy access', function() { 5 | 6 | if (!isProxySupported) { 7 | 8 | it('checks that J is undefined if Proxy is not supported', function() { 9 | expect(typeof J === 'undefined').toBe(true); 10 | }); 11 | 12 | } else { 13 | 14 | describe('java.lang.Integer', function() { 15 | 16 | it('toHexString', function() { 17 | return java.lang.Integer.toHexString(42).then(function(result) { 18 | expect(result).toEqual('2a'); 19 | }); 20 | }); 21 | 22 | it('reverse', function() { 23 | return java.lang.Integer.reverse(42).then(function(result) { 24 | expect(result).toEqual(1409286144); 25 | }); 26 | }); 27 | 28 | it('compare', function() { 29 | return java.lang.Integer.compare(42, 41).then(function(result) { 30 | expect(result).toEqual(1); 31 | }); 32 | }); 33 | 34 | it('parseInt', function() { 35 | return java.lang.Integer.parseInt('42').then(function(result) { 36 | expect(result).toEqual(42); 37 | }); 38 | }); 39 | 40 | }); 41 | 42 | describe('java.lang.Double', function() { 43 | it('toHexString', function() { 44 | return java.lang.Double.toHexString(42).then(function(result) { 45 | expect(result).toEqual('0x1.5p5'); 46 | }); 47 | }); 48 | }); 49 | 50 | describe('classes/Main.class', function() { 51 | it('static test()', function() { 52 | return J.Main.test().then(function(result) { 53 | expect(result).toEqual('test message'); 54 | }); 55 | }) 56 | }); 57 | 58 | describe('jars/javapoly-utils.jar', function() { 59 | it('jar static test()', function() { 60 | return com.javapoly.utils.Test.test().then(function(result) { 61 | expect(result).toEqual('test message in jar'); 62 | }); 63 | }) 64 | }); 65 | 66 | } 67 | }); 68 | 69 | } 70 | -------------------------------------------------------------------------------- /test/units/reference.js: -------------------------------------------------------------------------------- 1 | function hold(References, n) { 2 | if (n < 10) { 3 | return References.hold({n:n}).then(function (){ 4 | return hold(References, n + 1); 5 | }); 6 | } else { 7 | return Promise.resolve(n + 1); 8 | } 9 | } 10 | 11 | function testReference() { 12 | describe('hold and release weak references', function(){ 13 | it('should release references', function(){ 14 | return JavaPoly.type('References').then(function(References) { 15 | return hold(References, 0).then(function() { 16 | return JavaPoly.type('java.lang.Thread').then(function(Thread) { 17 | return Thread.sleep(1000).then(function (){ 18 | return References.release().then(function (){ 19 | return new Promise((resolve) => { 20 | setTimeout(resolve, 2000); 21 | }); 22 | }); 23 | }); 24 | }); 25 | }); 26 | }); 27 | }); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/units/reflect.js: -------------------------------------------------------------------------------- 1 | function testReflect() { 2 | if (!isWorkerBased) { 3 | describe('Reflection', function() { 4 | it('should reflect js object into java', function() { 5 | return JavaPoly.type("EvalTest").then(function(EvalTest) { 6 | var obj = {name1: "xyz", name2: 10}; 7 | var objDeep = {name1: "xyz", name2: 10, name3: {inner: "pqr"}}; 8 | var objFunction = {name1: "xyz", square: function(n) { return n * n}}; 9 | var objArray = {name1: "xyz", primes: [2, 3, 5, 7, 11]}; 10 | 11 | var stringTest = EvalTest.getProperty(obj, "name1").then(function(name1Val) { 12 | expect(name1Val).toBe("xyz"); 13 | }); 14 | var numberTest = EvalTest.getProperty(obj, "name2").then(function(name2Val) { 15 | expect(name2Val).toBe(10); 16 | }); 17 | var deepObjTest = EvalTest.getProperty(objDeep, "name3").then(function(name3Val) { 18 | expect(name3Val.inner).toBe("pqr"); 19 | }); 20 | var functionObjTest = EvalTest.getProperty(objFunction, "square").then(function(square) { 21 | expect(square(5)).toBe(25); 22 | }); 23 | var arrayObjTest = EvalTest.getProperty(objArray, "primes").then(function(primes) { 24 | expect(primes[0]).toBe(2); 25 | expect(primes[4]).toBe(11); 26 | }); 27 | return Promise.all([stringTest, numberTest, deepObjTest, functionObjTest, arrayObjTest]); 28 | }); 29 | }); 30 | 31 | it('should reflect js object from java', function() { 32 | return JavaPoly.type("com.javapoly.Eval").then(function(Eval) { 33 | return Eval.eval('({name1: "xyz", name2: 10, name3: {inner: "music"}})').then(function(result) { 34 | expect(result.name1).toBe("xyz"); 35 | expect(result.name3.inner).toBe("music"); 36 | }); 37 | }); 38 | }); 39 | }); 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /test/units/stringIdAccess.js: -------------------------------------------------------------------------------- 1 | function testStringIdAccess() { 2 | describe('String identifiers access', function() { 3 | 4 | it('checks that Java is defined', function() { 5 | expect(JavaPoly).toExist(); 6 | expect(JavaPoly.type).toExist(); 7 | }); 8 | 9 | if (!isWorkerBased) { 10 | // Doppio not available when using web worker 11 | it('doppio should not be a release build', function() { 12 | JavaPoly.type('java.lang.Integer').then(function() { 13 | expect(Doppio.VM.JVM.isReleaseBuild()).toBe(false); 14 | }); 15 | }); 16 | } 17 | 18 | describe('JavaPoly.type', function() { 19 | 20 | it('should load class', function() { 21 | return JavaPoly.type('java.lang.Integer').then(function(Integer) { 22 | expect(Integer).toExist(); 23 | }); 24 | }); 25 | 26 | describe('java.lang.Integer', function() { 27 | it('instance method should not exist in class wrapper: byteValue', function() { 28 | return JavaPoly.type('java.lang.Integer').then(function(Integer) { 29 | expect(Integer.byteValue).toNotExist(); 30 | }); 31 | }); 32 | 33 | it('toHexString', function() { 34 | return JavaPoly.type('java.lang.Integer').then(function(Integer) { 35 | return Integer.toHexString(42).then(function(result) { 36 | expect(result).toEqual('2a'); 37 | }); 38 | }); 39 | }); 40 | 41 | it('toString', function() { 42 | return JavaPoly.type('java.lang.Integer').then(function(Integer) { 43 | return Integer.toString(42, 16).then(function(result) { 44 | expect(result).toEqual('2a'); 45 | }); 46 | }); 47 | }); 48 | 49 | it('parseInt', function() { 50 | return JavaPoly.type('java.lang.Integer').then(function(Integer) { 51 | return Integer.parseInt('42').then(function(result) { 52 | expect(result).toEqual(42); 53 | }); 54 | }); 55 | }); 56 | }); 57 | 58 | }); 59 | 60 | describe('Math', function() { 61 | it('addExact() should add', function() { 62 | return JavaPoly.type('java.lang.Math').then(function(Math) { 63 | return Math.addExact(3, 7).then(function(result) { 64 | expect(result).toEqual(10); 65 | }); 66 | }); 67 | }); 68 | 69 | it('random() should return a value', function() { 70 | return JavaPoly.type('java.lang.Math').then(function(Math) { 71 | return Math.random().then(function(result) { 72 | expect(result) 73 | .toExist() 74 | .toBeLessThan(1.0) 75 | .toBeGreaterThanOrEqual(0); 76 | }); 77 | }); 78 | }) 79 | 80 | it('final fields should be accessible', function() { 81 | return JavaPoly.type('java.lang.Math').then(function(JMath) { 82 | return JMath.PI.then(function(result) { 83 | expect(result) 84 | .toBeLessThan(4.0) 85 | .toBeGreaterThanOrEqual(3.14); 86 | }); 87 | }); 88 | }) 89 | }); 90 | 91 | describe('System', function() { 92 | it('currentTimeMillis() should return a timestamp', function() { 93 | return JavaPoly.type('java.lang.System').then(function(System) { 94 | return System.currentTimeMillis().then(function(result) { 95 | expect(result) 96 | .toExist() 97 | .toBeGreaterThanOrEqual(0); 98 | }); 99 | }); 100 | }); 101 | }); 102 | 103 | describe('classes/Main.class', function() { 104 | it('instance and private methods should not exist in class wrapper', function() { 105 | return JavaPoly.type('Main').then(function(Main) { 106 | expect(Main.publicInstanceMethod).toNotExist(); 107 | expect(Main.privateMethod).toNotExist(); 108 | expect(Main.protectedMethod).toNotExist(); 109 | expect(Main.privateInstanceMethod).toNotExist(); 110 | expect(Main.protectedInstanceMethod).toNotExist(); 111 | }); 112 | }); 113 | 114 | it('static test()', function() { 115 | return JavaPoly.type('Main').then(function(Main) { 116 | return Main.test().then(function(result) { 117 | expect(result).toEqual('test message'); 118 | }); 119 | }); 120 | }); 121 | 122 | it('function that flips a boolean value', function() { 123 | return JavaPoly.type('Main').then(function(Main) { 124 | var trueCheck = Main.flip(true).then(function(result) { 125 | expect(result).toEqual(false); 126 | }); 127 | var falseCheck = Main.flip(false).then(function(result) { 128 | expect(result).toEqual(true); 129 | }); 130 | return Promise.all([trueCheck, falseCheck]); 131 | }); 132 | }); 133 | 134 | it('function that returns a true value', function() { 135 | return JavaPoly.type('Main').then(function(Main) { 136 | return Main.checkLength("xyz", 3).then(function(result) { 137 | expect(result).toEqual(true); 138 | }); 139 | }); 140 | }); 141 | 142 | it('function that returns a false value', function() { 143 | return JavaPoly.type('Main').then(function(Main) { 144 | return Main.checkLength("xyz", 8).then(function(result) { 145 | expect(result).toEqual(false); 146 | }); 147 | }); 148 | }); 149 | 150 | }); 151 | describe('method signature matching logic', function() { 152 | if (!isWorkerBased) { 153 | it('should print string', function() { 154 | return JavaPoly.type('java.lang.System').then(function(System) { 155 | return System.out.then(function(out) { 156 | out.println("hello javapoly"); 157 | }); 158 | }); 159 | }); 160 | } 161 | 162 | it('should call char static method', function() { 163 | return JavaPoly.type('Overload').then(function(Overload) { 164 | return Overload.staticMethod('a').then(function(result) { 165 | expect(result).toEqual('char:a'); 166 | }); 167 | }); 168 | }); 169 | 170 | it('should call byte static method', function() { 171 | return JavaPoly.type('Overload').then(function(Overload) { 172 | return Overload.staticMethod(42).then(function(result) { 173 | expect(result).toEqual('byte:42'); 174 | }); 175 | }); 176 | }); 177 | 178 | it('should call Float static method', function() { 179 | return JavaPoly.type('Overload').then(function(Overload) { 180 | return Overload.staticMethod(42.5).then(function(result) { 181 | expect(result).toEqual('Float:42.5'); 182 | }); 183 | }); 184 | }); 185 | 186 | if (!isWorkerBased) { 187 | it('should call String method', function() { 188 | return JavaPoly.new('Overload').then(function(obj) { 189 | return obj.method('a').then(function(result) { 190 | expect(result).toEqual('String:a'); 191 | }); 192 | }); 193 | }); 194 | 195 | it('should call Short method', function() { 196 | return JavaPoly.new('Overload').then(function(obj) { 197 | return obj.method(142).then(function(result) { 198 | expect(result).toEqual('Short:142'); 199 | }); 200 | }); 201 | }); 202 | 203 | it('should call Character constructor', function() { 204 | return JavaPoly.new('Overload', 'a').then(function(obj) { 205 | return obj.getText().then(function(result) { 206 | expect(result).toEqual('Character:a'); 207 | }); 208 | }); 209 | }); 210 | 211 | it('should call long constructor', function() { 212 | return JavaPoly.new('Overload', 100000000000001).then(function(obj) { 213 | return obj.getText().then(function(result) { 214 | expect(result).toEqual('long:100000000000001'); 215 | }); 216 | }); 217 | }); 218 | 219 | it('should call Float constructor', function() { 220 | return JavaPoly.new('Overload', 1.5).then(function(obj) { 221 | return obj.getText().then(function(result) { 222 | expect(result).toEqual('Float:1.5'); 223 | }); 224 | }); 225 | }); 226 | 227 | it('should accept null as a parameter', function() { 228 | return JavaPoly.type('java.lang.String').then(function(String) { 229 | return String.valueOf(null).then(function(result) { 230 | expect(result).toEqual('null'); 231 | }); 232 | }); 233 | }); 234 | 235 | it('should accept null as a return value', function() { 236 | return JavaPoly.type('Overload').then(function(Overload) { 237 | return Overload.identityFunction(null).then(function(result) { 238 | expect(result).toEqual(null); 239 | }); 240 | }); 241 | }); 242 | } 243 | }); 244 | }); 245 | } 246 | -------------------------------------------------------------------------------- /test/units/urls.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function readFromIS(is, callback) { 4 | const bytes = []; 5 | return new Promise(function(resolve, reject) { 6 | function handleRead(byte) { 7 | if (byte < 0) { 8 | resolve(bytes); 9 | } else { 10 | bytes.push(byte); 11 | is.read().then(handleRead); 12 | } 13 | } 14 | is.read().then(handleRead); 15 | }); 16 | } 17 | 18 | function utf8ByteArrayToString(bytes) { 19 | const out = []; 20 | let pos = 0, c = 0; 21 | while (pos < bytes.length) { 22 | const c1 = bytes[pos++]; 23 | if (c1 < 128) { 24 | out[c++] = String.fromCharCode(c1); 25 | } else if (c1 > 191 && c1 < 224) { 26 | const c2 = bytes[pos++]; 27 | out[c++] = String.fromCharCode((c1 & 31) << 6 | c2 & 63); 28 | } else if (c1 > 239 && c1 < 365) { 29 | // Surrogate Pair 30 | const c2 = bytes[pos++]; 31 | const c3 = bytes[pos++]; 32 | const c4 = bytes[pos++]; 33 | const u = ((c1 & 7) << 18 | (c2 & 63) << 12 | (c3 & 63) << 6 | c4 & 63) - 0x10000; 34 | out[c++] = String.fromCharCode(0xD800 + (u >> 10)); 35 | out[c++] = String.fromCharCode(0xDC00 + (u & 1023)); 36 | } else { 37 | const c2 = bytes[pos++]; 38 | const c3 = bytes[pos++]; 39 | out[c++] = String.fromCharCode((c1 & 15) << 12 | (c2 & 63) << 6 | c3 & 63); 40 | } 41 | } 42 | return out.join(''); 43 | }; 44 | 45 | 46 | function testUrls() { 47 | if (!isWorkerBased) { 48 | describe('URLs', function() { 49 | before(() => { 50 | return JavaPoly.type("com.javapoly.XHRUrlStreamHandlerFactory").then(function(XHRUrlStreamHandlerFactory) { 51 | return XHRUrlStreamHandlerFactory.register(); 52 | }); 53 | }); 54 | 55 | it('should fetch data', function() { 56 | return JavaPoly.type("java.net.URL").then(function(URL) { 57 | return new URL(window.location.origin + "/simpleResponse.bin").then(function(url) { 58 | return url.openConnection().then(function(urlConnection) { 59 | var headerPromise = urlConnection.getHeaderField('content-type').then(function(contentType) { 60 | expect(contentType).toBe('application/octet-stream'); 61 | }); 62 | var contentPromise = urlConnection.getInputStream().then(function(is) { 63 | return readFromIS(is).then(function(content) { 64 | expect(content[0]).toBe(97); 65 | expect(content[1]).toBe(98); 66 | expect(content[2]).toBe(99); 67 | }); 68 | }); 69 | return Promise.all([headerPromise, contentPromise]); 70 | }); 71 | }); 72 | }); 73 | }); 74 | 75 | it('should set the request method', function() { 76 | return JavaPoly.type("java.net.URL").then(function(URL) { 77 | return new URL(window.location.origin + "/api").then(function(url) { 78 | return url.openConnection().then(function(urlConnection) { 79 | return urlConnection.setRequestMethod("POST").then(function() { 80 | return urlConnection.getInputStream().then(function(is) { 81 | return readFromIS(is).then(function(content) { 82 | const json = JSON.parse(utf8ByteArrayToString(content)); 83 | expect(json.method).toBe("POST"); 84 | }); 85 | }); 86 | }); 87 | }); 88 | }); 89 | }); 90 | }); 91 | 92 | it('should set the request headers', function() { 93 | return JavaPoly.type("java.net.URL").then(function(URL) { 94 | return new URL(window.location.origin + "/api").then(function(url) { 95 | return url.openConnection().then(function(urlConnection) { 96 | return urlConnection.addRequestProperty("xxx-test-property", "test42").then(function() { 97 | return urlConnection.getInputStream().then(function(is) { 98 | return readFromIS(is).then(function(content) { 99 | const json = JSON.parse(utf8ByteArrayToString(content)); 100 | expect(json.headers["xxx-test-property"]).toBe("test42"); 101 | }); 102 | }); 103 | }); 104 | }); 105 | }); 106 | }); 107 | }); 108 | 109 | it('should send data', function() { 110 | return JavaPoly.type("URLConnectionTest").then(function(URLConnectionTest) { 111 | return URLConnectionTest.sendData(window.location.origin + "/api").then(function(is) { 112 | return readFromIS(is).then(function(content) { 113 | const json = JSON.parse(utf8ByteArrayToString(content)); 114 | expect(json["input-count"]).toBe(120); 115 | expect(json["input-md5"]).toBe('b7ba1efc6022e9ed272f00b8831e26e6'); 116 | }); 117 | }); 118 | }); 119 | }); 120 | 121 | }); 122 | } 123 | } 124 | 125 | -------------------------------------------------------------------------------- /test/units/util.js: -------------------------------------------------------------------------------- 1 | function getParams(searchString) { 2 | function cast(str) { 3 | try { 4 | return JSON.parse(str); 5 | } catch (e) { 6 | return str; 7 | } 8 | } 9 | 10 | var result = {}; 11 | if (searchString.startsWith("?")) { 12 | var fields = searchString.substring(1).split("&"); 13 | for (var f of fields) { 14 | var pair = f.split("="); 15 | if (pair.length == 2) { 16 | result[pair[0]] = cast(pair[1]); 17 | } 18 | } 19 | } 20 | return result; 21 | } --------------------------------------------------------------------------------