diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..992d272
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,14 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 4
+indent_style = space
+insert_final_newline = false
+max_line_length = 120
+tab_width = 4
+ij_javascript_space_before_function_left_parenth = false
+
+[{*.json,.eslintrc}]
+indent_size = 2
diff --git a/.eslintrc.json b/.eslintrc.json
new file mode 100644
index 0000000..28c5bfe
--- /dev/null
+++ b/.eslintrc.json
@@ -0,0 +1,41 @@
+{
+ "env": {
+ "commonjs": true,
+ "es2017": true,
+ "node": true
+ },
+ "extends": "eslint:recommended",
+ "parserOptions": {
+ "ecmaVersion": 2018
+ },
+ "rules": {
+ "indent": [
+ "error",
+ 4,
+ {
+ "SwitchCase": 1
+ }
+ ],
+ "linebreak-style": [
+ "error",
+ "unix"
+ ],
+ "quotes": [
+ "error",
+ "double"
+ ],
+ "semi": [
+ "error",
+ "always"
+ ],
+ "no-var": "error",
+ "prefer-const": "error",
+ "curly": "error",
+ "prefer-template": "error",
+ "brace-style": "error",
+ "padded-blocks": [
+ "error",
+ "never"
+ ]
+ }
+}
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
new file mode 100644
index 0000000..f0ae348
--- /dev/null
+++ b/.github/workflows/test.yml
@@ -0,0 +1,26 @@
+name: Java CI
+
+on: [push]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2
+ - name: Set up JDK 1.8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 1.8
+ - name: Cache Maven packages
+ uses: actions/cache@v2
+ with:
+ path: ~/.m2
+ key: ${{ runner.os }}-m2-${{ hashFiles('pom.xml') }}
+ restore-keys: ${{ runner.os }}-m2
+ - name: Build with Maven
+ run: mvn --batch-mode --update-snapshots verify
+ - run: mkdir staging && cp target/*.jar staging
+ - uses: actions/upload-artifact@v2
+ with:
+ name: Package
+ path: staging
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index b473ad9..623a45d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,5 +1,10 @@
+/node
+/out
/node_modules/
/test/*.class
/package-lock.json
/.nyc_output/
/coverage/
+/.idea
+*.iml
+/target
\ No newline at end of file
diff --git a/package.json b/package.json
index 3f7d9c8..14147b6 100644
--- a/package.json
+++ b/package.json
@@ -2,10 +2,13 @@
"name": "java-deserialization",
"version": "0.1.0",
"description": "Parse Java object serialization format using pure JavaScript",
- "main": "src/index.js",
+ "main": "src/main/node/index.js",
"scripts": {
- "test": "nyc --reporter=html --reporter=text-summary mocha test/*.js",
- "gentest": "cd test && javac *.java && java GenerateTestCases > generated.js && npm test"
+ "built": "echo 'build here'",
+ "lint": "echo 'no linting setup yet'",
+ "test": "nyc --reporter=html --reporter=text-summary mocha src/test/node/*.js",
+ "gentest": "java -classpath target/classes io.github.gagern.nodeJavaDeserialization.GenerateTestCases > src/test/node/generated.js && npm test",
+ "package": "echo 'make publishable artifact here'"
},
"repository": {
"type": "git",
@@ -24,11 +27,19 @@
},
"homepage": "https://github.com/gagern/nodeJavaDeserialization#readme",
"devDependencies": {
- "chai": "^4.1.2",
- "mocha": "^4.1.0",
- "nyc": "^11.4.1"
+ "@types/node": "^10.17.55",
+ "chai": "^4.3.4",
+ "eslint": "^7.22.0",
+ "eslint-config-airbnb-base": "^14.2.1",
+ "eslint-plugin-import": "^2.22.1",
+ "mocha": "^8.3.2",
+ "nyc": "^15.1.0",
+ "typescript": "^4.2.3"
},
"dependencies": {
- "long": "^3.2.0"
+ "long": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=18.0.0"
}
}
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..225b7f6
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,125 @@
+
+
+ 4.0.0
+
+
+ io.github.gagern
+ 1.0.0-SNAPSHOT
+ nodeJavaDeserialization
+ jar
+
+
+ 1.13.4
+ v20.12.1
+ 10.5.0
+
+
+ 1.8
+ 1.8
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.13.0
+
+ 1.8
+ 1.8
+
+
+
+ com.github.eirslett
+ frontend-maven-plugin
+ ${frontend-maven-plugin.version}
+
+
+ install node and npm
+
+ install-node-and-npm
+
+
+ ${node.version}
+ ${npm.version}
+
+
+
+ npm install
+
+ npm
+
+ generate-resources
+
+ install
+
+
+
+ npm lint
+
+ npm
+
+ generate-resources
+
+ run lint
+
+
+
+ npm gentest
+
+ npm
+
+ test-compile
+
+ run gentest
+
+
+
+ npm test
+
+ npm
+
+ test
+
+ run test
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ npm package
+
+ npm
+
+ package
+
+ run package
+
+
+
+
+
+
+
diff --git a/test/GenerateTestCases.java b/src/main/java/io/github/gagern/nodeJavaDeserialization/GenerateTestCases.java
similarity index 92%
rename from test/GenerateTestCases.java
rename to src/main/java/io/github/gagern/nodeJavaDeserialization/GenerateTestCases.java
index 2efc662..e4af550 100644
--- a/test/GenerateTestCases.java
+++ b/src/main/java/io/github/gagern/nodeJavaDeserialization/GenerateTestCases.java
@@ -1,3 +1,5 @@
+package io.github.gagern.nodeJavaDeserialization;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
@@ -10,12 +12,16 @@ class GenerateTestCases {
public static void main(String[] args) throws Exception {
System.out.print
- ("'use strict';\n" +
+ ("//This is a generated file from GeneratedTestCases.java\n" +
+ "'use strict';\n" +
"\n" +
"const chai = require('chai');\n" +
"const expect = chai.expect;\n" +
"const zlib = require('zlib');\n" +
- "const javaDeserialization = require('../');\n" +
+ "const javaDeserialization = require('../../main/node');\n" +
+ "\n" +
+ "// Register a classdata parser for the io.github.gagern.nodeJavaDeserialization.CompletelyCustomFormat test.\n" +
+ "javaDeserialization.registerClassDataParser('io.github.gagern.nodeJavaDeserialization.CompletelyCustomFormat', '0000000000000001', cls => ({}));\n" +
"\n" +
"function testCase(b64data, checks) {\n" +
" return function() {\n" +
diff --git a/test/SerializationTestCase.java b/src/main/java/io/github/gagern/nodeJavaDeserialization/SerializationTestCase.java
similarity index 85%
rename from test/SerializationTestCase.java
rename to src/main/java/io/github/gagern/nodeJavaDeserialization/SerializationTestCase.java
index 2991509..09efd38 100644
--- a/test/SerializationTestCase.java
+++ b/src/main/java/io/github/gagern/nodeJavaDeserialization/SerializationTestCase.java
@@ -1,3 +1,5 @@
+package io.github.gagern.nodeJavaDeserialization;
+
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/test/TestCases.java b/src/main/java/io/github/gagern/nodeJavaDeserialization/TestCases.java
similarity index 84%
rename from test/TestCases.java
rename to src/main/java/io/github/gagern/nodeJavaDeserialization/TestCases.java
index c230baa..0a437ff 100644
--- a/test/TestCases.java
+++ b/src/main/java/io/github/gagern/nodeJavaDeserialization/TestCases.java
@@ -1,3 +1,5 @@
+package io.github.gagern.nodeJavaDeserialization;
+
import java.io.Serializable;
import java.util.HashMap;
@@ -41,6 +43,7 @@ class CustomFormat implements Serializable {
private static final long serialVersionUID = 0x1;
private int foo = 12345;
+ private String bar = "Hello, World!";
private void writeObject(java.io.ObjectOutputStream out)
throws java.io.IOException
@@ -59,6 +62,32 @@ private void readObjectNoData()
throws java.io.ObjectStreamException { }
}
+class CompletelyCustomFormat implements Serializable {
+ private static final long serialVersionUID = 0x1;
+
+ // These are here to cause an error if there is an attempt to parse
+ // the defaultWriteObject format.
+ private int foo;
+ private String bar;
+
+ private void writeObject(java.io.ObjectOutputStream out)
+ throws java.io.IOException
+ {
+ out.writeObject("Hello, World!");
+ out.writeInt(12345);
+
+ // These numbers should result in "test" visible in the base64 output ;)
+ byte[] data = { -75, -21, 45, 0, -75, -21, 45, 0, -75, -21, 45 };
+ out.write(data);
+ }
+
+ private void readObject(java.io.ObjectInputStream in)
+ throws java.io.IOException, ClassNotFoundException { }
+
+ private void readObjectNoData()
+ throws java.io.ObjectStreamException { }
+}
+
class External implements Serializable, java.io.Externalizable {
// These numbers should result in "test" visible in the base64 output ;)
@@ -169,23 +198,23 @@ public void nulls() throws Exception {
@SerializationTestCase public void inheritedField() throws Exception {
writeObject(new DerivedClassWithAnotherField());
- checkStrictEqual("itm.class.name", "'DerivedClassWithAnotherField'");
- checkStrictEqual("itm.class.super.name", "'BaseClassWithField'");
+ checkStrictEqual("itm.class.name", "'io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'");
+ checkStrictEqual("itm.class.super.name", "'io.github.gagern.nodeJavaDeserialization.BaseClassWithField'");
checkStrictEqual("itm.class.super.super", "null");
- checkStrictEqual("itm.extends.DerivedClassWithAnotherField.bar", "234");
- checkStrictEqual("itm.extends.DerivedClassWithAnotherField.foo", "undefined");
- checkStrictEqual("itm.extends.BaseClassWithField.foo", "123");
+ checkStrictEqual("itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].bar", "234");
+ checkStrictEqual("itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].foo", "undefined");
+ checkStrictEqual("itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo", "123");
checkStrictEqual("itm.bar", "234");
checkStrictEqual("itm.foo", "123");
}
@SerializationTestCase public void duplicateField() throws Exception {
writeObject(new DerivedClassWithSameField());
- checkStrictEqual("itm.class.name", "'DerivedClassWithSameField'");
- checkStrictEqual("itm.class.super.name", "'BaseClassWithField'");
+ checkStrictEqual("itm.class.name", "'io.github.gagern.nodeJavaDeserialization.DerivedClassWithSameField'");
+ checkStrictEqual("itm.class.super.name", "'io.github.gagern.nodeJavaDeserialization.BaseClassWithField'");
checkStrictEqual("itm.class.super.super", "null");
- checkStrictEqual("itm.extends.DerivedClassWithSameField.foo", "345");
- checkStrictEqual("itm.extends.BaseClassWithField.foo", "123");
+ checkStrictEqual("itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithSameField'].foo", "345");
+ checkStrictEqual("itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo", "123");
checkStrictEqual("itm.foo", "345");
}
@@ -233,7 +262,7 @@ public void enums() throws Exception {
checkInstanceof("one", "String");
checkLooseEqual("one", "'ONE'");
checkNotStrictEqual("one", "'ONE'");
- checkStrictEqual("one.class.name", "'SomeEnum'");
+ checkStrictEqual("one.class.name", "'io.github.gagern.nodeJavaDeserialization.SomeEnum'");
checkThat("one.class.isEnum");
checkStrictEqual("one.class.super.name", "'java.lang.Enum'");
checkStrictEqual("one.class.super.super", "null");
@@ -264,6 +293,18 @@ public void exception() throws Exception {
"'b5eb2d00b5eb2d00b5eb2d'");
checkStrictEqual("itm['@'][1]", "'and more'");
checkStrictEqual("itm.foo", "12345");
+ checkStrictEqual("itm.bar", "'Hello, World!'");
+ }
+
+ @SerializationTestCase public void completelyCustomFormat() throws Exception {
+ writeObject(new CompletelyCustomFormat());
+ checkArray("itm['@']");
+ checkLength("itm['@']", 2);
+ checkStrictEqual("itm['@'][0]", "'Hello, World!'");
+ // This is the primitive data lump (TC_BLOCKDATA).
+ checkThat("Buffer.isBuffer(itm['@'][1])");
+ checkStrictEqual("itm['@'][1].toString('hex')",
+ "'00003039b5eb2d00b5eb2d00b5eb2d'");
}
@SerializationTestCase public void externalizable() throws Exception {
@@ -371,7 +412,7 @@ public void enumMap() throws Exception {
checkStrictEqual("itm.obj.THREE", "'baz'");
checkStrictEqual("itm.obj.ONE.value", "123");
checkKeys("itm.obj", "'ONE', 'THREE'");
- checkStrictEqual("itm.keyType.name", "'SomeEnum'");
+ checkStrictEqual("itm.keyType.name", "'io.github.gagern.nodeJavaDeserialization.SomeEnum'");
checkThat("itm.keyType.isEnum");
checkInstanceof("itm.map", "Map");
checkStrictEqual("itm.map.get(three)", "'baz'");
@@ -411,5 +452,4 @@ public void hashSet() throws Exception {
checkStrictEqual("itm.set.size", "2");
checkThat("itm.set.has('foo')");
}
-
}
diff --git a/src/index.js b/src/main/node/index.js
similarity index 84%
rename from src/index.js
rename to src/main/node/index.js
index cf53263..f9d6d5d 100644
--- a/src/index.js
+++ b/src/main/node/index.js
@@ -1,16 +1,16 @@
/*
* Copyright (c) 2015,2018 Martin von Gagern
- *
+ *
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
- *
+ *
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
- *
+ *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -22,10 +22,13 @@
"use strict";
-var Parser = require("./parser.js");
+const Parser = require("./parser.js");
require("./util.js");
module.exports.parse = function parse(buf) {
- var parser = new Parser(buf);
+ const parser = new Parser(buf);
return parser.contents;
-}
+};
+
+module.exports.registerClassDataParser = Parser.registerClassDataParser;
+module.exports.registerPostProcessor = Parser.registerPostProcessor;
diff --git a/src/main/node/parser.js b/src/main/node/parser.js
new file mode 100644
index 0000000..c7f2628
--- /dev/null
+++ b/src/main/node/parser.js
@@ -0,0 +1,443 @@
+/**
+ * Copyright (c) 2015,2018 Martin von Gagern
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+// See http://docs.oracle.com/javase/7/docs/platform/serialization/spec/protocol.html for reference
+
+"use strict";
+
+const assert = require("assert");
+const Long = require("long");
+
+const names = [
+ "Null", "Reference", "ClassDesc", "Object", "String", "Array", "Class", "BlockData", "EndBlockData",
+ "Reset", "BlockDataLong", "Exception", "LongString", "ProxyClassDesc", "Enum",
+];
+
+const endBlock = {};
+
+/** @type {Object.} */
+const classDataParsers = {};
+
+/** @type {Object.} */
+const classPostProcessors = {};
+
+/** @type {Object.} */
+const typeHandlers = {
+ "Null": function() {
+ return null;
+ },
+ "Reference": function() {
+ return this.handles[this.readInt32()];
+ },
+ "ClassDesc": function() {
+ const res = {};
+ res.name = this.utf();
+ res.serialVersionUID = this.readHex(8);
+ this.newHandle(res);
+ res.flags = this.readUInt8();
+ res.isEnum = !!(res.flags & 0x10);
+ const count = this.readUInt16();
+ res.fields = [];
+ for (let i = 0; i < count; ++i) {
+ res.fields.push(this.fieldDesc());
+ }
+ res.annotations = this.annotations();
+ res.super = this.classDesc();
+ return res;
+ },
+ "Object": function() {
+ const res = Object.defineProperties({}, {
+ "class": {
+ configurable: true,
+ value: this.classDesc(),
+ },
+ "extends": {
+ configurable: true,
+ value: {},
+ },
+ });
+ this.newHandle(res);
+ this.recursiveClassData(res.class, res);
+ return res;
+ },
+ "String": function() {
+ return this.newHandle(this.utf());
+ },
+ "Array": function() {
+ const classDesc = this.classDesc();
+ const res = Object.defineProperties([], {
+ "class": {
+ configurable: true,
+ value: classDesc,
+ },
+ "extends": {
+ configurable: true,
+ value: {},
+ },
+ });
+ this.newHandle(res);
+ const len = this.readInt32();
+ const handler = this.primHandler(classDesc.name.charAt(1));
+ res.length = len;
+ for (let i = 0; i < len; ++i) {
+ res[i] = handler.call(this);
+ }
+ return res;
+ },
+ "Class": function() {
+ return this.newHandle(this.classDesc());
+ },
+ "BlockData": function() {
+ const len = this.readUInt8();
+ const res = this.buf.slice(this.pos, this.pos + len);
+ this.pos += len;
+ return res;
+ },
+ "EndBlockData": function() {
+ return endBlock;
+ },
+ "BlockDataLong": function() {
+ const len = this.readUInt32();
+ const res = this.buf.slice(this.pos, this.pos + len);
+ this.pos += len;
+ return res;
+ },
+ "LongString": function() {
+ return this.newHandle(this.utfLong());
+ },
+ "Enum": function() {
+ const clazz = this.classDesc();
+ const deferredHandle = this.newDeferredHandle();
+ const constant = this.content();
+ // We need to use the object wrapper here to define the additional properties.
+ // noinspection JSPrimitiveTypeWrapperUsage
+ const obj = new String(constant); // eslint-disable-line no-new-wrappers
+ const res = Object.defineProperties(obj, {
+ "class": {
+ configurable: true,
+ value: clazz,
+ },
+ "extends": {
+ configurable: true,
+ value: {},
+ },
+ });
+ deferredHandle(res);
+ return res;
+ },
+};
+
+/** @type {Object.} */
+const primHandlers = {
+ "B": function() {
+ return this.readInt8();
+ },
+ "C": function() {
+ return String.fromCharCode(this.readUInt16());
+ },
+ "D": function() {
+ return this.buf.readDoubleBE(this.step(8));
+ },
+ "F": function() {
+ return this.buf.readFloatBE(this.step(4));
+ },
+ "I": function() {
+ return this.readInt32();
+ },
+ "J": function() {
+ const high = this.readUInt32();
+ const low = this.readUInt32();
+ return Long.fromBits(low, high);
+ },
+ "S": function() {
+ return this.readInt16();
+ },
+ "Z": function() {
+ return !!this.readInt8();
+ },
+ "L": function() {
+ return this.content();
+ },
+ "[": function() {
+ return this.content();
+ },
+};
+
+class Parser {
+ /**
+ * @param {Buffer} buf
+ */
+ constructor(buf) {
+ this.buf = buf;
+ this.pos = 0;
+ this.nextHandle = 0x7e0000;
+ this.handles = [];
+ this.contents = [];
+
+ this.magic();
+ this.version();
+
+ while (this.pos < this.buf.length) {
+ this.contents.push(this.content());
+ }
+ }
+
+ /**
+ * @param {string} className
+ * @param {string} serialVersionUID
+ * @param {function(this:Parser, Object): Object} parser
+ */
+ static registerClassDataParser(className, serialVersionUID, parser) {
+ assert.strictEqual(serialVersionUID.length, 16,
+ "serialVersionUID must be 16 hex digits");
+
+ classDataParsers[`${className}@${serialVersionUID}`] = parser;
+ }
+
+ /**
+ * @param {string} className
+ * @param {string} serialVersionUID
+ * @param {function(this:Parser, Object, Object, Array): Object} parser
+ */
+ static registerPostProcessor(className, serialVersionUID, parser) {
+ assert.strictEqual(serialVersionUID.length, 16,
+ "serialVersionUID must be 16 hex digits");
+
+ classPostProcessors[`${className}@${serialVersionUID}`] = parser;
+ }
+
+ step(len) {
+ const pos = this.pos;
+ this.pos += len;
+
+ if (this.pos > this.buf.length) {
+ const err = new Error("Premature end of input");
+ err.buf = this.buf;
+ err.pos = this.pos;
+ throw err;
+ }
+
+ return pos;
+ }
+
+ chunk(len, encoding) {
+ const pos = this.step(len);
+ return this.buf.toString(encoding, pos, this.pos);
+ }
+
+ readUInt8() {
+ return this.buf.readUInt8(this.step(1));
+ }
+
+ readInt8() {
+ return this.buf.readInt8(this.step(1));
+ }
+
+ readUInt16() {
+ return this.buf.readUInt16BE(this.step(2));
+ }
+
+ readInt16() {
+ return this.buf.readInt16BE(this.step(2));
+ }
+
+ readUInt32() {
+ return this.buf.readUInt32BE(this.step(4));
+ }
+
+ readInt32() {
+ return this.buf.readInt32BE(this.step(4));
+ }
+
+ readHex(len) {
+ return this.chunk(len, "hex");
+ }
+
+ utf() {
+ return this.chunk(this.readUInt16(), "utf8");
+ }
+
+ utfLong() {
+ if (this.readUInt32() !== 0) {
+ throw new Error("Can't handle more than 2^32 bytes in a string");
+ }
+
+ return this.chunk(this.readUInt32(), "utf8");
+ }
+
+ magic() {
+ this.magic = this.readUInt16();
+ if (this.magic !== 0xaced) {
+ throw Error("STREAM_MAGIC not found");
+ }
+ }
+
+ version() {
+ this.version = this.readUInt16();
+ if (this.version !== 5) {
+ throw Error("Only understand protocol version 5");
+ }
+ }
+
+ content(allowed) {
+ const tc = this.readUInt8() - 0x70;
+ if (tc < 0 || tc > names.length) {
+ throw Error(`Don't know about type 0x${(tc + 0x70).toString(16)}`);
+ }
+
+ const name = names[tc];
+ if (allowed && allowed.indexOf(name) === -1) {
+ throw Error(`${name} not allowed here`);
+ }
+
+ const handler = typeHandlers[name];
+ if (!handler) {
+ throw Error(`Don't know how to handle ${name}`);
+ }
+
+ return handler.call(this);
+ }
+
+ annotations(allowed) {
+ const annotations = [];
+ for (;;) {
+ const annotation = this.content(allowed);
+ if (annotation === endBlock) {
+ break;
+ }
+
+ annotations.push(annotation);
+ }
+
+ return annotations;
+ }
+
+ classDesc() {
+ return this.content(["ClassDesc", "ProxyClassDesc", "Null", "Reference"]);
+ }
+
+ fieldDesc() {
+ const res = {};
+ res.type = String.fromCharCode(this.readUInt8());
+ res.name = this.utf();
+
+ if ("[L".indexOf(res.type) !== -1) {
+ res.className = this.content();
+ }
+
+ return res;
+ }
+
+ recursiveClassData(cls, obj) {
+ if (cls.super) {
+ this.recursiveClassData(cls.super, obj);
+ }
+
+ const fields = obj.extends[cls.name] = this.classdata(cls, obj);
+ for (const name in fields) {
+ obj[name] = fields[name];
+ }
+ }
+
+ classdata(cls) {
+ // For bcompat, this defaults to the values handler - same as without a write method.
+ const classDataParser = classDataParsers[`${cls.name}@${cls.serialVersionUID}`] || this.values;
+ const postProcessor = classPostProcessors[`${cls.name}@${cls.serialVersionUID}`];
+
+ switch (cls.flags & 0x0f) {
+ case 0x02: // SC_SERIALIZABLE without SC_WRITE_METHOD
+ return this.values(cls);
+ case 0x03: { // SC_SERIALIZABLE with SC_WRITE_METHOD
+ let res = classDataParser.call(this, cls);
+ const data = res["@"] = this.annotations();
+ if (postProcessor) {
+ res = postProcessor.call(this, cls, res, data);
+ }
+
+ return res;
+ }
+ case 0x04: // SC_EXTERNALIZABLE without SC_BLOCKDATA
+ throw Error("Can't parse version 1 external content");
+ case 0x0c: // SC_EXTERNALIZABLE with SC_BLOCKDATA
+ return { "@": this.annotations() };
+ default:
+ throw Error(`Don't know how to deserialize class with flags 0x${cls.flags.toString(16)}`);
+ }
+ }
+
+ /**
+ * @param {string} type
+ * @return {function(this:Parser): *}
+ */
+ primHandler(type) {
+ const handler = primHandlers[type];
+ if (!handler) {
+ throw Error(`Don't know how to read field of type '${type}'`);
+ }
+
+ return handler;
+ }
+
+ /**
+ * @param cls
+ * @return {Object}
+ */
+ values(cls) {
+ const vals = {};
+
+ const fields = cls.fields;
+ for (let i = 0; i < fields.length; ++i) {
+ const field = fields[i];
+ const handler = this.primHandler(field.type);
+ vals[field.name] = handler.call(this);
+ }
+
+ return vals;
+ }
+
+ /**
+ * @template T
+ * @param {T} obj
+ * @return {T}
+ */
+ newHandle(obj) {
+ this.handles[this.nextHandle++] = obj;
+ return obj;
+ }
+
+ /**
+ * @return {function(*): void}
+ */
+ newDeferredHandle() {
+ const idx = this.nextHandle++;
+ const handles = this.handles;
+ handles[idx] = null;
+ return function(obj) {
+ handles[idx] = obj;
+ };
+ }
+}
+
+// Backwards compat shim.
+Parser.register = Parser.registerPostProcessor;
+
+module.exports = Parser;
diff --git a/src/util.js b/src/main/node/util.js
similarity index 88%
rename from src/util.js
rename to src/main/node/util.js
index e393c77..e96d890 100644
--- a/src/util.js
+++ b/src/main/node/util.js
@@ -36,8 +36,8 @@ function mapParser(cls, fields, data) {
var map = new Map();
var obj = {};
for (var i = 0; i < size; ++i) {
- var key = data[2*i + 1];
- var value = data[2*i + 2];
+ var key = data[(2 * i) + 1];
+ var value = data[(2 * i) + 2];
map.set(key, value);
if (typeof key === "string") {
obj[key] = value;
@@ -53,8 +53,8 @@ function enumMapParser(cls, fields, data) {
var map = new Map();
var obj = {};
for (var i = 0; i < size; ++i) {
- var key = data[2*i + 1];
- var value = data[2*i + 2];
+ var key = data[(2 * i) + 1];
+ var value = data[(2 * i) + 2];
map.set(key, value);
obj[key] = value;
}
@@ -69,14 +69,14 @@ function hashSetParser(cls, fields, data) {
var size = data[0].readInt32BE(8);
if (data.length !== size + 1)
throw new Error("Expected " + size + " elements " +
- "but parsed " + (data.length - 1));
+ "but parsed " + (data.length - 1));
fields.set = new Set(data.slice(1));
return fields;
}
-Parser.register("java.util.ArrayList", "7881d21d99c7619d", listParser);
+Parser.register("java.util.ArrayList", "7881d21d99c7619d", listParser);
Parser.register("java.util.ArrayDeque", "207cda2e240da08b", listParser);
-Parser.register("java.util.Hashtable", "13bb0f25214ae4b8", mapParser);
+Parser.register("java.util.Hashtable", "13bb0f25214ae4b8", mapParser);
Parser.register("java.util.HashMap", "0507dac1c31660d1", mapParser);
Parser.register("java.util.EnumMap", "065d7df7be907ca1", enumMapParser);
Parser.register("java.util.HashSet", "ba44859596b8b734", hashSetParser);
diff --git a/src/parser.js b/src/parser.js
deleted file mode 100644
index 01482e4..0000000
--- a/src/parser.js
+++ /dev/null
@@ -1,373 +0,0 @@
-/*
- * Copyright (c) 2015,2018 Martin von Gagern
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be
- * included in all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- */
-
-// See http://docs.oracle.com/javase/7/docs/platform/serialization/spec/protocol.html for reference
-
-"use strict";
-
-var assert = require("assert");
-var Long = require("long");
-
-var names = [
- "Null", "Reference", "ClassDesc", "Object", "String", "Array", "Class", "BlockData", "EndBlockData",
- "Reset", "BlockDataLong", "Exception", "LongString", "ProxyClassDesc", "Enum"
-];
-
-var endBlock = {};
-
-function Parser(buf) {
- this.buf = buf;
- this.pos = 0;
- this.nextHandle = 0x7e0000;
- this.handles = [];
- this.contents = [];
- this.magic();
- this.version();
- while (this.pos < this.buf.length) {
- this.contents.push(this.content());
- }
-}
-
-Parser.prototype.step = function(len) {
- var pos = this.pos;
- this.pos += len;
- if (this.pos > this.buf.length) {
- var err = new Error("Premature end of input");
- err.buf = this.buf;
- err.pos = this.pos;
- throw err;
- }
- return pos;
-}
-
-Parser.prototype.chunk = function(len, encoding) {
- var pos = this.step(len);
- return this.buf.toString(encoding, pos, this.pos);
-}
-
-Parser.prototype.readUInt8 = function() {
- return this.buf.readUInt8(this.step(1));
-}
-
-Parser.prototype.readInt8 = function() {
- return this.buf.readInt8(this.step(1));
-}
-
-Parser.prototype.readUInt16 = function() {
- return this.buf.readUInt16BE(this.step(2));
-}
-
-Parser.prototype.readInt16 = function() {
- return this.buf.readInt16BE(this.step(2));
-}
-
-Parser.prototype.readUInt32 = function() {
- return this.buf.readUInt32BE(this.step(4));
-}
-
-Parser.prototype.readInt32 = function() {
- return this.buf.readInt32BE(this.step(4));
-}
-
-Parser.prototype.readHex = function(len) {
- return this.chunk(len, "hex");
-}
-
-Parser.prototype.utf = function() {
- return this.chunk(this.readUInt16(), "utf8");
-}
-
-Parser.prototype.utfLong = function() {
- if (this.readUInt32() !== 0)
- throw new Error("Can't handle more than 2^32 bytes in a string");
- return this.chunk(this.readUInt32(), "utf8");
-}
-
-Parser.prototype.magic = function() {
- this.magic = this.readUInt16();
- if (this.magic !== 0xaced)
- throw Error("STREAM_MAGIC not found");
-}
-
-Parser.prototype.version = function() {
- this.version = this.readUInt16();
- if (this.version !== 5)
- throw Error("Only understand protocol version 5");
-}
-
-Parser.prototype.content = function(allowed) {
- var tc = this.readUInt8() - 0x70;
- if (tc < 0 || tc > names.length)
- throw Error("Don't know about type 0x" + (tc + 0x70).toString(16));
- var name = names[tc];
- if (allowed && allowed.indexOf(name) === -1)
- throw Error(name + " not allowed here");
- var handler = this["parse" + name];
- if (!handler)
- throw Error("Don't know how to handle " + name);
- var elt = handler.call(this);
- return elt;
-}
-
-Parser.prototype.annotations = function(allowed) {
- var annotations = [];
- while (true) {
- var annotation = this.content(allowed);
- if (annotation === endBlock)
- break;
- annotations.push(annotation);
- }
- return annotations;
-}
-
-Parser.prototype.classDesc = function() {
- return this.content(["ClassDesc", "ProxyClassDesc", "Null", "Reference"]);
-}
-
-Parser.prototype.parseClassDesc = function() {
- var res = {};
- res.name = this.utf();
- res.serialVersionUID = this.readHex(8);
- this.newHandle(res);
- res.flags = this.readUInt8();
- res.isEnum = !!(res.flags & 0x10);
- var count = this.readUInt16();
- res.fields = [];
- for (var i = 0; i < count; ++i)
- res.fields.push(this.fieldDesc());
- res.annotations = this.annotations();
- res.super = this.classDesc();
- return res;
-}
-
-Parser.prototype.fieldDesc = function() {
- var res = {};
- res.type = String.fromCharCode(this.readUInt8());
- res.name = this.utf();
- if ("[L".indexOf(res.type) !== -1)
- res.className = this.content();
- return res;
-}
-
-Parser.prototype.parseClass = function() {
- return this.newHandle(this.classDesc());
-}
-
-Parser.prototype.parseObject = function() {
- var res = Object.defineProperties({}, {
- "class": {
- configurable: true,
- value: this.classDesc()
- },
- "extends": {
- configurable: true,
- value: {}
- }
- });
- this.newHandle(res);
- this.recursiveClassData(res.class, res);
- return res;
-}
-
-Parser.prototype.recursiveClassData = function(cls, obj) {
- if (cls.super)
- this.recursiveClassData(cls.super, obj);
- var fields = obj.extends[cls.name] = this.classdata(cls, obj);
- for (var name in fields)
- obj[name] = fields[name];
-}
-
-Parser.prototype.classdata = function(cls) {
- var res, data;
- var postproc = this[cls.name + "@" + cls.serialVersionUID];
- switch (cls.flags & 0x0f) {
- case 0x02: // SC_SERIALIZABLE without SC_WRITE_METHOD
- return this.values(cls);
- case 0x03: // SC_SERIALIZABLE with SC_WRITE_METHOD
- res = this.values(cls);
- data = res["@"] = this.annotations();
- if (postproc)
- res = postproc.call(this, cls, res, data);
- return res;
- case 0x04: // SC_EXTERNALIZABLE without SC_BLOCKDATA
- throw Error("Can't parse version 1 external content");
- case 0x0c: // SC_EXTERNALIZABLE with SC_BLOCKDATA
- return {"@": this.annotations()};
- default:
- throw Error("Don't know how to deserialize class with flags 0x" + cls.flags.toString(16));
- }
-}
-
-Parser.prototype.parseArray = function() {
- var classDesc = this.classDesc();
- var res = Object.defineProperties([], {
- "class": {
- configurable: true,
- value: classDesc
- },
- "extends": {
- configurable: true,
- value: {}
- }
- });
- this.newHandle(res);
- var len = this.readInt32();
- var handler = this.primHandler(classDesc.name.charAt(1));
- res.length = len;
- for (var i = 0; i < len; ++i)
- res[i] = handler.call(this);
- return res;
-}
-
-Parser.prototype.parseEnum = function() {
- var clazz = this.classDesc();
- var deferredHandle = this.newDeferredHandle();
- var constant = this.content();
- var res = Object.defineProperties(new String(constant), {
- "class": {
- configurable: true,
- value: clazz
- },
- "extends": {
- configurable: true,
- value: {}
- }
- });
- deferredHandle(res);
- return res;
-}
-
-Parser.prototype.parseBlockData = function() {
- var len = this.readUInt8();
- var res = this.buf.slice(this.pos, this.pos + len);
- this.pos += len;
- return res;
-}
-
-Parser.prototype.parseBlockDataLong = function() {
- var len = this.readUInt32();
- var res = this.buf.slice(this.pos, this.pos + len);
- this.pos += len;
- return res;
-}
-
-Parser.prototype.parseString = function() {
- return this.newHandle(this.utf());
-}
-
-Parser.prototype.parseLongString = function() {
- return this.newHandle(this.utfLong());
-}
-
-Parser.prototype.primHandler = function(type) {
- var handler = this["prim" + type];
- if (!handler)
- throw Error("Don't know how to read field of type '" + type + "'");
- return handler;
-}
-
-Parser.prototype.values = function(cls) {
- var vals = {};
- var fields = cls.fields;
- for (var i = 0; i < fields.length; ++i) {
- var field = fields[i];
- var handler = this.primHandler(field.type);
- vals[field.name] = handler.call(this);
- }
- return vals;
-}
-
-Parser.prototype.newHandle = function(obj) {
- this.handles[this.nextHandle++] = obj;
- return obj;
-}
-
-Parser.prototype.newDeferredHandle = function() {
- var idx = this.nextHandle++;
- var handles = this.handles;
- handles[idx] = null;
- return function(obj) {
- handles[idx] = obj;
- };
-}
-
-Parser.prototype.parseReference = function() {
- return this.handles[this.readInt32()];
-}
-
-Parser.prototype.parseNull = function() {
- return null;
-}
-
-Parser.prototype.parseEndBlockData = function() {
- return endBlock;
-}
-
-Parser.prototype.primB = function() {
- return this.readInt8();
-}
-
-Parser.prototype.primC = function() {
- return String.fromCharCode(this.readUInt16());
-}
-
-Parser.prototype.primD = function() {
- return this.buf.readDoubleBE(this.step(8));
-}
-
-Parser.prototype.primF = function() {
- return this.buf.readFloatBE(this.step(4));
-}
-
-Parser.prototype.primI = function() {
- return this.readInt32();
-}
-
-Parser.prototype.primJ = function() {
- var high = this.readUInt32();
- var low = this.readUInt32();
- return Long.fromBits(low, high);
-}
-
-Parser.prototype.primS = function() {
- return this.readInt16();
-}
-
-Parser.prototype.primZ = function() {
- return !!this.readInt8();
-}
-
-Parser.prototype.primL = function() {
- return this.content();
-}
-
-Parser.prototype["prim["] = function() {
- return this.content();
-}
-
-Parser.register = function(className, serialVersionUID, parser) {
- assert.strictEqual(serialVersionUID.length, 16,
- "serialVersionUID must be 16 hex digits");
- Parser.prototype[className + "@" + serialVersionUID] = parser;
-}
-
-module.exports = Parser;
diff --git a/test/failures.js b/src/test/node/failures.js
similarity index 98%
rename from test/failures.js
rename to src/test/node/failures.js
index 73a52b2..d9f3a2d 100644
--- a/test/failures.js
+++ b/src/test/node/failures.js
@@ -1,7 +1,7 @@
"use strict";
const expect = require('chai').expect;
-const javaDeserialization = require('../');
+const javaDeserialization = require('../../main/node');
const STREAM_MAGIC = "aced";
const STREAM_VERSION = "0005";
@@ -147,17 +147,17 @@ describe("Failure scenarios", function() {
expect(parsing(template1({flags: 0})))
.to.throw("Don't know how to deserialize class with flags 0x0");
});
-
+
it("version 1 external", function() {
expect(parsing(template1({flags: SC_EXTERNALIZABLE})))
.to.throw("Can't parse version 1 external content");
});
-
+
it("unknown primitive", function() {
expect(parsing(template1({fieldType: "Q"})))
.to.throw("Don't know how to read field of type 'Q'");
});
-
+
it("bad classDesc", function() {
expect(parsing(template1({classDesc: TC_OBJECT})))
.to.throw("Object not allowed here");
diff --git a/src/test/node/generated.js b/src/test/node/generated.js
new file mode 100644
index 0000000..e1fd721
--- /dev/null
+++ b/src/test/node/generated.js
@@ -0,0 +1,327 @@
+//This is a generated file from GeneratedTestCases.java
+'use strict';
+
+const chai = require('chai');
+const expect = chai.expect;
+const zlib = require('zlib');
+const javaDeserialization = require('../../main/node');
+
+// Register a classdata parser for the io.github.gagern.nodeJavaDeserialization.CompletelyCustomFormat test.
+javaDeserialization.registerClassDataParser('io.github.gagern.nodeJavaDeserialization.CompletelyCustomFormat', '0000000000000001', cls => ({}));
+
+function testCase(b64data, checks) {
+ return function() {
+ let bytes = Buffer.from(b64data, 'base64');
+ if (b64data.substring(0, 4) === 'H4sI')
+ bytes = zlib.gunzipSync(bytes);
+ const res = javaDeserialization.parse(bytes);
+ const begin = res[0];
+ const end = res[res.length - 1];
+ expect(begin[0]).to.equal('Begin');
+ expect(begin[1]).to.equal(begin);
+ expect(end[0]).to.equal(end);
+ expect(end[1]).to.equal('End');
+ expect(res.length,
+ 'Number of serialized objects must match args list'
+ ).to.equal(checks.length + 2);
+ return checks.apply(null, res.slice(1, -1));
+ };
+}
+
+describe('Deserialization of', function() {
+
+ it('Exception as regular object', testCase(
+ 'H4sIAAAAAAAAAIVSXWvUQBS9m83WNqAufmGrriBaUWQXQYWSIthllWKsoBWEBWU2ud1OnUzizMSNCqKIr+KrgvoHfBX8AX48FEQEH330TZ99seDc1P2Qgs7DTHJz7plzTu6rH1DJFGxvByvsFqsLJrv1i50VDI3/5OPVl1V9RDgAeQoAjoHKHHa5vAn3oKQVTA1bLmXS8BhbeYip4Yl8cX3snHfi4TfqtexD4ADxaW3/6Sl/79sNiMVllfRYR+CX9ycPz/TerJbBDaASskyjgZ2FzgYhGwOkH8DmCA3j4gJqzboWt20Ed9koLrt+GzxtWHhjUbHQImrtvyD9Dy2BMUpjKXfoLE2VJcRooFobqK63ZYaLRsC18fOUAvEMjJ1nnSSJbZy10Tg3cjtnj87Orh2Y6SdLYe75RwObePds+tTXRw648+AJLnEhizuoAtgSYSgY2WsKpjUJ2RTA+BIXuMBi/PPuxWiWk2hQKS69a6DcFNbQuN3rdLstWKDVMl1oIYf1ZiKEHQayfvCKjJOIL3GKnJz/2nro+Ovvj6sOlAJwha0Q+4T9ncf+TzCsT87B/dVrP2sFTSk0sGsk4SHM5qz7g1Iwn1GK3SYd+YPP+55+YM/LUJoHV/M7WBiEnkt7TqJ25xkdtBx6mLReWzL6DS3112r+AgAA',
+ function(itm) {
+ expect(itm.class.name, "itm.class.name").to.equal('java.lang.RuntimeException');
+ expect(itm.detailMessage, "itm.detailMessage").to.equal('Kaboom');
+ }));
+
+ it('string', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABdAAIc29tZXRleHR1cQB+AAAAAAACcQB+AAR0AANFbmQ=',
+ function(itm) {
+ expect(typeof itm, "typeof itm").to.equal('string');
+ expect(itm, "itm").to.equal('sometext');
+ }));
+
+ it('canaries only', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABdXEAfgAAAAAAAnEAfgADdAADRW5k',
+ function() {
+ }));
+
+ it('long string', testCase(
+ 'H4sIAAAAAAAAAO3JuwnCABRA0Wc0veAUNlnATrATbAWr+CEYQvCTiIVkBjdwAWdxE3eQgGOcU12472+k7SUmm2WZ3/KsyusiW23Lw66ZPT/r1/g6rZKI+ykikibS+aE41ufoYvCIXv8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/tpzdNFL+hg1MVzU+x8AC//OVwACAA==',
+ function(itm) {
+ expect(typeof itm, "typeof itm").to.equal('string');
+ expect(itm, "itm").to.have.lengthOf(131072);
+ expect(itm[0], "itm[0]").to.equal('x');
+ expect(itm[(1 << 17) - 1], "itm[(1 << 17) - 1]").to.equal('x');
+ }));
+
+ it('null', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABcHVxAH4AAAAAAAJxAH4AA3QAA0VuZA==',
+ function(itm) {
+ expect(typeof itm, "typeof itm").to.equal('object');
+ expect(itm, "itm").to.equal(null);
+ }));
+
+ it('duplicate object', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAO2lvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQmFzZUNsYXNzV2l0aEZpZWxkAAAAAAAAEjQCAAFJAANmb294cAAAAHt0AAVkZWxpbXEAfgAEdXEAfgAAAAAAAnEAfgAGdAADRW5k',
+ function(obj1, delim, obj2) {
+ expect(obj1, "obj1").to.equal(obj2);
+ }));
+
+ it('primitive fields', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAOGlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uUHJpbWl0aXZlRmllbGRzAAASNFZ4mrwCAAhaAAJib0IAAmJ5QwABY0QAAWRGAAFmSQABaUoAAWxTAAFzeHAB6xI0QCiuFHrhR65CmQAA////hf////////zr/jh1cQB+AAAAAAACcQB+AAV0AANFbmQ=',
+ function(itm) {
+ expect(itm.i, "itm.i").to.equal(-123);
+ expect(itm.s, "itm.s").to.equal(-456);
+ expect(String(itm.l), "String(itm.l)").to.equal('-789');
+ expect(itm.l.toNumber(), "itm.l.toNumber()").to.equal(-789);
+ expect(itm.l.equals(-789), "itm.l.equals(-789)").to.be.true;
+ expect(itm.by, "itm.by").to.equal(-21);
+ expect(itm.d, "itm.d").to.equal(12.34);
+ expect(itm.f, "itm.f").to.equal(76.5);
+ expect(itm.bo, "itm.bo").to.equal(true);
+ expect(itm.c, "itm.c").to.equal('\u1234');
+ expect(itm, "itm").to.have.all.keys(['i', 's', 'l', 'by', 'd', 'f', 'bo', 'c']);
+ expect(itm.class.serialVersionUID, "itm.class.serialVersionUID").to.equal('0000123456789abc');
+ }));
+
+ it('boxed primitives', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cP///4VzcgAPamF2YS5sYW5nLlNob3J0aE03EzRg2lICAAFTAAV2YWx1ZXhxAH4ABP44c3IADmphdmEubGFuZy5Mb25nO4vkkMyPI98CAAFKAAV2YWx1ZXhxAH4ABP////////zrc3IADmphdmEubGFuZy5CeXRlnE5ghO5Q9RwCAAFCAAV2YWx1ZXhxAH4ABOtzcgAQamF2YS5sYW5nLkRvdWJsZYCzwkopa/sEAgABRAAFdmFsdWV4cQB+AARAKK4UeuFHrnNyAA9qYXZhLmxhbmcuRmxvYXTa7cmi2zzw7AIAAUYABXZhbHVleHEAfgAEQpkAAHNyABFqYXZhLmxhbmcuQm9vbGVhbs0gcoDVnPruAgABWgAFdmFsdWV4cAFzcgATamF2YS5sYW5nLkNoYXJhY3RlcjSLR9lrGiZ4AgABQwAFdmFsdWV4cBI0dXEAfgAAAAAAAnEAfgAUdAADRW5k',
+ function(i, s, l, by, d, f, bo, c) {
+ expect(i.value, "i.value").to.equal(-123);
+ expect(s.value, "s.value").to.equal(-456);
+ expect(l.value.equals(-789), "l.value.equals(-789)").to.be.true;
+ expect(by.value, "by.value").to.equal(-21);
+ expect(d.value, "d.value").to.equal(12.34);
+ expect(f.value, "f.value").to.equal(76.5);
+ expect(bo.value, "bo.value").to.equal(true);
+ expect(c.value, "c.value").to.equal('\u1234');
+ expect(i.class.name, "i.class.name").to.equal('java.lang.Integer');
+ expect(s.class.name, "s.class.name").to.equal('java.lang.Short');
+ expect(l.class.name, "l.class.name").to.equal('java.lang.Long');
+ expect(by.class.name, "by.class.name").to.equal('java.lang.Byte');
+ expect(d.class.name, "d.class.name").to.equal('java.lang.Double');
+ expect(f.class.name, "f.class.name").to.equal('java.lang.Float');
+ expect(bo.class.name, "bo.class.name").to.equal('java.lang.Boolean');
+ expect(c.class.name, "c.class.name").to.equal('java.lang.Character');
+ }));
+
+ it('inherited field', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IARWlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uRGVyaXZlZENsYXNzV2l0aEFub3RoZXJGaWVsZAAAAAAAACNFAgABSQADYmFyeHIAO2lvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQmFzZUNsYXNzV2l0aEZpZWxkAAAAAAAAEjQCAAFJAANmb294cAAAAHsAAADqdXEAfgAAAAAAAnEAfgAGdAADRW5k',
+ function(itm) {
+ expect(itm.class.name, "itm.class.name").to.equal('io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField');
+ expect(itm.class.super.name, "itm.class.super.name").to.equal('io.github.gagern.nodeJavaDeserialization.BaseClassWithField');
+ expect(itm.class.super.super, "itm.class.super.super").to.equal(null);
+ expect(itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].bar, "itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].bar").to.equal(234);
+ expect(itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].foo, "itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithAnotherField'].foo").to.equal(undefined);
+ expect(itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo, "itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo").to.equal(123);
+ expect(itm.bar, "itm.bar").to.equal(234);
+ expect(itm.foo, "itm.foo").to.equal(123);
+ }));
+
+ it('duplicate field', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAQmlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uRGVyaXZlZENsYXNzV2l0aFNhbWVGaWVsZAAAAAAAADRWAgABSQADZm9veHIAO2lvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQmFzZUNsYXNzV2l0aEZpZWxkAAAAAAAAEjQCAAFJAANmb294cAAAAHsAAAFZdXEAfgAAAAAAAnEAfgAGdAADRW5k',
+ function(itm) {
+ expect(itm.class.name, "itm.class.name").to.equal('io.github.gagern.nodeJavaDeserialization.DerivedClassWithSameField');
+ expect(itm.class.super.name, "itm.class.super.name").to.equal('io.github.gagern.nodeJavaDeserialization.BaseClassWithField');
+ expect(itm.class.super.super, "itm.class.super.super").to.equal(null);
+ expect(itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithSameField'].foo, "itm.extends['io.github.gagern.nodeJavaDeserialization.DerivedClassWithSameField'].foo").to.equal(345);
+ expect(itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo, "itm.extends['io.github.gagern.nodeJavaDeserialization.BaseClassWithField'].foo").to.equal(123);
+ expect(itm.foo, "itm.foo").to.equal(345);
+ }));
+
+ it('primitive array', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABdXIAAltJTbpgJnbqsqUCAAB4cAAAAAMAAAAMAAAAIgAAADh1cQB+AAAAAAACcQB+AAV0AANFbmQ=',
+ function(itm) {
+ expect(itm, "itm").to.be.an('Array');
+ expect(itm, "itm").to.have.lengthOf(3);
+ expect(itm[0], "itm[0]").to.equal(12);
+ expect(itm[1], "itm[1]").to.equal(34);
+ expect(itm[2], "itm[2]").to.equal(56);
+ expect(itm.class.name, "itm.class.name").to.equal('[I');
+ }));
+
+ it('nested array', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABdXIAFFtbTGphdmEubGFuZy5TdHJpbmc7Mk0JrYQy5FcCAAB4cAAAAAJ1cgATW0xqYXZhLmxhbmcuU3RyaW5nO63SVufpHXtHAgAAeHAAAAACdAABYXQAAWJ1cQB+AAUAAAABdAABY3VxAH4AAAAAAAJxAH4AC3QAA0VuZA==',
+ function(itm) {
+ expect(itm, "itm").to.be.an('Array');
+ expect(itm, "itm").to.have.lengthOf(2);
+ expect(itm[0], "itm[0]").to.be.an('Array');
+ expect(itm[0], "itm[0]").to.have.lengthOf(2);
+ expect(itm[1], "itm[1]").to.have.lengthOf(1);
+ expect(itm[0][0], "itm[0][0]").to.equal('a');
+ expect(itm[0][1], "itm[0][1]").to.equal('b');
+ expect(itm[1][0], "itm[1][0]").to.equal('c');
+ }));
+
+ it('array fields', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IANGlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQXJyYXlGaWVsZHMAAAAAAAAAAQIAA1sAAmlhdAACW0lbAANpYWF0AANbW0lbAAJzYXQAE1tMamF2YS9sYW5nL1N0cmluZzt4cHVyAAJbSU26YCZ26rKlAgAAeHAAAAADAAAADAAAACIAAAA4dXIAA1tbSRf35E8Zj4k8AgAAeHAAAAACdXEAfgAIAAAAAgAAAAsAAAAMdXEAfgAIAAAAAwAAABUAAAAWAAAAF3VyABNbTGphdmEubGFuZy5TdHJpbmc7rdJW5+kde0cCAAB4cAAAAAJ0AANmb290AANiYXJ1cQB+AAAAAAACcQB+ABJ0AANFbmQ=',
+ function(itm) {
+ expect(itm.ia, "itm.ia").to.be.an('Array');
+ expect(itm.iaa, "itm.iaa").to.be.an('Array');
+ expect(itm.sa, "itm.sa").to.be.an('Array');
+ expect(itm.iaa[1][2], "itm.iaa[1][2]").to.equal(23);
+ expect(itm.sa[1], "itm.sa[1]").to.equal('bar');
+ }));
+
+ it('enum', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABfnIAMWlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uU29tZUVudW0AAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AANPTkV+cQB+AAN0AAVUSFJFRXEAfgAHdXEAfgAAAAAAAnEAfgAJdAADRW5k',
+ function(one, three, three2) {
+ expect(typeof one, "typeof one").to.equal('object');
+ expect(one, "one").to.be.an.instanceof(String);
+ expect(one == 'ONE', "one == 'ONE'").to.be.true;
+ expect(one, "one").to.not.equal('ONE');
+ expect(one.class.name, "one.class.name").to.equal('io.github.gagern.nodeJavaDeserialization.SomeEnum');
+ expect(one.class.isEnum, "one.class.isEnum").to.be.true;
+ expect(one.class.super.name, "one.class.super.name").to.equal('java.lang.Enum');
+ expect(one.class.super.super, "one.class.super.super").to.equal(null);
+ expect(three == 'THREE', "three == 'THREE'").to.be.true;
+ expect(typeof three2, "typeof three2").to.equal('object');
+ expect(three2, "three2").to.be.an.instanceof(String);
+ expect(three2 == 'THREE', "three2 == 'THREE'").to.be.true;
+ expect(three2, "three2").to.equal(three);
+ }));
+
+ it('custom format', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IANWlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQ3VzdG9tRm9ybWF0AAAAAAAAAAEDAAJJAANmb29MAANiYXJ0ABJMamF2YS9sYW5nL1N0cmluZzt4cAAAMDl0AA1IZWxsbywgV29ybGQhdwu16y0AtestALXrLXQACGFuZCBtb3JleHVxAH4AAAAAAAJxAH4ACHQAA0VuZA==',
+ function(itm) {
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm['@'], "itm['@']").to.have.lengthOf(2);
+ expect(Buffer.isBuffer(itm['@'][0]), "Buffer.isBuffer(itm['@'][0])").to.be.true;
+ expect(itm['@'][0].toString('hex'), "itm['@'][0].toString('hex')").to.equal('b5eb2d00b5eb2d00b5eb2d');
+ expect(itm['@'][1], "itm['@'][1]").to.equal('and more');
+ expect(itm.foo, "itm.foo").to.equal(12345);
+ expect(itm.bar, "itm.bar").to.equal('Hello, World!');
+ }));
+
+ it('completely custom format', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAP2lvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uQ29tcGxldGVseUN1c3RvbUZvcm1hdAAAAAAAAAABAwACSQADZm9vTAADYmFydAASTGphdmEvbGFuZy9TdHJpbmc7eHB0AA1IZWxsbywgV29ybGQhdw8AADA5testALXrLQC16y14dXEAfgAAAAAAAnEAfgAHdAADRW5k',
+ function(itm) {
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm['@'], "itm['@']").to.have.lengthOf(2);
+ expect(itm['@'][0], "itm['@'][0]").to.equal('Hello, World!');
+ expect(Buffer.isBuffer(itm['@'][1]), "Buffer.isBuffer(itm['@'][1])").to.be.true;
+ expect(itm['@'][1].toString('hex'), "itm['@'][1].toString('hex')").to.equal('00003039b5eb2d00b5eb2d00b5eb2d');
+ }));
+
+ it('externalizable', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAMWlvLmdpdGh1Yi5nYWdlcm4ubm9kZUphdmFEZXNlcmlhbGl6YXRpb24uRXh0ZXJuYWyNBpwj8+bz8wwAAHhwdw8AAAALtestALXrLQC16y10AAhhbmQgbW9yZXh1cQB+AAAAAAACcQB+AAZ0AANFbmQ=',
+ function(itm) {
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm['@'], "itm['@']").to.have.lengthOf(2);
+ expect(Buffer.isBuffer(itm['@'][0]), "Buffer.isBuffer(itm['@'][0])").to.be.true;
+ expect(itm['@'][0].toString('hex'), "itm['@'][0].toString('hex')").to.equal('0000000bb5eb2d00b5eb2d00b5eb2d');
+ expect(itm['@'][1], "itm['@'][1]").to.equal('and more');
+ expect(itm, "itm").to.have.all.keys(['@']);
+ }));
+
+ it('long externalizable', testCase(
+ 'H4sIAAAAAAAAAFvzloG1tIhBONonK7EsUS8nMS9dzz8pKzW5xHrCuYj5AsWaOUwMDBUFDAwMTCUMrE6p6Zl5hQx1DIzFRQyGmfl66ZklGaVJeumJ6alFeXp5+SmpXkBzXFKLU4syE3MyqxJLMvPz9FwrSoDSiTm9bHOUPz/7/JkHZGQV0EgWIGZgYGRiZmFlY+fg5OLm4eXjFxAUEhYRFROXkJSSlpGVk1dQVFJWUVVT19DU0tbR1dM3MDQyNjE1M7ewtLK2sbWzd3B0cnZxdXP38PTy9vH18w8IDAoOCQ0Lj4iMio6JjYtPSExKTklNS8/IzMrOyc3LLygsKi4pLSuvqKyqrqmtq29obGpuaW1r7+js6u7p7eufMHHS5ClTp02fMXPW7Dlz581fsHDR4iVLly1fsXLV6jVr163fsHHT5i1bt23fsXPX7j179+0/cPDQ4SNHjx0/cfLU6TNnz52/cPHS5StXr12/cfPW7Tt3791/8PDR4ydPnz1/8fLV6zdv373/8PHT5y9fv33/8fPX7z9///0f6f4vYeBIzEtRyM0vSq0oBSUuEGACMdhKGJhd81IAbs5j0qUCAAA=',
+ function(itm) {
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm['@'], "itm['@']").to.have.lengthOf(2);
+ expect(Buffer.isBuffer(itm['@'][0]), "Buffer.isBuffer(itm['@'][0])").to.be.true;
+ expect(itm['@'][0], "itm['@'][0]").to.have.lengthOf(516);
+ expect(itm['@'][0].toString('hex', 0, 4), "itm['@'][0].toString('hex', 0, 4)").to.equal('00000200');
+ expect(itm['@'][0].toString('hex', 4, 8), "itm['@'][0].toString('hex', 4, 8)").to.equal('00010203');
+ expect(itm['@'][1], "itm['@'][1]").to.equal('and more');
+ expect(itm, "itm").to.have.all.keys(['@']);
+ }));
+
+ it('HashMap', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAJ0AANiYXJ0AANiYXp0AANmb29zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAe3h1cQB+AAAAAAACcQB+AAt0AANFbmQ=',
+ function(itm) {
+ expect(typeof itm.obj, "typeof itm.obj").to.equal('object');
+ expect(typeof itm['@'], "typeof itm['@']").to.equal('object');
+ expect(itm.obj.bar, "itm.obj.bar").to.equal('baz');
+ expect(itm.obj.foo.value, "itm.obj.foo.value").to.equal(123);
+ expect(itm.obj, "itm.obj").to.have.all.keys(['foo', 'bar']);
+ expect(itm.map, "itm.map").to.be.an.instanceof(Map);
+ expect(itm.map.get('bar'), "itm.map.get('bar')").to.equal('baz');
+ expect(itm.map.get('foo').value, "itm.map.get('foo').value").to.equal(123);
+ expect(itm.map.size, "itm.map.size").to.equal(2);
+ }));
+
+ it('HashMap', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAJ0AANiYXp0AANiYXJzcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAe3QAA2Zvb3hxAH4ACXVxAH4AAAAAAAJxAH4AC3QAA0VuZA==',
+ function(itm, i123) {
+ expect(typeof itm.obj, "typeof itm.obj").to.equal('object');
+ expect(typeof itm['@'], "typeof itm['@']").to.equal('object');
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm.obj, "itm.obj").to.have.all.keys(['baz']);
+ expect(itm.obj.baz, "itm.obj.baz").to.equal('bar');
+ expect(itm.map, "itm.map").to.be.an.instanceof(Map);
+ expect(itm.map.get('baz'), "itm.map.get('baz')").to.equal('bar');
+ expect(itm.map.get(i123), "itm.map.get(i123)").to.equal('foo');
+ expect(itm.map.size, "itm.map.size").to.equal(2);
+ }));
+
+ it('empty HashMap', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAAAHcIAAAAEAAAAAB4dXEAfgAAAAAAAnEAfgAFdAADRW5k',
+ function(itm) {
+ expect(typeof itm.obj, "typeof itm.obj").to.equal('object');
+ expect(itm.obj, "itm.obj").to.be.an('object').that.is.empty;
+ expect(itm.map, "itm.map").to.be.an.instanceof(Map);
+ expect(itm.map.size, "itm.map.size").to.equal(0);
+ }));
+
+ it('Hashtable', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAE2phdmEudXRpbC5IYXNodGFibGUTuw8lIUrkuAMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAIdwgAAAALAAAAAnQAA2JhcnQAA2JhenQAA2Zvb3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAB7eHVxAH4AAAAAAAJxAH4AC3QAA0VuZA==',
+ function(itm) {
+ expect(typeof itm.obj, "typeof itm.obj").to.equal('object');
+ expect(typeof itm['@'], "typeof itm['@']").to.equal('object');
+ expect(itm.obj.bar, "itm.obj.bar").to.equal('baz');
+ expect(itm.obj.foo.value, "itm.obj.foo.value").to.equal(123);
+ expect(itm.obj, "itm.obj").to.have.all.keys(['foo', 'bar']);
+ expect(itm.map, "itm.map").to.be.an.instanceof(Map);
+ expect(itm.map.get('bar'), "itm.map.get('bar')").to.equal('baz');
+ expect(itm.map.get('foo').value, "itm.map.get('foo').value").to.equal(123);
+ expect(itm.map.size, "itm.map.size").to.equal(2);
+ }));
+
+ it('EnumMap', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5FbnVtTWFwBl19976QfKEDAAFMAAdrZXlUeXBldAARTGphdmEvbGFuZy9DbGFzczt4cHZyADFpby5naXRodWIuZ2FnZXJuLm5vZGVKYXZhRGVzZXJpYWxpemF0aW9uLlNvbWVFbnVtAAAAAAAAAAASAAB4cgAOamF2YS5sYW5nLkVudW0AAAAAAAAAABIAAHhwdwQAAAACfnEAfgAGdAADT05Fc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAHt+cQB+AAZ0AAVUSFJFRXQAA2JhenhxAH4ACXEAfgAOdXEAfgAAAAAAAnEAfgARdAADRW5k',
+ function(itm, one, three) {
+ expect(typeof itm.obj, "typeof itm.obj").to.equal('object');
+ expect(typeof itm['@'], "typeof itm['@']").to.equal('object');
+ expect(itm.obj.THREE, "itm.obj.THREE").to.equal('baz');
+ expect(itm.obj.ONE.value, "itm.obj.ONE.value").to.equal(123);
+ expect(itm.obj, "itm.obj").to.have.all.keys(['ONE', 'THREE']);
+ expect(itm.keyType.name, "itm.keyType.name").to.equal('io.github.gagern.nodeJavaDeserialization.SomeEnum');
+ expect(itm.keyType.isEnum, "itm.keyType.isEnum").to.be.true;
+ expect(itm.map, "itm.map").to.be.an.instanceof(Map);
+ expect(itm.map.get(three), "itm.map.get(three)").to.equal('baz');
+ expect(itm.map.get(one).value, "itm.map.get(one).value").to.equal(123);
+ expect(itm.map.size, "itm.map.size").to.equal(2);
+ }));
+
+ it('ArrayList', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAE2phdmEudXRpbC5BcnJheUxpc3R4gdIdmcdhnQMAAUkABHNpemV4cAAAAAJ3BAAAAAJ0AANmb29zcgARamF2YS5sYW5nLkludGVnZXIS4qCk94GHOAIAAUkABXZhbHVleHIAEGphdmEubGFuZy5OdW1iZXKGrJUdC5TgiwIAAHhwAAAAe3h1cQB+AAAAAAACcQB+AAl0AANFbmQ=',
+ function(itm) {
+ expect(itm.list, "itm.list").to.be.an('Array');
+ expect(itm.list, "itm.list").to.have.lengthOf(2);
+ expect(itm.list[0], "itm.list[0]").to.equal('foo');
+ expect(itm.list[1].value, "itm.list[1].value").to.equal(123);
+ }));
+
+ it('ArrayDeque', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAFGphdmEudXRpbC5BcnJheURlcXVlIHzaLiQNoIsDAAB4cHcEAAAAAnQAA2Zvb3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAB7eHVxAH4AAAAAAAJxAH4ACXQAA0VuZA==',
+ function(itm) {
+ expect(itm.list, "itm.list").to.be.an('Array');
+ expect(itm.list, "itm.list").to.have.lengthOf(2);
+ expect(itm.list[0], "itm.list[0]").to.equal('foo');
+ expect(itm.list[1].value, "itm.list[1].value").to.equal(123);
+ }));
+
+ it('HashSet', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAACdAADZm9vc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAHt4dXEAfgAAAAAAAnEAfgAJdAADRW5k',
+ function(itm) {
+ expect(itm.set, "itm.set").to.be.an.instanceof(Set);
+ expect(itm.set.size, "itm.set.size").to.equal(2);
+ expect(itm.set.has('foo'), "itm.set.has('foo')").to.be.true;
+ }));
+
+});
diff --git a/test/generated.js b/src/test/node/generatedOriginal.js
similarity index 94%
rename from test/generated.js
rename to src/test/node/generatedOriginal.js
index 52ec290..bd61cd2 100644
--- a/test/generated.js
+++ b/src/test/node/generatedOriginal.js
@@ -3,7 +3,10 @@
const chai = require('chai');
const expect = chai.expect;
const zlib = require('zlib');
-const javaDeserialization = require('../');
+const javaDeserialization = require('../../main/node/index');
+
+// Register a classdata parser for the CompletelyCustomFormat test.
+javaDeserialization.registerClassDataParser('CompletelyCustomFormat', '0000000000000001', cls => ({}));
function testCase(b64data, checks) {
return function() {
@@ -26,6 +29,23 @@ function testCase(b64data, checks) {
describe('Deserialization of', function() {
+ it('ArrayDeque', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAFGphdmEudXRpbC5BcnJheURlcXVlIHzaLiQNoIsDAAB4cHcEAAAAAnQAA2Zvb3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAB7eHVxAH4AAAAAAAJxAH4ACXQAA0VuZA==',
+ function(itm) {
+ expect(itm.list, "itm.list").to.be.an('Array');
+ expect(itm.list, "itm.list").to.have.lengthOf(2);
+ expect(itm.list[0], "itm.list[0]").to.equal('foo');
+ expect(itm.list[1].value, "itm.list[1].value").to.equal(123);
+ }));
+
+ it('HashSet', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAACdAADZm9vc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAHt4dXEAfgAAAAAAAnEAfgAJdAADRW5k',
+ function(itm) {
+ expect(itm.set, "itm.set").to.be.an.instanceof(Set);
+ expect(itm.set.size, "itm.set.size").to.equal(2);
+ expect(itm.set.has('foo'), "itm.set.has('foo')").to.be.true;
+ }));
+
it('canaries only', testCase(
'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABdXEAfgAAAAAAAnEAfgADdAADRW5k',
function() {
@@ -182,7 +202,7 @@ describe('Deserialization of', function() {
}));
it('custom format', testCase(
- 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IADEN1c3RvbUZvcm1hdAAAAAAAAAABAwABSQADZm9veHAAADA5dwu16y0AtestALXrLXQACGFuZCBtb3JleHVxAH4AAAAAAAJxAH4ABnQAA0VuZA==',
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IADEN1c3RvbUZvcm1hdAAAAAAAAAABAwACSQADZm9vTAADYmFydAASTGphdmEvbGFuZy9TdHJpbmc7eHAAADA5dAANSGVsbG8sIFdvcmxkIXcLtestALXrLQC16y10AAhhbmQgbW9yZXh1cQB+AAAAAAACcQB+AAh0AANFbmQ=',
function(itm) {
expect(itm['@'], "itm['@']").to.be.an('Array');
expect(itm['@'], "itm['@']").to.have.lengthOf(2);
@@ -190,6 +210,17 @@ describe('Deserialization of', function() {
expect(itm['@'][0].toString('hex'), "itm['@'][0].toString('hex')").to.equal('b5eb2d00b5eb2d00b5eb2d');
expect(itm['@'][1], "itm['@'][1]").to.equal('and more');
expect(itm.foo, "itm.foo").to.equal(12345);
+ expect(itm.bar, "itm.bar").to.equal('Hello, World!');
+ }));
+
+ it('completely custom format', testCase(
+ 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAFkNvbXBsZXRlbHlDdXN0b21Gb3JtYXQAAAAAAAAAAQMAAkkAA2Zvb0wAA2JhcnQAEkxqYXZhL2xhbmcvU3RyaW5nO3hwdAANSGVsbG8sIFdvcmxkIXcPAAAwObXrLQC16y0AtesteHVxAH4AAAAAAAJxAH4AB3QAA0VuZA==',
+ function(itm) {
+ expect(itm['@'], "itm['@']").to.be.an('Array');
+ expect(itm['@'], "itm['@']").to.have.lengthOf(2);
+ expect(itm['@'][0], "itm['@'][0]").to.equal('Hello, World!');
+ expect(Buffer.isBuffer(itm['@'][1]), "Buffer.isBuffer(itm['@'][1])").to.be.true;
+ expect(itm['@'][1].toString('hex'), "itm['@'][1].toString('hex')").to.equal('00003039b5eb2d00b5eb2d00b5eb2d');
}));
it('externalizable', testCase(
@@ -292,21 +323,4 @@ describe('Deserialization of', function() {
expect(itm.list[1].value, "itm.list[1].value").to.equal(123);
}));
- it('ArrayDeque', testCase(
- 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAFGphdmEudXRpbC5BcnJheURlcXVlIHzaLiQNoIsDAAB4cHcEAAAAAnQAA2Zvb3NyABFqYXZhLmxhbmcuSW50ZWdlchLioKT3gYc4AgABSQAFdmFsdWV4cgAQamF2YS5sYW5nLk51bWJlcoaslR0LlOCLAgAAeHAAAAB7eHVxAH4AAAAAAAJxAH4ACXQAA0VuZA==',
- function(itm) {
- expect(itm.list, "itm.list").to.be.an('Array');
- expect(itm.list, "itm.list").to.have.lengthOf(2);
- expect(itm.list[0], "itm.list[0]").to.equal('foo');
- expect(itm.list[1].value, "itm.list[1].value").to.equal(123);
- }));
-
- it('HashSet', testCase(
- 'rO0ABXVyABNbTGphdmEubGFuZy5PYmplY3Q7kM5YnxBzKWwCAAB4cAAAAAJ0AAVCZWdpbnEAfgABc3IAEWphdmEudXRpbC5IYXNoU2V0ukSFlZa4tzQDAAB4cHcMAAAAED9AAAAAAAACdAADZm9vc3IAEWphdmEubGFuZy5JbnRlZ2VyEuKgpPeBhzgCAAFJAAV2YWx1ZXhyABBqYXZhLmxhbmcuTnVtYmVyhqyVHQuU4IsCAAB4cAAAAHt4dXEAfgAAAAAAAnEAfgAJdAADRW5k',
- function(itm) {
- expect(itm.set, "itm.set").to.be.an.instanceof(Set);
- expect(itm.set.size, "itm.set.size").to.equal(2);
- expect(itm.set.has('foo'), "itm.set.has('foo')").to.be.true;
- }));
-
});
diff --git a/test/other.js b/src/test/node/other.js
similarity index 94%
rename from test/other.js
rename to src/test/node/other.js
index d8e9763..cef7666 100644
--- a/test/other.js
+++ b/src/test/node/other.js
@@ -2,7 +2,7 @@
const chai = require('chai');
const expect = chai.expect;
-const javaDeserialization = require('../');
+const javaDeserialization = require('../../main/node');
const parse = javaDeserialization.parse;
describe('Special cases', function() {
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..9433204
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "lib": ["es2018"],
+ "module": "commonjs",
+ "target": "es2018",
+ "allowJs": true,
+ "declaration": true,
+ "emitDeclarationOnly": true,
+ "outDir": "dist"
+ }
+}
\ No newline at end of file